diff --git a/.github/workflows/linux-lua.yml b/.github/workflows/linux-lua.yml index 723b568..6c7ac89 100644 --- a/.github/workflows/linux-lua.yml +++ b/.github/workflows/linux-lua.yml @@ -64,7 +64,7 @@ jobs: - name: Test native titlebar alignment if: runner.os == 'macOS' run: >- - cargo test --locked -p nebula --bin pebrel --features gpui-test-support gpui_shell::workspace::window_titlebar::macos::tests:: + cargo test --locked -p nebula --bin pebrel --features gpui-test-support platform::window_chrome::macos::tests:: - name: Test all Python helpers if: ${{ !cancelled() }} run: python -m unittest discover -s scripts/tests -v diff --git a/nebula_app/src/gpui_shell/workspace/window_titlebar.rs b/nebula_app/src/gpui_shell/workspace/window_titlebar.rs index 56c9377..aa6c875 100644 --- a/nebula_app/src/gpui_shell/workspace/window_titlebar.rs +++ b/nebula_app/src/gpui_shell/workspace/window_titlebar.rs @@ -1,8 +1,5 @@ use super::*; -#[cfg(target_os = "macos")] -pub(super) mod macos; - /// Prepaint records the actual pane column before any titlebar paint runs. Keep /// this cell for the workspace lifetime, so resizing and panel animations do not /// allocate a new shared slot or duplicate the body's layout calculations. @@ -90,6 +87,7 @@ impl NebulaWorkspace { cx: &mut Context, ) -> gpui::Div { let top_tabs = self.tabs_position == nebula_settings::TabsPositionName::Top; + let native_layout = crate::platform::window_chrome::layout(window); let bar = TitleBar::new() // Leave 8px above and below the existing 32px controls. .h(px(48.0)) @@ -97,7 +95,7 @@ impl NebulaWorkspace { .when(settings_active, |bar| { bar.border_b_1().border_color(crate::gpui_shell::theme::settings_hairline(cx)) }) - .when(top_tabs && !cfg!(target_os = "macos"), |bar| { + .when(top_tabs && native_layout.is_none(), |bar| { bar.pl(px(top_tabs::TOP_TAB_LEFT_INSET)) }) .when(top_tabs, |bar| { @@ -120,10 +118,10 @@ impl NebulaWorkspace { // AppKit owns both the control group and its geometry. Read the live // frames so system layout, resize and full-screen transitions agree. - #[cfg(target_os = "macos")] - let bar = { - let (height, inset) = macos::layout(window); + let bar = if let Some((height, inset)) = native_layout { bar.h(px(height)).pl(px(inset)) + } else { + bar }; div() diff --git a/nebula_app/src/gpui_shell/workspace/windowing.rs b/nebula_app/src/gpui_shell/workspace/windowing.rs index 657159d..29265b0 100644 --- a/nebula_app/src/gpui_shell/workspace/windowing.rs +++ b/nebula_app/src/gpui_shell/workspace/windowing.rs @@ -310,25 +310,15 @@ fn workspace_window_options(cx: &mut App, focus: bool, role: WindowRole) -> Wind ) }, ); - WindowOptions { + crate::platform::window_chrome::configure_options(WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), window_min_size: Some(size(px(760.0), px(540.0)).min(&bounds.size)), - titlebar: Some(gpui::TitlebarOptions { - // A manual traffic-light position makes GPUI rewrite AppKit's - // titlebar container, breaking the system toolbar layout. - traffic_light_position: if cfg!(target_os = "macos") { - None - } else { - TitleBar::title_bar_options().traffic_light_position - }, - ..TitleBar::title_bar_options() - }), - app_owns_titlebar_drag: cfg!(target_os = "macos"), + titlebar: Some(TitleBar::title_bar_options()), app_id: Some("pebrel".to_owned()), window_background: crate::gpui_shell::wallpaper::initial_background_appearance(), focus, ..Default::default() - } + }) }, #[cfg(windows)] WindowRole::QuickTerminal => { @@ -392,8 +382,7 @@ fn open_workspace_window( let hwnd_out = hwnd_slot.clone(); let handle = cx.open_window(options, move |window, cx| { window.set_window_title(crate::brand::NAME); - #[cfg(target_os = "macos")] - super::window_titlebar::macos::configure(window); + crate::platform::window_chrome::configure(window); *hwnd_out.borrow_mut() = native_hwnd(window).unwrap_or_default(); #[cfg(windows)] crate::gpui_shell::set_native_window_icon(window); diff --git a/nebula_app/src/platform/mod.rs b/nebula_app/src/platform/mod.rs index a49c7db..868374f 100644 --- a/nebula_app/src/platform/mod.rs +++ b/nebula_app/src/platform/mod.rs @@ -27,6 +27,8 @@ pub mod notifications; pub mod shell; pub mod shell_integration; pub mod startup; +#[cfg(feature = "gpui-shell")] +pub(crate) mod window_chrome; pub use capabilities::CAPABILITIES; diff --git a/nebula_app/src/platform/window_chrome.rs b/nebula_app/src/platform/window_chrome.rs new file mode 100644 index 0000000..47424a0 --- /dev/null +++ b/nebula_app/src/platform/window_chrome.rs @@ -0,0 +1,43 @@ +//! Native window decoration belongs to the platform boundary. Workspace code +//! supplies ordinary GPUI options and uses optional geometry without depending +//! on AppKit types or duplicating platform detection. + +use gpui::{Window, WindowOptions}; + +#[cfg(target_os = "macos")] +mod macos; + +pub(crate) fn configure_options(options: WindowOptions) -> WindowOptions { + #[cfg(target_os = "macos")] + { + let mut options = options; + if let Some(titlebar) = options.titlebar.as_mut() { + // A manual position makes GPUI rewrite AppKit's titlebar container, + // breaking the native toolbar layout. + titlebar.traffic_light_position = None; + } + options.app_owns_titlebar_drag = true; + options + } + #[cfg(not(target_os = "macos"))] + { + options + } +} + +pub(crate) fn configure(_window: &Window) { + #[cfg(target_os = "macos")] + macos::configure(_window); +} + +/// Height and left inset when native controls determine the titlebar geometry. +pub(crate) fn layout(_window: &Window) -> Option<(f32, f32)> { + #[cfg(target_os = "macos")] + { + Some(macos::layout(_window)) + } + #[cfg(not(target_os = "macos"))] + { + None + } +} diff --git a/nebula_app/src/gpui_shell/workspace/window_titlebar/macos.rs b/nebula_app/src/platform/window_chrome/macos.rs similarity index 98% rename from nebula_app/src/gpui_shell/workspace/window_titlebar/macos.rs rename to nebula_app/src/platform/window_chrome/macos.rs index 8302715..016c80d 100644 --- a/nebula_app/src/gpui_shell/workspace/window_titlebar/macos.rs +++ b/nebula_app/src/platform/window_chrome/macos.rs @@ -14,7 +14,7 @@ fn native_window(window: &Window) -> Option> { view.window() } -pub(in super::super) fn configure(window: &Window) { +pub(super) fn configure(window: &Window) { let Some(native) = native_window(window) else { return }; let toolbar = NSToolbar::initWithIdentifier( NSToolbar::alloc(native.mtm()), diff --git a/scripts/conformance/tests/test_posix_lifecycle.py b/scripts/conformance/tests/test_posix_lifecycle.py index 8cd3cb8..817ed50 100644 --- a/scripts/conformance/tests/test_posix_lifecycle.py +++ b/scripts/conformance/tests/test_posix_lifecycle.py @@ -29,7 +29,11 @@ role = sys.argv[2] if role != 'grandchild': next_role = 'child' if role == 'parent' else 'grandchild' subprocess.Popen([sys.executable, '-I', '-S', __file__, str(root), next_role]) -(root / (role + '.pid')).write_text(str(os.getpid())) +# Publish the PID only after the contents are complete. The parent polls for +# file existence, so creating the final path first exposes an empty file. +pid_path = root / (role + '.pid.tmp') +pid_path.write_text(str(os.getpid())) +pid_path.replace(root / (role + '.pid')) while True: if role == 'parent' and (root / 'exit').exists(): code = root / 'exit-code'