merge: 同步主分支并保留全部专项与原生测试

This commit is contained in:
GeekMr committed 2026-09-14 19:58:49 +08:00
commit f4d3afc0cd
11 files changed
+188 -11

No files matched your search

+22 -2
View File
@@ -28,7 +28,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-2022, macos-15, macos-15-intel]
os: [ubuntu-24.04, windows-2022, macos-26, macos-26-intel]
runs-on: ${{ matrix.os }}
timeout-minutes: 120
env:
@@ -52,17 +52,37 @@ jobs:
libwayland-dev libxkbcommon-dev libxkbcommon-x11-dev \
libx11-xcb-dev libxcb1-dev libxcb-render0-dev \
libxcb-shape0-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcb-xkb-dev libxi-dev
- name: Require the AppKit SDK used by this PR
if: runner.os == 'macOS'
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.97.1"
- uses: Swatinem/rust-cache@v2
- name: Test native titlebar alignment
if: runner.os == 'macOS'
run: >-
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
- name: Test all conformance harness helpers
if: ${{ !cancelled() }}
run: python -m unittest discover -s scripts/conformance/tests -v
- name: Test the full Rust workspace
if: ${{ !cancelled() }}
run: cargo test --locked --workspace
- name: Test all GPUI product and interaction cases
run: cargo test --locked -p nebula --bin pebrel --features gpui-test-support
if: ${{ !cancelled() }}
run: cargo test --locked -p nebula --features gpui-test-support
- name: Check source architecture
if: ${{ !cancelled() && runner.os == 'Linux' }}
run: python scripts/check_architecture.py
- name: Test translation and allocation contracts
if: ${{ !cancelled() && runner.os == 'Linux' }}
run: cargo test --locked --manifest-path tools/i18n-contract/Cargo.toml
- name: Check the release workspace
if: ${{ !cancelled() }}
run: cargo check --locked --workspace --release
+7 -2
View File
@@ -302,10 +302,10 @@ jobs:
fail-fast: false
matrix:
include:
- runner: macos-15
- runner: macos-26
architecture: aarch64
native_architecture: arm64
- runner: macos-15-intel
- runner: macos-26-intel
architecture: x86_64
native_architecture: x86_64
runs-on: ${{ matrix.runner }}
@@ -323,6 +323,11 @@ jobs:
- name: Verify native runner architecture
run: test "$(uname -m)" = "${{ matrix.native_architecture }}"
- name: Require the modern AppKit SDK for native window controls
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- name: Test native DMG capacity and mounted contents
run: python3 -m unittest scripts.tests.test_macos_dmg -v
+6 -2
View File
@@ -253,11 +253,11 @@ jobs:
fail-fast: false
matrix:
include:
- runner: macos-15
- runner: macos-26
architecture: aarch64
native_architecture: arm64
asset_architecture: arm64
- runner: macos-15-intel
- runner: macos-26-intel
architecture: x86_64
native_architecture: x86_64
asset_architecture: x64
@@ -272,6 +272,10 @@ jobs:
python-version: "3.13"
- name: Verify native runner architecture
run: test "$(uname -m)" = "${{ matrix.native_architecture }}"
- name: Require the modern AppKit SDK for native window controls
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- name: Test native DMG capacity and mounted contents
run: python3 -m unittest scripts.tests.test_macos_dmg -v
- uses: dtolnay/rust-toolchain@stable
+4 -1
View File
@@ -128,7 +128,10 @@ Keep the extracted directory structure intact:
Install [rustup](https://rustup.rs) and the build dependencies for your platform.
The repository pins Rust 1.97.1 in `rust-toolchain.toml`. Linux dependency packages
are listed in `.github/workflows/release.yml`; macOS requires Xcode command-line
tools. Windows requires Windows 10 1809+ / 11 and a supported Rust linker toolchain.
tools with macOS SDK 26 or newer (`xcrun --sdk macosx --show-sdk-version`) for
the native Liquid Glass window controls. This build requirement does not raise
the macOS 14 minimum runtime version. Windows requires Windows 10 1809+ / 11
and a supported Rust linker toolchain.
```powershell
git clone https://github.com/Kuddev/pebrel
+3
View File
@@ -181,7 +181,10 @@ objc2-foundation = { version = "0.3.1", default-features = false, features = [
objc2-app-kit = { version = "0.3.1", default-features = false, features = [
"std",
"NSColorSpace",
"NSButton",
"NSControl",
"NSResponder",
"NSToolbar",
"NSView",
"NSWindow",
] }
@@ -87,6 +87,7 @@ impl NebulaWorkspace {
cx: &mut Context<Self>,
) -> 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))
@@ -94,8 +95,11 @@ impl NebulaWorkspace {
.when(settings_active, |bar| {
bar.border_b_1().border_color(crate::gpui_shell::theme::settings_hairline(cx))
})
.when(top_tabs && native_layout.is_none(), |bar| {
bar.pl(px(top_tabs::TOP_TAB_LEFT_INSET))
})
.when(top_tabs, |bar| {
bar.pl(px(top_tabs::TOP_TAB_LEFT_INSET)).child(self.render_top_title_bar(
bar.child(self.render_top_title_bar(
files_active,
git_active,
settings_active,
@@ -112,6 +116,14 @@ impl NebulaWorkspace {
))
});
// AppKit owns both the control group and its geometry. Read the live
// frames so system layout, resize and full-screen transitions agree.
let bar = if let Some((height, inset)) = native_layout {
bar.h(px(height)).pl(px(inset))
} else {
bar
};
div()
.relative()
.flex_shrink_0()
@@ -310,7 +310,7 @@ 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(TitleBar::title_bar_options()),
@@ -318,7 +318,7 @@ fn workspace_window_options(cx: &mut App, focus: bool, role: WindowRole) -> Wind
window_background: crate::gpui_shell::wallpaper::initial_background_appearance(),
focus,
..Default::default()
}
})
},
#[cfg(windows)]
WindowRole::QuickTerminal => {
@@ -382,6 +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);
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);
+2
View File
@@ -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;
+43
View File
@@ -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
}
}
@@ -0,0 +1,80 @@
//! Keep the standard window controls in AppKit's native toolbar hierarchy.
use gpui::Window;
use objc2::{MainThreadOnly, rc::Retained};
use objc2_app_kit::{NSToolbar, NSView, NSWindow, NSWindowButton, NSWindowToolbarStyle};
use objc2_foundation::{NSRect, ns_string};
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
fn native_window(window: &Window) -> Option<Retained<NSWindow>> {
let handle = HasWindowHandle::window_handle(window).ok()?;
let RawWindowHandle::AppKit(handle) = handle.as_raw() else { return None };
// SAFETY: GPUI owns this live NSView and calls the adapter on the UI thread.
let view = unsafe { handle.ns_view.cast::<NSView>().as_ref() };
view.window()
}
pub(super) fn configure(window: &Window) {
let Some(native) = native_window(window) else { return };
let toolbar = NSToolbar::initWithIdentifier(
NSToolbar::alloc(native.mtm()),
ns_string!("pebrel-window-controls"),
);
// An empty native toolbar supplies the system's current window-control
// treatment. AppKit retains it; GPUI continues to render the app controls.
native.setToolbarStyle(NSWindowToolbarStyle::Unified);
native.setToolbar(Some(&toolbar));
}
pub(super) fn layout(window: &Window) -> (f32, f32) {
if window.is_fullscreen() {
return (52.0, 8.0);
}
let Some(native) = native_window(window) else { return (52.0, 96.0) };
let Some(close) = native.standardWindowButton(NSWindowButton::CloseButton) else {
return (52.0, 96.0);
};
let Some(zoom) = native.standardWindowButton(NSWindowButton::ZoomButton) else {
return (52.0, 96.0);
};
let Some(content) = native.contentView() else { return (52.0, 96.0) };
let close = close.convertRect_toView(close.bounds(), Some(&content));
let zoom = zoom.convertRect_toView(zoom.bounds(), Some(&content));
control_layout(close, zoom, content.bounds(), content.isFlipped())
}
fn control_layout(close: NSRect, zoom: NSRect, content: NSRect, flipped: bool) -> (f32, f32) {
let center = close.origin.y + close.size.height / 2.0;
let center_from_top = if flipped {
center - content.origin.y
} else {
content.origin.y + content.size.height - center
};
(
2.0 * center_from_top as f32,
(zoom.origin.x + zoom.size.width - content.origin.x + 16.0) as f32,
)
}
#[cfg(test)]
mod tests {
use super::*;
use objc2_foundation::{NSPoint, NSSize};
#[test]
fn app_controls_follow_native_centers_and_leave_room_for_the_group() {
for height in [600.0, 900.0] {
for (inset, center) in [(19.0, 26.0), (12.0, 20.0)] {
for flipped in [false, true] {
let content = NSRect::new(NSPoint::new(5.0, 3.0), NSSize::new(900.0, height));
let y = 3.0 + if flipped { center - 7.0 } else { height - center - 7.0 };
let close = NSRect::new(NSPoint::new(5.0 + inset, y), NSSize::new(14.0, 14.0));
let zoom = NSRect::new(NSPoint::new(5.0 + inset + 46.0, y), close.size);
let (bar_height, left_padding) = control_layout(close, zoom, content, flipped);
assert_eq!(bar_height / 2.0, center as f32);
assert_eq!(left_padding, (inset + 46.0 + 14.0 + 16.0) as f32);
}
}
}
}
}
@@ -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'