main
99
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1f37291957 |
gpui_macos: Release the accessibility adapter when a window is dropped (#64143)
## Objective Closing a GPUI window on macOS does not release that window's native view tree, so every open/close cycle leaves a `GPUIView`, its `CAMetalLayer` and the renderer's command queue alive in the process. A long-running app that opens and closes windows grows without bound; this is the mechanism described in #50737. The reason is a retain cycle: ``` MacWindowState -> accesskit_adapter (crates/gpui_macos/src/window.rs:710) -> NSWindow.contentView (strongly retained by accesskit_macos) -> GPUIView (a subview of that content view) -> Arc<Mutex<MacWindowState>> in its ivar (back to the start) ``` `accesskit_macos::SubclassingAdapter::for_window` keeps a strong `Id<NSView>` of the window's content view, and both the `NSWindow` and the `GPUIView` store an `Arc<Mutex<MacWindowState>>` in their `windowState` ivar. `MacWindow::drop` resets the delegate and tears down the frame source and the visibility callback, but nothing drops the adapter, so the native view tree outlives the window. Worth noting while reading the existing teardown: `gpui_apple::metal_renderer::destroy()` is currently a no-op (`crates/gpui_apple/src/metal_renderer.rs`), so the `renderer.destroy()` call that is already there is not evidence that GPU-side resources were released — what matters is whether the state that owns the renderer can run its destructor at all. ## Solution One line in `MacWindow::drop`: ```rust let mut this = self.0.lock(); drop(this.accesskit_adapter.take()); this.renderer.destroy(); ``` Nothing else in the teardown changes: - `frame_source.take()` stays. `WindowFrameSource`'s `Drop` cancels `frame_requests`, and that dispatch source's context points at the native view, so it has to be cancelled while the view is still valid. - `visibility_callback.take()` stays, as do the sheet handling and the delegate reset. - No view or layer is released by hand. The native view is already handed to its parent view via `autorelease`; the problem is the cycle, not the ownership. ## Testing Done locally on macOS (aarch64), with `stable`: - `cargo fmt -p gpui_macos -- --check` — clean. - `cargo test -p gpui_macos --lib` — 7 passed, 0 failed. - `cargo clippy -p gpui_macos --release --all-targets --all-features -- --deny warnings` — clean. Note: the adapter is only created when Zed runs with `ZED_EXPERIMENTAL_A11Y=1`, so the leak only affects that path and the fix is a no-op otherwise. What isn't covered: - **No automated test.** The cycle needs a real `NSWindow`/`contentView` pair, and the change is a `Drop` impl; a unit test here would be testing `NSView` ownership rather than this crate's logic. `gpui_macos` has no window-lifecycle test harness today, and adding one is more than this fix should carry. - **No memory measurement by me.** I did not run Instruments/`Allocations` against this change, and the minimal-repro numbers quoted in #50737 were measured on a different fork revision, not this one. So the leak here is established from the ownership graph above rather than from a footprint curve I produced. The follow-up that would settle it is a before/after count of live `GPUIView` / `CAMetalLayer` / command-queue objects after closing a window, with accessibility enabled and disabled; I have not run that, and I am not claiming this explains every memory-growth report. - Only the macOS backend is touched, so there is nothing Windows/Linux-specific here. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards (UI/UX and icon guidelines) — no UI change - [ ] Tests cover the new/changed behavior — not applicable, see "What isn't covered" - [x] Performance impact has been considered and is acceptable ## AI disclosure Built with substantial LLM assistance: I had help reading the `accesskit_macos` and `gpui_macos` ownership graph and drafting this description, and I reviewed every line of the diff myself. The change is one line plus a comment. English isn't my first language, so the prose here was machine-polished; happy to restate anything in plain terms. --- Release Notes: - [GPUI] Fixed a memory leak on macOS where closing a window with accessibility enabled left its native view, `CAMetalLayer` and the renderer's resources alive. - Fixed a memory leak on macOS where closing a window left its native view, `CAMetalLayer` and the renderer's resources alive. |
||
|
|
20d55798db |
Fix cargo shear warnings (#64340)
Also updates CI to always run this - that way we ensure that disabling or enabling tests does not become stale at some point and thus can safely add this. Release Notes: - N/A - [GPUI] N/A |
||
|
|
eb55524b57 |
gpui_macos: Fix traffic lights animation during fullscreen exit (#64339)
# Objective Fixes #64050 Going out of fullscreen on MacOS currently gives the traffic lights a janky animation. This seems to be a timing issue during ownership transfer of traffic light rendering from AppKit to Zed. ## Solution Add an additional boolean flag (yes, sad), which keeps track of whether the animation from full screen to windows has completed. ## Testing Tested this on a mac, animation is now buttery smooth. ## Showcase <details> <summary>Click to view showcase</summary> Before: https://github.com/user-attachments/assets/f629f2d8-2466-47e3-9b8b-65975bdd1c61 After: https://github.com/user-attachments/assets/a393d3a7-647d-4784-bc2a-9d67fac9f951 </details> --- Release Notes: - Fixed MacOS traffic lights animation when exiting fullscreen - [GPUI] Fixed MacOS traffic lights animation when exiting fullscreen |
||
|
|
85b8aa5898 |
gpui_macos: Migrate screen capture to objc2 (#64251)
# Objective Get rid of instances of the `block` crate, as it will not compile in a future version of Rust. This gets rid of the `block` crate inside of `gpui_macos`, which means were on the home stretch. ## Solution Migrate `screen_capture.rs` completely to `objc2`. While a more targeted fix is theoretically possible, there were enough upsides where I thought this was worth a full migration. Completely gets rid of the `media` crate, which had manual bindings to `core_media`, which `objc2` can provide. Gets rid of tons of unsafe code, and a lot of code in general. ## Testing Tested manual sharing inside of a Zed channel Release Notes: - N/A - [GPUI] Migrated macOS screen capture to `objc2` |
||
|
|
658460cc2b |
gpui_macos: Fix hover feedback when another application is focused (#64301)
Follow-up #64234 If Zed is inactive, hovering over it produces no hover effects. While this matches other apps like Slack, I think it’s better to preserve existing behavior. This change keeps the bug fix of the original PR. This PR uses `ActiveAlways` for normal windows, matching popup windows. Release Notes: - N/A - [GPUI] N/A |
||
|
|
7a3199f7f2 |
gpui_macos: Improve mouse tracking for normal windows (#64234)
To reproduce: 1. Place Safari over part of Zed, then focus Zed. 2. Right-click Safari to open its context menu, then left-click Safari anywhere in it. 3. Move the pointer over Safari where it covers the Zed. Zed shows hover effects underneath. Use a view tracking area and disable `acceptsMouseMovedEvents` for normal macOS windows. This addresses hover effects on covered content when the pointer moves over another window. [Chromium also requests mouse movement through tracking areas](https://github.com/chromium/chromium/blob/5a43095e95ffe566d1512b9d67d534e3ae05b975/components/remote_cocoa/app_shim/bridged_content_view.mm#L436-L465). A search of its macOS window/view code found no explicit use of `acceptsMouseMovedEvents`, which [defaults to `false`](https://developer.apple.com/documentation/appkit/nswindow/acceptsmousemovedevents). Tracking stays enabled in unfocused windows while the app is active. Before: https://github.com/user-attachments/assets/f21cd3e5-04d5-4d34-b495-02f8d0f68ecf After: https://github.com/user-attachments/assets/89a8b9c9-5aa3-4075-aa91-13e200ea3613 Release Notes: - [GPUI] Fixed mouse tracking on macOS so moving the pointer over another app’s window no longer triggers hover effects in covered GPUI windows. - Fixed an issue on macOS where moving the pointer over another app could trigger hover effects in a Zed window underneath it. --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me> |
||
|
|
6736a09abe |
gpui: Add is_visible and on_visibility_changed API on Window (#64107)
Adds a `WindowVisibility` API to GPUI: `Window::visibility()` / `Window::is_visible()`, `Window::observe_window_visibility`, and `Context::observe_window_visibility`, backed by one platform signal per backend — `NSWindow.occlusionState` on macOS, `WS_VISIBLE` plus the minimized state on Windows, the xdg-shell v6 `suspended` toplevel state on Wayland, mapped state plus `VisibilityNotify` on X11, and the Page Visibility API on web. Only transitions are reported, the initial value is read from the platform when the window is created, and the two `PlatformWindow` methods have no default so a backend cannot silently opt out. The per-platform limits (Windows can't see a fully covered window, pre-v6 Wayland compositors never report `Hidden`) are documented on the type. It also adds `Platform::on_system_sleep` / `App::on_system_sleep`, mirroring the existing wake hook, on macOS (`NSWorkspaceWillSleepNotification`), Windows (`PBT_APMSUSPEND`), and Linux (logind `PrepareForSleep(true)`, which the listener previously discarded). Nothing in Zed subscribes to either yet; the hang-telemetry journal will use visibility to stop measuring frames that could not have been presented, and the sleep/wake pair to bracket time the process was not running. Release Notes: - N/A |
||
|
|
c70724205d |
gpui_macos: Migrate start_external_drag to objc2 (#64033)
# Objective Get rid of instances of the `block` crate, as it will not compile in a future version of Rust. ## Solution Migrate an independent code block to the `objc2` crate family. ## Testing Tested dragging/dropping files & folders from Zed into Finder. There seems to be some highlighting bug with folders, but that existed previously as well. Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov <mail4score@gmail.com> |
||
|
|
00365ad222 |
gpui_macos: Migrate get_sources to objc2 (#64031)
# Objective Get rid of instances of the `block` crate, as it will not compile in a future version of Rust. ## Solution Migrate an independent code block to the `objc2` crate family. ## Testing I tested screen sharing inside of a Zed channel Release Notes: - N/A |
||
|
|
2b56eae5a2 |
gpui_macos: Migrate prompt_for_path to objc2 (#63994)
# Objective
Get rid of instances of the `block` crate, as it will not compile in a
future version of Rust.
## Solution
Migrate to the `objc2` crate family.
This gets rid of `block` inside of `platform.rs` 🎉
Release Notes:
- N/A
|
||
|
|
ded89e373b |
gpui_macos: Migrate register_url_scheme to objc2 (#63945)
# Objective The `block` crate will no longer compile with some unspecified future version of Rust. The long term goal is to get rid of the `block` crate entirely, and this is one such step. ## Solution Migrate a standalone function to the `objc2` crate family. Removes many lines of unsafe code, and switches from `block` to `block2`. ## Testing All of the calls to `objc2` are identical to the ones in the old code. Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov <mail4score@gmail.com> |
||
|
|
3d0649186f |
gpui: Prevent idle sleep during AI response streaming (#53130)
Release Notes: - Added button tu prevent idle sleep during AI response streaming **Context / Motivation:** When an AI agent takes a long time to generate a response, the operating system might enter an idle sleep (or display sleep) state if there is no user interaction (mouse or keyboard). This can interrupt the workflow. This PR aims to prevent the system from sleeping during an active AI turn by invoking native OS APIs, and provides a UI toggle for users to control this behavior. **Key Changes:** 1. **Cross-Platform Idle Sleep Prevention in GPUI (`gpui`, `gpui_*`)** - Added a new `prevent_idle_sleep` method to the `Platform` trait. It returns a RAII-based `PreventIdleSleepToken`, which automatically restores the original sleep policy when dropped. - **macOS**: Utilizes `NSProcessInfo`'s `beginActivityWithOptions` with `NSActivityIdleDisplaySleepDisabled`. - **Windows**: Uses `SetThreadExecutionState` to prevent display and system sleep, incorporating a reference counter to handle concurrent requests safely. - **Linux**: Uses `ashpd` to request `org.freedesktop.portal.Inhibit` (inhibiting `Idle` and `Suspend` states) via D-Bus. 2. **AI Agent State Integration (`crates/acp_thread`)** - Introduced idle sleep prevention logic in `AcpThread`: Acquires the token at the beginning of `run_turn`, and automatically releases it when the turn completes normally, errors out, or is explicitly canceled by the user. - Added comprehensive unit tests to ensure the token is correctly acquired and released under various cancellation and concurrency scenarios. 3. **Settings and UI (`crates/agent_settings`, `crates/agent_ui`)** - Added `AgentOutputIdleSleepControl` to global state. - Added a new toggle button (screen icon) to the toolbar at the bottom of the `ThreadView` (AI conversation panel). Users can hover to view the tooltip and click to toggle the feature on or off. <img width="419" height="132" alt="截屏2026-04-04 16 09 08" src="https://github.com/user-attachments/assets/619da9ff-10f7-46e0-b959-d7c74b16b918" /> --------- Signed-off-by: Xiaobo Liu <cppcoffee@gmail.com> Co-authored-by: Kirill Bulatov <kirill@zed.dev> |
||
|
|
a6cf4032e6 |
Restore macOS windows to their original Space after restart (#58886)
Addresses #9640 (the Spaces part). On macOS, Zed already restores each window's size, position, and display, but not its Space (virtual desktop). So after a restart, all windows open on whatever Space is active at launch, not the Space they were on before. This is the most reported problem in #9640. A window's Space is not part of the window bounds we save. It is handled by AppKit's own window state restoration. So instead of tracking the Space myself, this PR uses that AppKit feature: - I mark `GPUIWindow` as restorable and encode its native state (frame + Space) by calling `encodeRestorableStateWithCoder:` with an `NSKeyedArchiver`. A delegate skips the `NSView`/`NSWindow` objects that do not support `NSSecureCoding`. - I save the blob per workspace in a new `restorable_window_states` table. - When a saved workspace is opened again, I create the window and then replay the blob with `restoreStateWithCoder:`. AppKit then moves the window back to its Space. On other platforms, and when there is no saved blob, this does nothing. Verification: - Added a round-trip test for the database part (save / overwrite / read, and `ON DELETE CASCADE`). - The macOS encode/restore part calls into AppKit, so I could not unit-test it. I checked it by hand: I put several windows on different Spaces, quit with Cmd+Q, started Zed again, and each window came back on the Space it was closed on. I also checked that the saved blob is a real AppKit archive (it starts with `bplist00`). For now this only works for local workspaces. Remote workspaces could use the same way later. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - macOS: Restored windows now reopen on the Space (virtual desktop) they were on, instead of all landing on the currently active Space. --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev> |
||
|
|
b753928cc7 |
macos: Honor "Tiled windows have margins" for the Fill titlebar action (#63759)
# Objective Fixes #52884 On macOS, with **"Double-click a window's title bar"** set to **Fill** and **"Tiled windows have margins"** enabled (System Settings > Desktop & Dock), double-clicking Zed's title bar fills the whole visible frame with no margins, while native apps leave the margin in place. The `"Fill"` arm of `titlebar_double_click` currently falls back to `zoom:`, with a comment stating that there is no documented API for the Fill action. `zoom:` is the classic maximize: it targets the screen's visible frame and never goes through the system tiling path, so the margin preference cannot apply. ## Solution AppKit does implement the action: `_zoomFill:` is the selector behind **Window > Move & Resize > Fill**, and it goes through the system tiling path. Prefer it for the `"Fill"` case, keeping `zoom:` as a fallback when the window does not respond to it. This means the margin geometry stays owned by the OS: neither `EnableTiledWindowMargins` nor `TiledWindowSpacing` has to be read or reimplemented in gpui, and the behavior follows the setting when the user toggles it. The selector is underscore-prefixed, hence the `respondsToSelector:` guard and the `zoom:` fallback. ## Testing Measured on macOS 26.6.2 (25G76), aarch64, on a 2560x1440 display with the Dock on the left, so `visibleFrame` is `(80, 0, 2480, 1410)`. Probe: a plain `NSWindow` (`titled`, `resizable`) in a standalone AppKit binary, with `EnableTiledWindowMargins = 1` and `TiledWindowSpacing` unset (default 8). | action | resulting frame | insets vs `visibleFrame` | | --- | --- | --- | | `zoom:` | `(80, 0, 2480, 1410)` | 0 on all four sides | | `_zoomFill:` | `(88, 8, 2464, 1394)` | 8 pt on all four sides | `respondsToSelector: _zoomFill:` returns `YES` on that build. `cargo check -p gpui_macos` and `cargo fmt -p gpui_macos -- --check` both pass. To reproduce as a reviewer: enable both settings above, then double-click the title bar. Before this change the window touches the screen edges, after it the margin matches Finder or Safari. With "Tiled windows have margins" off, both behave the same. One behavior difference worth flagging: `zoom:` toggles, so a second double-click used to restore the previous frame, while `_zoomFill:` stays filled. AppKit exposes `_zoomUntile:` for the reverse direction, which restores the pre-fill frame in my testing. I left it out to keep the diff minimal, but I am happy to add the toggle if you would prefer that a second double-click untiles. I could not find a way to cover this with an automated test, since the assertion would be about AppKit's own tiling geometry. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed double-clicking the title bar ignoring the macOS "Tiled windows have margins" setting when "Double-click a window's title bar" is set to "Fill". 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Um4zWW8chJ5rwbGXhW8APh --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> |
||
|
|
fd35a813e9 |
gpui_macos: Migrate PlatformWindow::prompt to objc2 on MacOS (#59572)
# Objective Rust 1.96 emits a warning when compiling the `block` crate, that it will no longer compile past some future version of Rust. The `block` crate provides `ConcreteBlock` and `RcBlock`, which are stack- and heap-based functions often passed as callbacks to MacOS. The old `objc` crate family is deprecated and no longer maintained. The goal of the PR is to replace one call to `block::ConcreteBlock` by migrating a standalone portion of code to the objc2 crate family. A full migration to `objc2` by Zed has been long on the horizon (#22408) Additional benefits: The new code is completely typed, uses automatic memory management, and reduces lines of unsafe Rust from 50+ to 1. The lingering unsafe converts a raw pointer stored by `self` to the respective `objc2` type, since the rest of the window implementation does not use the new crates yet. ## Solution Migrate `PlatformWindow::prompt`'s MacOS implementation to `objc2`. ## Testing Direct platform code is quite fragile and hard to test, however you can exercise the codepath by running the following `gpui` example and clicking on the prompt buttons. ```sh cargo run -p gpui --example window ``` I've also built Zed with these changes and tried codepaths which activate prompts, such as deleting a file. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
328ad39817 |
gpui_platform: Expose headless renderer through bench support (#63244)
GPUI's `bench-support` feature provides the benchmark context and headless-renderer interface without enabling `test-support`, but the platform implementation remained available only through `gpui_platform/test-support`. As a result, consumers could not use `#[gpui::bench]` with a fully isolated feature graph because the generated benchmark harness calls `gpui_platform::current_headless_renderer()`. This threads `bench-support` through `gpui_platform`, `gpui_macos`, and `gpui_apple`, exposing the existing Metal headless renderer without widening unrelated test window or prompt APIs. The existing benchmark package now requests the platform's canonical `bench-support` feature. Its other test-backed fixtures are intentionally unchanged and remain outside this focused dependency fix. Validation: - Confirmed `gpui_apple/bench-support`, `gpui_macos/bench-support`, and `gpui_platform/bench-support` resolve no `test-support` feature with `cargo tree --locked --no-default-features --features bench-support -e no-dev,features`. - Ran `cargo check --locked -p benchmarks --benches` to compile every existing `#[gpui::bench]` target against the real platform headless renderer. - Ran `cargo nextest run --locked -p gpui_apple -p gpui_macos -p gpui_platform`. - Ran `./script/clippy` for each changed platform package with `--no-default-features --features bench-support`. - Ran `cargo fmt --check`. Release Notes: - N/A |
||
|
|
8a6aaa73ef |
gpui: Support Windows Restart Manager shutdown (#62987)
Handle the `WM_QUERYENDSESSION` and `WM_ENDSESSION` messages, which are used by Windows when shutting down an application. For example, Restart Manager uses this mechanism to gracefully close applications (e.g. when installing an update to the application). Release Notes: - N/A |
||
|
|
12c7b62b90 |
gpui_macos: Fix fullscreen behavior on non-macOS and after restart (#62819)
Follow-up to #60020. - Fixes simple fullscreen preventing fullscreen from working on Linux and Windows (since I use shared dotfiles). - Fixes quitting Zed in simple fullscreen not correctly restoring the previous window bounds, causing it to reopen maximized. We are not handling opening it back in simple fullscreen yet. Release Notes: - N/A |
||
|
|
d7f85eed4e |
Preserve --user-data-dir across restarts (#62022)
# Objective Fixes #57701. When Zed is launched with `--user-data-dir`, a Zed-initiated restart—such as `workspace: clear trusted worktrees` or Restart to Update—can relaunch Zed without that argument. The restarted process then uses the default data directory instead of the directory selected by the user. ## Solution - Store the resolved custom data directory as GPUI restart arguments alongside the existing optional restart executable path. - Forward those arguments when relaunching on Linux, macOS, and Windows. - Preserve argument boundaries and native `OsString` values, including paths containing spaces. - On Windows updates, carry the arguments through `auto_update_helper.exe` when it launches the updated `Zed.exe`. Normal launches without `--user-data-dir` continue to restart without additional arguments. This does not attempt to preserve unrelated CLI arguments or general launch state. ## Testing - `cargo fmt --all -- --check` - `cargo test -p gpui` - `cargo test -p auto_update --features workspace/test-support` - `cargo check -p auto_update_helper --tests --target x86_64-pc-windows-msvc` - `./script/clippy -p gpui -p gpui_macos -p auto_update -p zed` - `./script/clippy -p auto_update_helper --target x86_64-pc-windows-msvc` The regression tests cover simultaneous restart executable and argument forwarding, Windows paths containing spaces and trailing backslashes, updater-helper argument parsing, and the final `Zed.exe` command. Compilation and unit tests were run on macOS. Linux and Windows runtime behavior was reviewed but not run locally. A full `gpui_windows` cross-target check was attempted, but its `psm` dependency requires the MSVC `lib.exe` tool, which is not available on this macOS host; the standalone Windows updater helper and its tests compile successfully for the Windows target. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards (no UI changes) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed Zed forgetting an explicitly passed `--user-data-dir` argument after restarting (e.g. clicking "Restart to Update") --------- Co-authored-by: Tom Houlé <tom@tomhoule.com> |
||
|
|
e8fcbd476d |
gpui_macos: Add simple fullscreen mode that covers the notch (#60020)
Closes #60013 # Objective Right now Zed can go full screen but it does not allow you to fix the hole screen, by that I mean that Zed can go behind the notch so you don't have extra useless room left. ## Solution You can now use the `fullscreen_mode` = `simple` setting to use the new simple full screen feature, that lives besides the normal full screen feature. But allows you to have an option to go 100% full screen without losing any useless space on your macbook screen. **Note** this is mostly usefull when you have a macbook that has a notch whitch is kinda in the way of your work flow. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase **Before** <img width="5712" height="4284" alt="IMG_0339" src="https://github.com/user-attachments/assets/9f908ffd-7cef-4999-a454-c80f72c40dc8" /> **After** (Note now Zed is behind your notch when using the simple full screen feature) <img width="5712" height="4284" alt="IMG_0360" src="https://github.com/user-attachments/assets/5917ed4d-2a64-4464-a794-bc46fd034521" /> --- Release Notes: - Added support for simple fullscreen mode using the `fullscreen_mode` setting, set it to `simple` to try it out. |
||
|
|
232eaba704 |
Extract shared Apple renderer from gpui_macos (#62649)
Extract parts of `platform_macos` to `gpui_apple` to be used as a shared crate between `gpui_macos` and a future `gpui_ios` for iOS/iPadOS apps. Release Notes: - N/A |
||
|
|
3692029a80 |
Bump rustc to 1.97 (#62395)
Release Notes: - N/A |
||
|
|
ec5ddcf55e |
Tweak renderer resource management (#62069)
Factoring some changes out of https://github.com/zed-industries/zed/pull/61461: - On Metal and wgpu, bulk-upload instance buffers at the start of the frame rather than uploading chunks as we draw them. The depth buffer patch needs this architecture since all opaque quads in the scene are painted before anything else. This also lets us get rid of the awkward retry loop when the GPU buffer is too small for the scene. - On Windows, use a constant buffer for the instance offset rather than creating SRVs with different `FirstElement`s. `FirstElement` is buggy on some vendors' drivers, and reusing the SRV object should reduce D3D runtime overhead. - Misc. refactoring: remove redundant parameters, deduplicate code, create fewer bind groups on wgpu, etc. Fixes https://github.com/zed-industries/zed/issues/49588 Fixes https://github.com/zed-industries/zed/issues/59192 Release Notes: - Fixed an issue where UI elements would flicker on some Windows devices. |
||
|
|
938f8d7454 |
gpui: Add resizable and minimizable state to Window (#61473)
### Objective PR #36859 made native macOS and Windows controls respect `is_resizable` and `is_minimizable`, but custom window decorations cannot access those values from `Window`. Linux custom controls and client-side resize interactions remain active, macOS custom titlebar double-click does not check either option and custom Windows caption buttons still appear enabled even though the backend blocks their actions. ### Solution Expose `is_resizable` and `is_minimizable` on `Window`. Use them to disable custom window controls and titlebar actions and omit client-side resize cursors and hitboxes for non-resizable windows. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A |
||
|
|
a8b25c3131 |
gpui: Restore file drags when re-entering the source window on macOS (#61940)
Follow-up to #58161, which handed file drags to AppKit once the pointer left the window but left the handoff one-way. Re-entering the source window and dropping was not possible. GPUI now suspends the original drag when handing it to the platform and restores it when the drag re-enters the source window, so drop targets receive the original typed payload instead of the synthetic external-paths payload. No release notes this hasn't shipped yet. Release Notes: - N/A |
||
|
|
9a6ed13221 |
Add macOS file drag-out from project panel (#58161)
## Description Adds native macOS file drag out support for Project Panel entries. Local file paths are resolved only when a drag gesture starts, remote or missing paths are skipped, and unsupported platforms keep the default no-op behavior. The macOS implementation starts an AppKit drag session with file URL pasteboard items, allows Finder and other external apps to choose copy or move, keeps in-app GPUI drag/drop behavior in control for internal drags, and clears the active drag state when the native session ends. https://github.com/user-attachments/assets/42909389-1a32-4574-a200-9a14b913f55f Self-Review Checklist: - [X] I've reviewed my own diff for quality, security, and reliability - [X] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [X] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Closes #55370 Release Notes: - Added support for dragging files from the Project Panel to external macOS apps. --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com> |
||
|
|
5aca79139a |
gpui: Defer appearance change callback to avoid reentrant borrow (#61789)
### Objective PR #58902 made native macOS window chrome follow the selected theme, but AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while the settings observer holds an `App` borrow. `handle.update` then fails with `RefCell already borrowed`, leaving `Window::appearance()` and its observers stale. ### Steps to reproduce 1. Set Zed's theme mode to Dark. 2. Set macOS Appearance to Light. 3. Change Zed's theme mode to System. 4. Notice that the window chrome switches to light, but Zed's UI remains dark. https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba ### Expectation Zed should switch to its configured light theme when returning to System mode. ### Solution Defer `Window::appearance_changed` to the foreground executor so the current `App` borrow finishes before refreshing the cached appearance and notifying observers. ### Testing - Added a regression test. ### Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A |
||
|
|
90727b44e5 |
gpui_macos: Drop legacy pre-macOS 12 blur path and its private CGS APIs (#56315)
## Summary
`gpui_macos` links two private CoreGraphics Services APIs
unconditionally:
```rust
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
fn CGSMainConnectionID() -> id;
fn CGSSetWindowBackgroundBlurRadius(...) -> i32;
}
```
Apple rejects App Store submissions that reference private APIs, so any
application built on GPUI is ineligible for the Mac App Store —
regardless of
whether it ever sets `WindowBackgroundAppearance::Blurred`, since the
symbols are
linked either way.
Their only call site is the `NSAppKitVersionNumber <
NSAppKitVersionNumber12_0`
branch: macOS 11 and earlier. On macOS 12+ the same effect already goes
through
`NSVisualEffectView`, which the existing comment notes is the better
path anyway
(it downsamples the backdrop and gives more control over the effect
layer).
This deletes the legacy branch and the extern block.
## Changed from the previous revision of this PR
This originally added a `macos_app_store` feature gate. @reflectronic
suggested
deleting the legacy `WindowBackgroundAppearance::Blurred` support
instead of
carrying a flag — that is the better call and this PR now does that.
It is the stronger fix on the merits, not just the smaller one: deleting
makes
every GPUI consumer App Store eligible, whereas a feature flag only
helps those
who opt in and leaves the default build ineligible. It also removes a
conditional-compilation path rather than adding one. The diff went from
3 files / +13 −7 to **1 file / +23 −52**.
## Notes for review
Two details that are easy to miss:
- `this.native_window.windowNumber()` resolved through cocoa's
**`NSEvent`**
trait rather than `NSWindow` — `id` is untyped, so it picks up every
trait in
scope. Removing that call makes the `NSEvent` import genuinely unused,
so it is
dropped. Verified against a build of unmodified `main`, which does not
emit
that warning, to confirm this is a consequence of the change and not
pre-existing.
- `NSAppKitVersionNumber` and `NSAppKitVersionNumber12_0` had no other
users and
are dropped with it.
The `NSVisualEffectView` branch is preserved verbatim; the only change
to it is
one level of de-indentation.
## Behaviour change
On macOS 11 and earlier, `WindowBackgroundAppearance::Blurred` no longer
applies
a blur — the window falls back to the `NSVisualEffectView` path, which
those
releases handle via `CAProxyLayer` rather than concrete sublayers. macOS
11 is
past end of life. Every other platform and macOS 12+ are unaffected.
## Verification
`cargo check -p gpui_macos` on macOS — clean, no errors and no new
warnings.
The identical change was also applied to a downstream GPUI fork and
built against
a ~60k-line application using `gpui-component`, where CI passed on
macOS, Ubuntu
and Windows.
Release Notes:
- N/A
|
||
|
|
ff57679788 |
Match the macOS app appearance to the selected theme (#58902)
Closes #5049. When the system is in light mode but Zed uses a dark theme, the macOS window chrome — the window border and the titlebar — is rendered light, leaving a pale, low-contrast edge that makes the window look washed out. Zed's own content renders correctly; the affected chrome is drawn by AppKit, not Zed. Why it happens: `NSWindow` conforms to `NSAppearanceCustomization`, so a window's appearance is inherited app → window → view and resolved through `effectiveAppearance` unless an explicit appearance is set. Zed never set one, so every window inherited the *system* appearance and AppKit drew the chrome for light mode regardless of the dark theme. The fix uses the override AppKit provides for exactly this: set `NSApplication.appearance` app-wide, so every window inherits it. VSCode (`window.systemColorTheme`) and JetBrains IDEs do the same. <img width="2600" height="1346" alt="after-light" src="https://github.com/user-attachments/assets/c31376af-8f51-4c36-a62f-55714f63b59c" /> ## GPUI - Add `App::set_window_appearance(Option<WindowAppearance>)`, the setter paired with the existing `App::window_appearance()` getter. `Some(_)` forces a light/dark appearance; `None` clears the override and follows the system again. - macOS sets `NSApplication.appearance` (`None` → `nil`); no-op on other platforms. - Demo: `cargo run -p gpui --example window_appearance`. ## Zed - An explicit light/dark theme (or a static theme) forces the matching chrome; `System` follows the OS. - Wired once at startup via a global settings observer, so every window — including the settings window — stays in sync. | Before | After | | --- | --- | | <img width="3630" height="1878" alt="before-light" src="https://github.com/user-attachments/assets/44c9ab66-c0cf-433a-93b0-17446b34a284" /> | <img width="3686" height="1832" alt="SCR-20260609-nzjk" src="https://github.com/user-attachments/assets/32b0412b-0913-4e68-acba-bf39dcb6aa6a" /> | References: [`NSAppearanceCustomization`](https://developer.apple.com/documentation/appkit/nsappearancecustomization) · [`NSApplication.appearance`](https://developer.apple.com/documentation/appkit/nsapplication/appearance) Release Notes: - Fixed the macOS window border and titlebar not matching the selected theme when the system appearance differed (for example, a dark theme under a light system theme). ([#5049](https://github.com/zed-industries/zed/issues/5049)) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
484ae39d7c |
zed: Unify dependencies (#61381)
# Objective Unify dependencies inside of the root `Cargo.toml` to prevent versions from going out of sync and creating copies of dependencies. ## Testing - All tests pass, nothing touches the `Cargo.lock` ## Review Review is easiest by going through commit by commit. Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
9eb43a8356 |
gpui_macos: Retain pasteboard objects (#61024)
## Objective Prevent a use-after-free when a macOS pasteboard wrapper outlives the autorelease pool in which it was created. Pasteboard cached its NSPasteboard and custom zed-text-hash and zed-metadata NSString objects as raw ids. These constructors return autoreleased objects. A later clipboard read could therefore pass a dangling type identifier to dataForType and crash in objc_msgSend. ## Solution - Retain the pasteboard and custom type objects for the Pasteboard lifetime with StrongPtr. - Copy NSData contents into owned Rust byte buffers before the autoreleased data object can expire. - Add a regression test that drains the creation autorelease pool and then accesses the retained custom types. ## Testing - cargo test -p gpui_macos --features runtime_shaders test_custom_types_survive_creation_autorelease_pool --lib - Built an optimized macOS embedder against the patched GPUI checkout. ## Self-Review Checklist: - [x] I have reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks have justifying comments - [x] The content adheres to Zed UI and icon standards; this change has no UI - [x] Tests cover the changed lifetime behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed a macOS crash that could occur when reading from the clipboard. --- AI Disclosure: I ran into this bug on a project where I'm using GPUI. For my purposes I patched it on my fork I maintain. I had codex (gpt 5.6-sol) send this PR because I think this fix should be upstreamed. |
||
|
|
cd91c84675 |
gpui: Add system notification platform APIs (#61189)
Release Notes: - N/A or Added/Fixed/Improved ... --------- Co-authored-by: John Tur <john-tur@outlook.com> |
||
|
|
9948409b40 |
gpui: Unify raw-window-handle dependency (#61193)
Just something I noticed while skimming through dependencies Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
42d5c64723 |
gpui_macos: Replace per-window CVDisplayLinks with a per-display registry (#60696)
Fixes ZED-7XR CVDisplayLink has no safe teardown: `CVDisplayLinkStop` only flags the io thread to exit "soon" and returns, `CVDisplayLinkRelease` frees the link immediately, and there is no way to learn when a final output callback has finished. This produced two distinct use-after-free crash classes on the display-link thread: the io thread reading the freed link's internals (`CVHWTime::reset`, worked around by leaking the link in #32116), and a straggler output callback dereferencing the freed dispatch source through its context pointer (`dispatch_source_merge_data`, ZED-7XR). Because we tore down a `DisplayLink` on every resize tick, window activation, and occlusion change, the existing workaround also leaked a stopped CVDisplayLink per teardown, unboundedly. This replaces per-window links with a static registry holding one immortal `CVDisplayLink` per display. Windows subscribe to their display's link with the dispatch source that paces their frames; the output callback's context is a display id rather than a pointer, so the only memory it ever dereferences is the registry itself, under its lock. Links start and stop at subscriber-count edges (so multiple windows on one display can't interleave starts/stops incorrectly), and a window's dispatch source lives for the life of the window, is removed from the registry on close, and is then genuinely released. Both crash classes become unreachable — the link because it is never released, the source because a straggler callback either finds it live in the registry or doesn't find it at all — and there are no leaks and no `mem::forget`. Since we never hold the registry lock across CoreVideo calls, no lock cycle through CVDisplayLink's undocumented internal locks is possible; the module doc comment spells out the invariants. This supersedes #60481, which fixed ZED-7XR by leaking the dispatch source on teardown. Release Notes: - Fixed a rare crash on macOS when closing, resizing, or switching between windows, and eliminated a memory leak in display-link teardown |
||
|
|
a932df5550 |
gpui: Implement window attention (#58779)
Release Notes: - On agent notifications, the corresponding zed window will now mark itself for OS level attention --------- Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com> |
||
|
|
9ba1a0116a |
macos: Fix window move controls are disabled (#60620)
Closes https://github.com/zed-industries/zed/issues/60595 cc Anthony-Eid since you did an initial pass on this. # Objective Currenly on macOs there is no way right now after the following regression to use window management short keys to move them around. This worked before, but was regressed initially in https://github.com/zed-industries/zed/pull/59836 that tried fixing a macos 27 clicking delay issue on the titlebar. That accidentally set the `native_window.setMovable_(is_movable as BOOL)` to false. Because of this mocos detects this and disables the window managements actions because it thinks the window can/should not be able to move. ## Solution The macOS 27 click delay actually comes from AppKit's native titlebar dragging, not from `NSWindow.isMovable`. The problem is that `is_movable` is also what enables the Window menu's tiling options, and #59836 used it to turn off dragging, which disabled the tiling menu as a side effect. To fix this, I restored `is_movable` to its real meaning so the tiling menu works again, and added a separate `WindowOptions::app_owns_titlebar_drag` flag for windows that draw their own titlebar and handle dragging via `Window::start_window_move`. On macOS this tells AppKit to stop owning titlebar drags (removing the click delay) without affecting `is_movable`. Zed's main window now sets both `is_movable: true` and `app_owns_titlebar_drag: true`, so the tiling menu stays enabled, the click delay is gone. I also added a `window_movable` gpui example to make this behavior easy to test manually. ## Testing I added a gpui test example that helps testing this bug. You can run the newly added gpui example with 4 different window configurations that should confirm that this change is the right one for all of the window types that we have. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase **After** (note this now shows the window managements entries as they were greyed out.) https://github.com/user-attachments/assets/192cc0e1-15d8-486f-b788-a92bb51fb7d1 **GPUI** (test examples with different window configurations.) https://github.com/user-attachments/assets/c9e0f4d3-606b-4cd7-b4d8-a5bb16508164 --- Release Notes: - Fix macos window management controlls were grayed out/not working |
||
|
|
636e764737 |
gpui: Add parent-anchored native popup windows (with wayland xdg_popup implementation only so far) (#60232)
# Objective gpui can't show UI that extends past the window it belongs to. Menus, dropdowns and tooltips are drawn as elements inside the window, so they clip at its edges. This PR adds a window kind for platform-native popups anchored to a parent window, as groundwork for real native menus, dropdowns and tooltips. ## Solution `WindowKind::AnchoredPopup(PopupOptions)` opens a popup positioned relative to a parent window. Instead of giving the popup an absolute position, you describe where it should go and the platform figures out the rest: - `parent`: the window to anchor to - `anchor_rect`: a rectangle in the parent, e.g. the button that opened the menu - `anchor` and `gravity`: which point of that rect to attach to, and which direction to grow - `constraint_adjustment`: what the platform may do if the popup would leave the screen (slide, flip, resize) - `grab`: menu behavior, the popup takes focus and is dismissed when clicking outside the app The popup's size comes from `WindowOptions::window_bounds`. This model mirrors Wayland's `xdg_positioner`, where the compositor owns positioning and the client can only describe intent. Since that's the most restrictive case, the other platforms can implement the same description later with simple math against screen bounds. Only Wayland is implemented so far, via `xdg_popup` on top of the existing surface implementation. Popups can be parented to toplevels, layer-shell surfaces (a menu opened from a panel) and other popups (nested menus). macOS, Windows, X11 and web reject the kind with `PopupNotSupportedError`, so callers can detect that and fall back to in-window popovers. Some Wayland details that might help during review: - Anchor rects are translated from gpui coordinates into the parent's window geometry space and clamped to it. A rect outside the geometry, or with zero size, is a fatal protocol error - Resizing a mapped popup goes through `xdg_popup.reposition` - Mouse press serials are now recorded on press only, not release. Compositors decline grabs and interactive moves that reference a release serial ## Testing Tested manually on Wayland with an example app: the menu opens anchored below its button, extends past the parent window, flips above the button near the bottom of the screen, and a grabbing popup is dismissed when clicking into another application. Nested menus were tested in one of my projects (ignore that they are ugly, that's just a prototype 😛): https://github.com/user-attachments/assets/2cd3e2e9-87f7-4b02-986f-48e5633e205c I also have a complete runnable example demonstrating it. I did not add it to the PR, because this might give the impression that `WindowKind::AnchoredPopup` are a complete implementation, despite only working on wayland so far: <details> <summary>Click to view example</summary> ```rust //! Example and manual test for platform-native popups (`WindowKind::AnchoredPopup`). //! //! A native popup is a real, parent-anchored window that can extend beyond its parent onto the //! screen, unlike gpui's in-window popovers. Run it, open the menu, and confirm the points listed //! in the window. On a platform without an implementation the button reports that popups are not //! supported instead of opening anything. //! //! Run with: cargo run -p gpui --example popup #![cfg_attr(target_family = "wasm", no_main)] use gpui::{ AnyWindowHandle, App, Bounds, Context, MouseButton, SharedString, Window, WindowBounds, WindowHandle, WindowKind, WindowOptions, div, point, popup::*, prelude::*, px, rgb, size, }; use gpui_platform::application; /// The trigger button, at a fixed position so the popup can anchor to a known rectangle. Real code /// would anchor to the measured bounds of whatever element opens the popup. const BUTTON_BOUNDS: Bounds<gpui::Pixels> = Bounds { origin: point(px(24.), px(24.)), size: size(px(200.), px(32.)), }; const POPUP_SIZE: gpui::Size<gpui::Pixels> = size(px(260.), px(320.)); struct Menu; impl Render for Menu { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { let item = |label: &str| { div() .id(label.to_string()) .px_3() .py_1() .rounded_sm() .hover(|this| this.bg(rgb(0x3a3a3a))) .cursor_pointer() .child(label.to_string()) .on_click(|_, window, _| window.remove_window()) }; div() .id("menu-root") .size_full() .p_1() .flex() .flex_col() .gap_0p5() .bg(rgb(0x2a2a2a)) .text_color(gpui::white()) .rounded_md() .border_1() .border_color(rgb(0x454545)) .child(item("Foo")) .child(item("Bar")) .child(item("Baz")) .child(item("Qux")) .child(item("Alice")) .child(item("Bob")) } } struct PopupExample { menu: Option<WindowHandle<Menu>>, status: SharedString, } impl Default for PopupExample { fn default() -> Self { Self { menu: None, status: "Click \"Open menu\" to open a native popup.".into(), } } } impl PopupExample { /// Closes the menu if it is open. Returns true if a menu was actually open. fn close_menu(&mut self, cx: &mut App) -> bool { match self.menu.take() { Some(menu) => menu .update(cx, |_, window, _| window.remove_window()) .is_ok(), None => false, } } fn toggle_menu(&mut self, parent: AnyWindowHandle, cx: &mut App) { if self.close_menu(cx) { return; } match open_menu(parent, cx) { Ok(menu) => { self.menu = Some(menu); self.status = "Menu open. Dismiss it by selecting an item, clicking elsewhere in \ this window, or clicking another application." .into(); } // A real application would fall back to an in-window popover here. Err(error) => { self.status = format!("Failed to open a native popup: {error}").into(); log::error!("failed to open popup: {error}"); } } } } impl Render for PopupExample { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let bullet = |text: &str| div().child(format!("• {text}")); div() .id("root") .size_full() .bg(rgb(0xf7f7f7)) .text_color(rgb(0x222222)) // Same-app clicks don't auto-dismiss a grabbing popup (see `PopupOptions::grab`). .on_mouse_down( MouseButton::Left, cx.listener(|this, _, _window, cx| { this.close_menu(cx); }), ) .child( div() .size_full() .p_5() .pt(px(76.)) .flex() .flex_col() .gap_3() .child(div().text_xl().child("Native popup test")) .child(div().text_sm().child( "WindowKind::AnchoredPopup opens a real, parent-anchored window that can \ extend past this window onto the screen. Only some platforms implement \ it so far.", )) .child( div() .flex() .flex_col() .gap_1() .text_sm() .text_color(rgb(0x555555)) .child(div().child("Verify:")) .child(bullet("The menu opens anchored below the button.")) .child(bullet( "The menu extends past the bottom edge of this window.", )) .child(bullet( "Near the bottom of the screen, the menu flips above the button.", )) .child(bullet("Clicking another application dismisses the menu.")) .child(bullet( "Selecting an item or clicking in this window dismisses it.", )), ) .child( div() .text_sm() .text_color(rgb(0x333333)) .child(self.status.clone()), ), ) .child( div() .absolute() .left(BUTTON_BOUNDS.origin.x) .top(BUTTON_BOUNDS.origin.y) .w(BUTTON_BOUNDS.size.width) .h(BUTTON_BOUNDS.size.height) .flex() .items_center() .justify_center() .bg(rgb(0xffffff)) .border_1() .border_color(rgb(0xd0d0d0)) .rounded_md() .cursor_pointer() .id("open-menu") .active(|this| this.bg(rgb(0xeeeeee))) .child("Open menu ▾") // Open on mouse-down, not on click, so the grab is taken while the button is still held. .on_mouse_down( MouseButton::Left, cx.listener(|this, _, window, cx| { // Don't let the window handler above close the menu we are opening. cx.stop_propagation(); this.toggle_menu(window.window_handle(), cx); }), ), ) } } fn open_menu(parent: AnyWindowHandle, cx: &mut App) -> anyhow::Result<WindowHandle<Menu>> { cx.open_window( WindowOptions { titlebar: None, // Sizes the popup. The platform decides the position, so the origin is ignored. window_bounds: Some(WindowBounds::Windowed(Bounds { origin: point(px(0.), px(0.)), size: POPUP_SIZE, })), kind: WindowKind::AnchoredPopup(PopupOptions { parent, anchor_rect: BUTTON_BOUNDS, // Anchor to the button's bottom-left and grow down-right so the menu drops beneath it. anchor: PopupAnchor::BottomLeft, gravity: PopupGravity::BottomRight, // Slide horizontally and flip vertically if the menu would leave the screen. constraint_adjustment: PopupConstraintAdjustment::SLIDE_X | PopupConstraintAdjustment::FLIP_Y, offset: point(px(0.), px(4.)), // Grab input so the compositor dismisses the popup on clicks into other applications. grab: true, }), ..Default::default() }, |_, cx| cx.new(|_| Menu), ) } fn run_example() { application().run(|cx: &mut App| { cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(Bounds { origin: point(px(100.), px(100.)), size: size(px(420.), px(300.)), })), ..Default::default() }, |_, cx| cx.new(|_| PopupExample::default()), ) .unwrap(); cx.activate(true); }); } #[cfg(not(target_family = "wasm"))] fn main() { run_example(); } #[cfg(target_family = "wasm")] #[wasm_bindgen::prelude::wasm_bindgen(start)] pub fn start() { gpui_platform::web_init(); run_example(); } ``` </details> ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - N/A |
||
|
|
542bf6e106 |
gpui_macos: Guard start_display_link against nil screens (#60419)
# Objective Prevent a macOS crash in GPUI when AppKit temporarily reports that a visible window has no associated `NSScreen` during display reconfiguration. The crash can happen on this path: ```text NSScreen::deviceDescription gpui_macos::window::display_id_for_screen gpui_macos::window::MacWindowState::start_display_link gpui_macos::window::window_did_change_screen ``` `start_display_link` checked the window occlusion state before creating a display link, but it still assumed `NSWindow.screen()` was non-null. During display changes, sleep/wake, lid close/open, or monitor reconfiguration, AppKit can transiently return `nil` for `screen`, and passing that into `NSScreen::deviceDescription` aborts with a null pointer dereference. I observed this on macOS 26.5 after the system entered a rare display state: once in that state, running GPUI applications would crash immediately after wake. I have not identified the exact OS/display condition that triggers it, so the full wake/reconfiguration scenario is not deterministic, but the crash report consistently points to `NSWindow.screen()` being `nil` when `start_display_link` tries to create a display link. ## Solution Make `display_id_for_screen` explicitly handle a null `NSScreen` by returning `None`. Callers now handle that case by either: - returning early from `start_display_link`, because there is no valid display id to create a display link for yet - skipping a null screen while iterating `NSScreen::screens` The normal non-null screen path is unchanged. This keeps the nil-screen guard at the FFI boundary where `NSScreen::deviceDescription` is called. ## Testing Tested on macOS 26.5. Commands run: ```bash cargo fmt --check -p gpui_macos cargo test -p gpui_macos display_id_for_screen_returns_none_for_null_screen cargo check -p gpui_macos ``` Added a unit test covering the new boundary behavior: `window::tests::display_id_for_screen_returns_none_for_null_screen` ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed a macOS crash that could occur when display configuration changes while a GPUI window is temporarily not associated with a screen. |
||
|
|
5d3498a3df |
gpui_macos: Fix glyph rendering when fonts share a PostScript name (#57250)
On macOS, `load_family` was inserting every font into `font_ids_by_postscript_name` without checking for duplicates. When two installed font files claim the same PostScript name — typically an older Geist Mono left behind alongside the current brew cask — the second insert overwrote the first. After shaping, `id_for_native_font` looked up that PostScript name and got back the *other* font's `FontId`, so the rasterizer drew glyphs from the wrong table See #55472 for the screenshot This adds a local `HashSet` to dedup within a single `load_family` call. Scoping it to one call (rather than the global map) matters: the same family gets reloaded under different `FontKey`s when `FontFeatures` or `FontFallbacks` change, and a global check would skip every font on the reload and break weight selection. Cross-family PostScript name collisions and CoreText fallback substitutions that re-introduce a conflict are out of scope here Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed glyph rendering when fonts share a PostScript name on macOS --------- Co-authored-by: Lukas Wirth <lukas@zed.dev> |
||
|
|
e85a546880 |
gpui: Add system wake callback (#59831)
# Objective GPUI apps currently don't have a cross-platform way to react when the system wakes from sleep. ## Solution Add `Application::on_system_wake`, backed by platform hooks for macOS, Windows, and Linux. The platform listeners are registered lazily once an app installs a wake callback. ## Testing - N/A ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A |
||
|
|
55cc234090 |
Reapply traffic light position after appearance changes (#59712)
## Bug Before switching macOS appearance: <img width="154" height="39" alt="before switching OS theme" src="https://github.com/user-attachments/assets/2444fafe-21a7-47f6-84ac-ec33a61a6523" /> After switching macOS appearance: <img width="167" height="36" alt="after switching OS theme" src="https://github.com/user-attachments/assets/d4a34555-2964-4e92-8d6a-24da7620fc49" /> After focusing the window following the macOS appearance switch: <img width="151" height="37" alt="after focusing the window after switching the OS theme" src="https://github.com/user-attachments/assets/6bc2e996-256d-448d-a11b-fdc2f0e01f58" /> ## Fix Reapply GPUI's custom macOS traffic-light button position after AppKit reports an effective appearance change and after GPUI's appearance observers have been notified. ## Cause GPUI stores a custom `traffic_light_position` for windows that use transparent/custom titlebars. When macOS switches Light/Dark appearance, AppKit can relayout the standard window buttons, which resets the traffic lights to AppKit's default vertical position. The next normal window focus/layout pass moves them back, causing a visible jump. This change keeps the existing appearance observer callback behavior, then re-runs `move_traffic_light()` after the callback has completed. ## Validation - `cargo check -p gpui_macos --release --features runtime_shaders` - Manually verified with a GPUI window using a transparent titlebar, custom `traffic_light_position`, and an appearance observer that refreshes the window. - Toggled macOS Light/Dark appearance while the window was visible; traffic lights no longer shifted upward. Release Notes: - N/A |
||
|
|
8aca6b1eda |
Remove gpui's dependency on async-process (#59358)
This removes gpui's dependency on the `util` crate, which depends on async process. It now depends on `gpui_util` only, and uses `std::process::Command` for the two cases where it was previously using an async `Command`. This lifts the requirement of non-Zed consumers of gpui to depend on our forked async-process (see #59156). Release Notes: - N/A |
||
|
|
215ee03f57 |
gpui_macos: Warn when falling back to NoopTextSystem (#59247)
# Objective Without the `font-kit` feature, `MacPlatform` silently substitutes `gpui::NoopTextSystem`, which accepts fonts and resolves font ids but rasterizes every glyph to an empty bitmap. I hit this with the `component_preview` example https://github.com/zed-industries/zed/pull/59241. ## Solution Log a warning at startup in `MacPlatform::new()` when falling back to `NoopTextSystem`. Skipped in headless mode, where no text is expected to render and the fallback is a reasonable configuration. ## Testing Checked as part of https://github.com/zed-industries/zed/pull/59241 ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) ## Showcase It just render as like without any error: <img width="1312" height="945" alt="cp_pr1_before" src="https://github.com/user-attachments/assets/f5faf3c0-9523-4c5f-9756-e65cbaf3456f" /> --- Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
4f75f5e25c |
Revert "gpui: Fix title bar clicks being delayed on macOS 27 (#58947)" (#59214)
This reverts commit
|
||
|
|
1a9b9f0d35 |
gpui_macos: Fix traffic light hitbox after repositioning (#58534)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A This is a follow up to https://github.com/zed-industries/zed/pull/58169 and https://github.com/zed-industries/zed/pull/58518 after removing a bunch of unsafe usage. Before this, GPUI moved traffic lights by directly changing the native button frames inside AppKit’s existing titlebar layout and the old y-coordinate was based on the native titlebar height, so the buttons moved visually while the titlebar tracking geometry could still be based on the old layout. Now the native titlebar container is resized and repositioned to contain the traffic lights. We capture the original AppKit titlebar/button frames, resize the titlebar container to fit the requested position, move the native close/minimize/zoom buttons inside that container and update AppKit tracking areas. This keeps the visible buttons and hover/click geometry aligned. ### Before https://github.com/user-attachments/assets/77dde6cb-3fc8-4166-bc86-22cf2c93e119 ### After https://github.com/user-attachments/assets/da84ade6-ef47-4e53-ae4a-3779315773f9 --------- Co-authored-by: Yara 🏳️⚧️ <git@yara.blue> |
||
|
|
7f74def4b5 |
gpui_macos: Initialize fallback fonts with primary font weight and style (#56771)
This PR fixes an issue where font properties like bolds and italics would be lost when falling back to a fallback font. The problem we identified was that when loading the fallback fonts, we were just loading them by name. This meant that when the original font had a different font weight (e.g., bold) or style applied to it this would not cascade to the fallback font. To reproduce the problem, we used the following text in a Markdown file: ```md **한글 bold** ``` With the following font configuration in Zed: ```json "buffer_font_family": "Victor Mono", "buffer_font_fallbacks": [ "D2Coding" ] ``` The fonts can be obtained from their respective locations: - [Victor Mono](https://rubjo.github.io/victor-mono/) - [D2Coding](https://github.com/naver/d2codingfont) #### Before <img width="168" height="46" alt="Screenshot 2026-05-14 at 1 15 15 PM" src="https://github.com/user-attachments/assets/f6883e4b-8f2b-4285-a110-0e18d562c51d" /> #### After <img width="176" height="53" alt="Screenshot 2026-05-14 at 1 16 04 PM" src="https://github.com/user-attachments/assets/88d67207-6711-473b-8027-e0acfa58c258" /> Closes #51460. Release Notes: - macOS: Fixed fallback fonts missing weight/style. --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> |
||
|
|
6d9ddb96b2 |
Bench app context phase 2 (#58202)
This PR builds out GPUI's benchmark harness so render benchmarks measure
realistic frame costs using GPUI-owned, runtime-gated instrumentation.
It adds measurements for frame draw time, dirty-to-draw latency,
invalidation coalescing, and frame-budget overruns, and runs benchmark
workloads with production-like concurrency.
## How it works
**Frame timings flow through the GPUI profiler.** `Window::draw` emits a
`FrameTiming { window_id, dirty_at, invalidations, draw_start, draw_end
}` event into a global ring buffer in `gpui::profiler`, mirroring the
existing task-timing channel. Collection is runtime-gated by
`profiler::set_frame_trace_enabled` (one relaxed atomic load when
disabled — no `Instant::now` calls in production). `BenchReport` is a
pure listener: it drains events through a cursor-based
`FrameTimingCollector` and builds histograms in the bench layer.
`Window` carries no bench-only cfg fields, and the same event channel
can later feed the miniprofiler UI or an in-app frame-time HUD.
**`BenchDispatcher`** is a multithreaded `PlatformDispatcher` for
benchmarks: background tasks run on a worker pool (same priority queue
as `LinuxDispatcher`, with task-profiler hooks), timers fire in real
time on a dedicated thread, and foreground tasks queue until the bench
thread drains them with a blocking `run_until_idle()`. Unlike
`TestDispatcher`, work executes in parallel in real time, so wall-clock
measurements reflect production concurrency. In-flight accounting is
panic-safe via drop guards.
**`gpui::bench_platform()`** returns a per-process `TestPlatform` backed
by the `BenchDispatcher`, cached in a thread-local so worker threads
persist across Criterion calibration passes. This replaces the earlier
approach of constructing a real platform per invocation, which had
process-global singleton issues, never ran foreground tasks (no run loop
pumped the main queue), and couldn't open windows on headless CI.
**Text shaping** uses `NoopTextSystem`: deterministic across
machines/font installations and CI-portable. Measured cost of this
trade: ~10% of editor draw time vs `MacTextSystem` (Noop still emits one
glyph per character at fixed advances, so downstream layout/paint
structure is preserved).
**GPU coverage (macOS only for now).** `PlatformHeadlessRenderer` gained
`render_scene`, which encodes and submits the scene to Metal against a
cached offscreen target without blocking on completion or reading pixels
back — matching production `present()` CPU cost (`render_scene_to_image`
would overstate it: it waits for the GPU and copies pixels back).
`TestWindow::draw` forwards scenes to the renderer, the real
`MetalAtlas` means glyph/SVG rasterization happens during paint, and
`bench_renderer` presents after each measured update. Platforms without
a headless renderer degrade to discarding the scene.
## Example
```rust
#[gpui::bench]
fn editor_render(cx: &mut BenchAppContext) {
init_context(cx);
let buffer = cx.update(|cx| { /* build a MultiBuffer */ });
let mut window = cx.add_empty_window();
let editor = window.update(|window, cx| {
let editor = window.replace_root(cx, |window, cx| {
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
editor.set_style(editor::EditorStyle::default(), window, cx);
editor
});
window.focus(&editor.focus_handle(cx), cx);
editor
});
let mut move_down = true;
cx.bench_renderer(editor, move |editor, window, cx| {
if move_down {
editor.move_down(&MoveDown, window, cx);
} else {
editor.move_up(&MoveUp, window, cx);
}
move_down = !move_down;
});
}
```
## Example output (release, M-series)
```
editor_render time: [329.75 µs 330.17 µs 330.69 µs]
GPUI bench report (all observed iterations): editor_render
note: includes Criterion warmup/calibration
window dirty-to-draw:
samples: 31533
mean: 0.321ms
p50: 0.322ms
p90: 0.336ms
p95: 0.342ms
p99: 0.360ms
max: 0.504ms
frame budget overruns total: 0
frame budget overruns max: 0
window draw:
samples: 31533
mean: 0.295ms
p50: 0.295ms
p90: 0.307ms
p95: 0.313ms
p99: 0.330ms
max: 0.455ms
frame budget overruns total: 0
frame budget overruns max: 0
invalidations per frame: mean 5.00, max 5
```
(`invalidations per frame: mean 5.00` is real signal: each `move_down`
notifies the window five times before the draw.)
## Known limitations
- **Draw-per-flush**: the harness draws synchronously when effects flush
rather than coalescing invalidations to a vsync tick, so `dirty-to-draw`
excludes queueing delay, and `frame budget overruns` is a draw-time
budget proxy rather than actual missed presents. A frame-paced mode is
natural follow-up work.
- **GPU submission is measured on macOS only**; other platforms have no
headless renderer yet.
- The GPUI report includes Criterion warmup/calibration samples (noted
in the output); Criterion's `time` is the regression-gating number.
- `run_until_idle` waits for queued, running, and already-due work, but
not for timers that haven't reached their due time — the dispatcher runs
in real time and can't skip ahead like `TestDispatcher`'s virtual clock.
## Future work
- A vsync-like frame-pacing mode (suppress draw-on-flush; tick-driven
draw + present) so dirty-to-draw captures queueing delay
- Record present duration in `FrameTiming` so the report can split draw
vs present
- Benches that scroll through novel content (cold layout caches) and an
agent-panel render bench
- Headless renderers for Windows/Linux
- Move benches into a dedicated crate
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the UI/UX checklist
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
|
||
|
|
5cf4935947 |
gpui: Fix title bar clicks being delayed on macOS 27 (#58947)
Fixes a macOS 27 beta regression where clicks on custom titlebar controls were delayed by the system double-click interval. After updating to macOS 27 dev beta, I noticed that our titlebar buttons had a delay before click events took effect. After some digging, I discovered that AppKit can treat that titlebar region as system-owned and delay click delivery while waiting to disambiguate double-clicks. I noticed that Slack had titlebar buttons that weren't affected by the double-click disambiguation delay, so I had Claude dig through the Chromium/Electron codebase to discover what they did differently. Chromium uses a private AppKit SPI, `_opaqueRectForWindowMoveWhenInTitlebar`, that allows them to set what regions should be treated as owned by a view vs. owned by AppKit. My fix uses the same API and returns the view bounds when `NSWindowStyleMask::NSFullSizeContentViewWindowMask` is set, which happens when a GPUI app uses a full-size content view for a custom titlebar. Otherwise, we return bounds of `NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))` so apps using macOS's native titlebar still work as expected. ### Before https://github.com/user-attachments/assets/ba9e0d2d-b85c-4d98-bc7d-bea00368d9cf ### After https://github.com/user-attachments/assets/b78bd7d4-6b43-48d3-a615-2c7a29e32e71 Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed clicks on titlebar controls being delayed on the macOS 27 beta. |
||
|
|
cc84a7bdd6 |
gpui: Free atlas tile space when removing tiles (#58874)
Partially addresses #54659 `PlatformAtlas::remove` removed the tile from `tiles_by_key` and decremented the texture's refcount, but never returned the tile's rectangle to the etagere allocator. Removed tiles' space could only be reclaimed if the entire texture was dropped, which doesn't happen if glyph/emoji share the texture. Because we don't have correct glyph/emoji removal I added regression tests on all three platforms to prevent this from occurring as well. Note: this doesn't fix glyph eviction or the nil-texture panic from #54659; those are follow ups. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - gpui: Fix GPUI memory growing unboundedly when images are repeatedly loaded and released |
||
|
|
5f9f1cc561 |
Fix macOS system font fallback cascade append (#58020)
Fixes #57916. `append_system_fallbacks` used `.map(...)` for the `CFArrayAppendValue` side effect, but the iterator was never consumed. This changes it to a `for` loop so the fallback descriptors are actually appended. |