T23: the owner's checks on macOS: on macOS 27 gpui's Selection material drew no blur, so blur/macos gives the window's effect view underWindowBackground, radius 30; a button at each end of the title bar hides and shows its column, kept in the settings; shot reads an effect view's blur radius; new comments cut to ten words
CI / platforms (ubuntu-24.04) (push) Successful in 2m9s
CI / platforms (windows-latest) (push) Successful in 2m32s
CI / macos (push) Successful in 17m37s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-15 14:42:28 -04:00
1 parent be8d0e20c5
commit 09930c2934
12 files changed
+216 -55

No files matched your search

+13 -2
View File
@@ -4,6 +4,8 @@ use gpui::{Window, WindowBackgroundAppearance};
use crate::settings::Settings;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "linux")]
mod wayland;
#[cfg(target_os = "linux")]
@@ -32,10 +34,15 @@ pub struct Blur {
impl Blur {
/// The window as `WindowOptions` opened it from these settings.
pub fn new(window: &Window, settings: &Settings) -> Self {
#[cfg(not(target_os = "linux"))]
let shown = appearance(settings);
#[cfg(target_os = "macos")]
if shown == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
let _ = window;
Self {
shown: appearance(settings),
shown,
#[cfg(target_os = "linux")]
wayland: wayland::Effect::on(window),
#[cfg(target_os = "linux")]
@@ -57,6 +64,10 @@ impl Blur {
self.shown = wanted;
window.set_background_appearance(wanted);
log::info!("blur: the window is {wanted:?} on {}", std::env::consts::OS);
#[cfg(target_os = "macos")]
if wanted == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
}
/// Keeps a Linux compositor's blur on the window's pane while Blurred; each frame, sending only a change.
+50
View File
@@ -0,0 +1,50 @@
use anyhow::bail;
use gpui::Window;
use objc::{
class, msg_send,
runtime::{BOOL, Object, YES},
sel, sel_impl,
};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
/// `NSVisualEffectMaterialUnderWindowBackground`.
const UNDER_WINDOW_BACKGROUND: isize = 21;
/// Gives gpui's effect view a blurring material; logs either way.
pub fn blur(window: &Window) {
match effect_view(window) {
Ok(view) => {
unsafe {
let _: () = msg_send![view, setMaterial: UNDER_WINDOW_BACKGROUND];
}
log::info!(
"blur: the window's NSVisualEffectView takes underWindowBackground; gpui's Selection material draws no blur on macOS 27"
);
}
Err(error) => log::error!("blur: the window does not blur: {error:#}"),
}
}
/// The NSVisualEffectView gpui puts under the window's content for `Blurred`.
fn effect_view(window: &Window) -> anyhow::Result<*mut Object> {
let handle = HasWindowHandle::window_handle(window)
.map_err(|error| anyhow::anyhow!("no native handle: {error:?}"))?;
let RawWindowHandle::AppKit(appkit) = handle.as_raw() else {
bail!("not an AppKit window");
};
unsafe {
let view = appkit.ns_view.as_ptr() as *mut Object;
let native: *mut Object = msg_send![view, window];
let content: *mut Object = msg_send![native, contentView];
let views: *mut Object = msg_send![content, subviews];
let count: usize = msg_send![views, count];
for ix in 0..count {
let view: *mut Object = msg_send![views, objectAtIndex: ix];
let effect: BOOL = msg_send![view, isKindOfClass: class!(NSVisualEffectView)];
if effect == YES {
return Ok(view);
}
}
}
bail!("gpui put no NSVisualEffectView under the content")
}
+1 -1
View File
@@ -186,7 +186,7 @@ impl Pty {
#[cfg(all(test, unix))]
impl Pty {
/// Whether the program reads keys one by one, as a line editor does.
/// Whether the program reads raw keys, canonical mode off.
pub(crate) fn reads_keys(&self) -> bool {
use rustix_openpty::rustix::termios::{LocalModes, tcgetattr};
let modes = tcgetattr(&self.master).expect("the pty's modes");
+1 -1
View File
@@ -108,7 +108,7 @@ impl Run {
}
}
/// Types `line` at a prompt, once its line editor reads raw keys.
/// Types `line` once the line editor reads raw keys.
fn send(&mut self, line: &str) {
self.until("line editor", |run| run.pty.reads_keys());
self.pty.write(format!("{line}\r").into_bytes());
+1 -1
View File
@@ -45,7 +45,7 @@ impl Pty {
self.child.id()
}
/// A copy of the master, for tests to read its modes.
/// A copy of the master; tests read its modes.
#[cfg(test)]
pub fn master(&self) -> io::Result<File> {
self.file.try_clone()
+60 -19
View File
@@ -320,14 +320,55 @@ fn show_tab(value: &SharedString, cx: &mut App) {
}
}
/// A header button that hides or shows a column, saved.
fn column_toggle(
workspace: &Entity<Workspace>,
(id, icon): (&'static str, IconName),
tip: String,
flip: fn(&mut Settings),
) -> IconButton {
let workspace = workspace.clone();
IconButton::new(id, icon)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip(tip)
.on_click(move |_, _, cx| {
cx.stop_propagation();
workspace.update(cx, |this, cx| this.toggle(flip, cx));
})
}
impl Render for Workspace {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.background.fit(window);
let settings = SettingsStore::get(cx);
let width = cx.theme().sidebar_width(false);
let workspace = cx.entity();
let tip =
|shown: bool, what: &str| format!("{} {what}", if shown { "Hide" } else { "Show" });
// It leads the first column or ends the last.
let left = column_toggle(
&workspace,
("toggle-sessions", IconName::PanelLeft),
tip(settings.show_sessions, "sessions"),
|settings| settings.show_sessions = !settings.show_sessions,
);
let right = column_toggle(
&workspace,
("toggle-panel", IconName::PanelRight),
tip(settings.show_panel, "Git and Files"),
|settings| settings.show_panel = !settings.show_panel,
);
let mut shell = ColumnShell::new("workspace").glass();
let shown = self
.sessions
.get(self.active)
.map(|session| session.terminal.clone());
let mut terminal = ShellColumn::new("terminal")
.title(self.title().to_string())
.children(shown);
if settings.show_sessions {
let (workspace, home) = (cx.entity(), dirs::home_dir());
let home = dirs::home_dir();
let rows = self.sessions.iter().enumerate().map(|(index, session)| {
let active = index == self.active;
sessions::row(&workspace, index, session, active, home.as_deref(), cx)
@@ -342,25 +383,22 @@ impl Render for Workspace {
workspace.update(cx, |this, cx| this.new_session(window, cx))
});
shell = shell.column(
ShellColumn::new("sessions").width(width).action(new).child(
div()
.id("sessions")
.size_full()
.overflow_y_scroll()
.p_2()
.child(list),
),
ShellColumn::new("sessions")
.width(width)
.leading(left)
.action(new)
.child(
div()
.id("sessions")
.size_full()
.overflow_y_scroll()
.p_2()
.child(list),
),
);
} else {
terminal = terminal.leading(left);
}
let shown = self
.sessions
.get(self.active)
.map(|session| session.terminal.clone());
shell = shell.column(
ShellColumn::new("terminal")
.title(self.title().to_string())
.children(shown),
);
if settings.show_panel {
let tab = settings.panel;
let chosen = match tab {
@@ -392,14 +430,17 @@ impl Render for Workspace {
};
// Git's list draws every row, so terminal output must not redraw it.
let fill = || StyleRefinement::default().size_full();
shell = shell.column(
shell = shell.column(terminal).column(
ShellColumn::new("panel")
.width(width)
.leading(switch)
.action(right)
.children(quiet.then(caption))
.child(body(tab == PanelTab::Git).child(self.git.clone().cached(fill())))
.child(body(tab == PanelTab::Files).child(self.files.clone())),
);
} else {
shell = shell.column(terminal.action(right));
}
// The context sits above the focus root, so its keys work wherever focus is.
div()