# 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>
# Objective
Fixes#62084
Fix the Wayland IME candidate window flickering between two positions
when an inactive Zed window has a terminal producing continuous output.
Terminal wakeup events in the inactive window can trigger IME position
updates, causing its cursor bounds to alternate with those from the
active window.
90d024b88a/crates/terminal_view/src/terminal_view.rs (L1130-L1136)90d024b88a/crates/gpui/src/window.rs (L5617-L5627)
## Solution
Ignore IME position updates from inactive Wayland windows.
This ensures that only the active window can update the cursor rectangle
of the shared Wayland text-input object.
## Testing
To test manually on Linux with Wayland and Fcitx5:
1. Open two Zed windows.
2. Start continuous terminal output in the first window.
3. Use the Chinese IME in an editor in the second window.
4. Verify that the IME candidate window remains at a stable position.
## 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#uxui-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
## Showcase
after fix:
[2026-08-02
22-43-35.webm](https://github.com/user-attachments/assets/ff2e3bc7-7772-481e-82f8-92e6e3fa7275)
---
Release Notes:
- Fixed the IME candidate window flickering when another Zed window has
continuous terminal output
# Objective
- Enable drag'n'drop from Zed in Wayland.
- Partially addresses #13186
- Related to #58161, #61940
## Solution
- Implement wl_data_source support for outbound drags on Wayland
## Testing
Tested on KDE Wayland, kwin 6.7.3
I dragged files to Firefox, to other Zed windows and into Dolphin.
## 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
Can try to record if requested
Release Notes:
- Added support for dragging files from the Project Panel to external
Linux Wayland apps.
# Objective
Closes#61054
This PR fixes Zed window not repainting after they are uncovered on X11.
When an X11 window becomes visible after being covered, X11 sends an
`Expose` event to tell the application that the window contents need to
be presented again. Previously, Zed stored this event and waited for the
periodic refresh loop to request a presentation but, the refresh loop is
stopped while a window is fully hidden to avoid unnecessary rendering.
This meant there might be no refresh tick to handle the pending
exposure.
window is fully hidden -> periodic refresh loop stops
window is uncovered -> expose event -> waits for refresh loop -> window
may remain blank
This change requests one presentation directly after processing the
pending X11 events. Multiple `Expose` events for the same window are
coalesced, and unmapped windows are ignored.
## Solution
window is uncovered -> expose event -> presentation is requested ->
window contents are displayed
Required presentations are also excluded from inactive-window frame
throttling, so the request is not dropped when Zed is unfocused. Regular
rendering for unfocused or fully obscured windows remains throttled.
## Testing
Check the issue being closed on steps to reproduction.
## 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:
- Fixed Zed windows sometimes not repainting after being uncovered on
X11
Closes#58651
Closes FR-123
https://github.com/zed-industries/zed/pull/50406/ while fixing one issue
regressed serial tracking in Wayland. What happens is we conflated
unrelated serials into a common value which is wrong. For example,
`InputMethod` serial does not come from the same source as `MousePress`
or `KeyPress`, and if Zed is running for a while and the user performs
lots of IME commits (`zwp_text_input_v3.commit`), `InputMethod` will
overtake other serial values and will be incorrectly used for clipboard
authorisation/selection by the compositor such as Mutter or kWin. This
will effectively poison the clipboard making Zed the indefinite owner
until we kill Zed. More concretely, we use `get_latest()` on the serial
tracker to extract *the largest serial REGARDLESS of kind* which is
wrong since not every serial kind comes from the same pool.
Release Notes:
- Fixed copy-paste behaviour in Wayland.
# 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 ...
# Objective
Close#61034.
Fix unbounded memory growth when composing text with Fcitx5 under
KWin/Wayland.
While a non-empty preedit is cached, KWin resends it whenever it
receives a text-input-v3 `commit()` ([relevant KWin
code](0e473e87a6/src/wayland/textinput_v3.cpp (L444-L504))).
Zed handles the replayed preedit as a buffer edit. This invalidates the
IME character coordinates, causing Zed to submit the same cursor
rectangle and call `commit()` on the next frame. That commit makes KWin
replay the cached preedit again, forming an infinite feedback loop.
The observed memory growth is caused by retained CRDT state accumulating
indefinitely.
## Solution
Following [SDL's solution to the same
problem](81479d8784),
cache the last submitted cursor rectangle and compare it with the new
rectangle before calling `set_cursor_rectangle()`.
If the rectangles are equal, skip both the rectangle update and its
associated `commit()`. The initial cursor rectangle and actual rectangle
changes during an active composition are still submitted, preserving the
candidate-window positioning behavior introduced by #59911.
## Testing
Manually tested on KDE Plasma with `fcitx5-chinese-addons`.
Before the fix, memory usage grew continuously while preedit text was
present, with `WAYLAND_DEBUG=client` showing text-input events repeating
indefinitely.
After the fix, the repeated events stopped and memory usage remained
stable.
No automated test was added because reproducing the feedback loop
requires compositor-specific KWin text-input-v3 behavior.
## 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:
- Fixed excessive memory usage when composing text with Fcitx5 on KDE
Wayland.
---------
Co-authored-by: Tom Houlé <tom@tomhoule.com>
# Objective
The exclusive zone and exclusive edge of a wlr-layer-shell surface could
only be set once, at creation, through `LayerShellOptions`. This adds
runtime setters so a live layer-shell window such as a panel can update
them without being recreated.
## Solution
Add two methods on `Window`:
- `set_exclusive_zone` updates how much screen space the surface
reserves. A positive value reserves that distance from the anchored
edge, 0 lets the surface be moved out of others' exclusive zones, and -1
ignores reserved space and may extend under other surfaces.
- `set_exclusive_edge` chooses which anchored edge the exclusive zone
applies to, which is needed to disambiguate a corner-anchored surface.
Setting an exclusive edge the surface is not anchored to is a fatal
protocol error, so the edge is validated (it must be a single edge that
the surface anchor contains) and an invalid edge is logged and ignored.
The same validation now also guards the creation path. Both setters only
commit the surface when a change actually applies, and are no-ops on
non-layer-shell windows and on other platforms.
This is where these 2 are documented:
https://wayland.app/protocols/wlr-layer-shell-unstable-v1#zwlr_layer_surface_v1:request:set_exclusive_zonehttps://wayland.app/protocols/wlr-layer-shell-unstable-v1#zwlr_layer_surface_v1:request:set_exclusive_edge
## Testing
- Verified with a small local layer-shell window (a top bar anchored
TOP, LEFT, RIGHT) with buttons that call the setters at runtime
- No automated test was added, since this calls through to the
compositor.
- Reviewers without a layer-shell capable compositor (for example
GNOME/Mutter) cannot exercise this, as `zwlr_layer_shell_v1` is
unavailable there, I tested this on the
[Smithay](https://github.com/Smithay/smithay/) based compositor
[niri](https://github.com/niri-wm/niri)
## 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
# Objective
When xkbcommon compiled with either wayland or x11 only, the current
manifests will prevent it from compiling, despite gpui is already
wayland/x11 seperated.
close#60943
## Solution
Simple changes to manifests.
## Testing
```
cargo tree -p gpui_platform --no-default-features --features {wayland,x11} -e features -i xkbcommon
```
## 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 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
- [ ] Performance impact has been considered and is acceptable
Release Notes:
- N/A
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
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>
Dropping a scheduled runnable cancels its task and makes the next poll
of any awaiter panic with "Task polled after completion." The only paths
where we drop these runnables seem to be during shutdown or extreme
resource exhaustion, so, let's leak the runnables instead of crashing.
On Windows, we also moved to calling the Win32 thread pool API directly,
because 1) WinRT thread pool API is just a wrapper that adds overhead we
don't need, and 2) the closure we pass to the `WorkItemHandler` object
takes ownership of the runnable object, so if the WinRT thread pool
releases the delegate, it can free the runnable without our control.
Release Notes:
- N/A
On Wayland, closing a window in the brief gap between requesting a
portal file dialog and ashpd exporting the window's surface (used to
parent the dialog) crashed Zed with `Unknown opcode 0 for object
<anonymous>@0`
([ZED-9KB](https://zed-dev.sentry.io/issues/7568720776/)). When the
export request fails because the surface is already dead,
wayland-scanner's generated code silently returns an inert proxy, and
ashpd's `Drop` impl later sends `destroy` on it — which wayland-backend
0.3.11 answers with a panic, because it looks up the request opcode
before checking whether the object is null. wayland-backend 0.3.15 fixes
this
([Smithay/wayland-rs#890](https://github.com/Smithay/wayland-rs/issues/890))
by returning an error instead, which the generated destructor discards,
so the drop becomes a harmless no-op. This bumps the lockfile to 0.3.15
and raises the version floor in `gpui_linux` so the fix can't silently
regress via a fresh lockfile.
Closes FR-100
Release Notes:
- Fixed a crash on Linux (Wayland) when a window was closed just as a
file dialog was being opened.
# Objective
Fix two gaps in element hover tracking at window boundaries. Hover was
only re-evaluated on `MouseMove`, so when the pointer left the window no
event fired `on_hover(false)` and the element stayed hovered.
Symmetrically on Wayland, no `Motion` follows `Enter` until the pointer
moves again, so hover was not established at the entry pixel. Both cases
are easy to miss since most hover-styled elements don't sit flush
against the window edge, but they surfaced while implementing
layer_shell popups with input_regions, which should close when stop
hovering.
## Solution
The hover compare-and-fire logic in `div` is refactored into a shared
`update_hover` closure, and a second listener on `MouseExitEvent` clears
hover when the pointer leaves the window. It clears unconditionally
because `MouseExited` doesn't update the tracked mouse position, so a
hit test during that dispatch would still report the element as hovered.
On Wayland, a `MouseMove` is synthesized at the entry position on
`wl_pointer.enter`, mirroring the `MouseExited` already dispatched on
`Leave`.
## Testing
Tested manually on Wayland/Linux: hover on a window-edge element clears
when the pointer leaves the window, and hover is established immediately
when the pointer enters a surface with an element under the entry pixel.
Not tested on other platforms. The `div` change relies on each
platform's existing `MouseExited` dispatch: macOS and X11 emit it, so
they get the exit fix too. Windows never dispatches `MouseExited`
(`WM_MOUSELEAVE` only flips the window-level hover flag), so the
stuck-hover case might remain there, unchanged from before.
Before:
https://github.com/user-attachments/assets/6af83bb3-de9d-40e2-a64c-bdefc98fc96d
After:
https://github.com/user-attachments/assets/721a9b5c-108a-499f-867e-da111836a34a
Here is an example application to test this:
```rs
#![cfg_attr(target_family = "wasm", no_main)]
use gpui::{
App, Bounds, Context, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
};
use gpui_platform::application;
struct HoverExit {
hovered: bool,
}
impl Render for HoverExit {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// Fills the whole window so its edge is the window edge: moving the mouse
// out of the window is what exercises the MouseExited path.
div()
.id("hover-exit")
.size_full()
.flex()
.justify_center()
.items_center()
.text_xl()
.text_color(rgb(0xffffff))
.bg(if self.hovered {
rgb(0x585f58)
} else {
rgb(0x505050)
})
.child(if self.hovered { "HOVERED" } else { "not hovered" })
.on_hover(cx.listener(|this, hovered, _, cx| {
this.hovered = *hovered;
cx.notify();
}))
}
}
fn run_example() {
application().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(240.), px(160.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
app_id: Some("gpui-hover-exit".to_string()),
..Default::default()
},
|_, cx| cx.new(|_| HoverExit { hovered: false }),
)
.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();
}
```
## 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:
- Fixed element hover state not clearing when the mouse leaves the
window
# 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
## Summary
- Refresh GPUI's cached mouse position when window bounds change so
hover hit-testing uses the current cursor position after live resize.
- Return X11 mouse positions in window-relative logical pixels to keep
`PlatformWindow::mouse_position()` consistent with other backends.
Fixes#57354
## Testing
- `cargo fmt -p gpui -p gpui_linux`
- `cargo check -p gpui_linux`
- `cargo check -p gpui`
## Suggested .rules additions
- In GPUI platform backends, `PlatformWindow::mouse_position()` should
return window-relative logical pixels; use separate APIs or fields for
global/device-pixel coordinates.
Release Notes:
- Fixed incorrect hover state while resizing GPUI windows.
# Objective
Wayland windows have no way to restrict which parts of the surface
accept pointer and touch input. This adds support for setting an input
region, so events outside it pass through to whatever is below the
window. This is useful for shaped or partially click-through windows.
Clicks in green area can pass through the window, clicks in red area do
not:
<img width="1057" height="395" alt="image"
src="https://github.com/user-attachments/assets/2039af62-e43b-4834-b877-edad2a8f5ccf"
/>
## Solution
Add `Window::set_input_region`, which takes `Option<&[Bounds<Pixels>]>`:
- `Some(rects)` restricts pointer and touch input to the union of the
rectangles, in window coordinates.
- `Some(&[])` is an empty region, so the window receives no input at all
and is fully click-through.
- `None` resets the region to the default, so the whole window receives
input again.
On Wayland this maps to `wl_surface.set_input_region`, building a
`wl_region` from the rectangles or clearing it for `None`, and commits
so the change applies immediately rather than waiting for the next
frame. The method is a no-op on other platforms.
## Testing
Tested on Linux with Wayland.
- Tested in my own GPUI application, which uses a fullscreen layer for
overlays while allowing clicks outside of the rendered elements to be
passed through to the underlying windows.
- No automated test was added, since this calls through to the
compositor and is checked by observing input routing.
## 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
# Objective
Fix IME candidate window not following cursor position in the integrated
terminal when running fullscreen TUI applications like opencode.
When using IME (Input Method Editor) in Zed's terminal with fullscreen
TUI applications (e.g., opencode), the candidate window does not follow
the cursor position. This issue does not occur in normal terminal usage
(e.g., bash shell).
# Solution
The root cause was three-layer blocking preventing IME position updates
in ALT_SCREEN mode:
1. **ALT_SCREEN blocking**: `selected_text_range()` returned `None` in
ALT_SCREEN mode, causing `selected_bounds()` to return `None`
2. **Missing trigger**: `Event::Wakeup` did not call
`invalidate_character_coordinates()`, so cursor movement did not trigger
IME position updates
3. **Composition blocking**: `update_ime_position()` skipped updates
when `state.composing` was true
Fixes:
- Remove ALT_SCREEN check in `selected_text_range()` so IME position
updates work in fullscreen TUI apps
- Add `window.invalidate_character_coordinates()` in `Event::Wakeup` to
trigger IME position updates when terminal cursor moves
- Remove `state.composing` check in `update_ime_position()` to allow IME
position updates during text-input-v3 composition
- Remove unused `terminal` field from `TerminalInputHandler` struct
# Testing
**Did you test these changes? If so, how?**
- Yes, tested on Linux GNOME Wayland with iBus input method
- Verified IME candidate window correctly follows cursor in opencode
- Verified normal terminal usage with IME still works correctly
**Are there any parts that need more testing?**
- Other IMEs (fcitx, etc.) may need testing
**How can other people (reviewers) test your changes?**
1. Open Zed's integrated terminal
2. Run a fullscreen TUI application like `opencode`
3. Activate IME (e.g., iBus with Chinese input)
4. Type text and observe the IME candidate window follows the cursor
**What platforms did you test these changes on?**
- Linux (GNOME Wayland) - tested
- macOS - not tested (may have different IME behavior)
- Windows - not tested (different code path)
# Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content adheres to Zed's UI standards (UX/UI and icon
guidelines)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
# Showcase
<details>
<summary>Before</summary>
<video
src="https://github.com/user-attachments/assets/ee2fac4e-801b-49af-a57e-32ce25e01db5"
width="320" height="180" />
</details>
<details>
<summary>After</summary>
<video
src="https://github.com/user-attachments/assets/c669ea99-2ffc-4179-b587-a47873e33e70"
width="320" height="180" />
</details>
---
Release Notes:
- Fixed IME candidate window not following cursor in terminal TUI apps
This actually makes it possible for Linux headless client to have
working windows for computation (well, we are in headless mode after
all). This also matches macOS headless client behaviour.
Release Notes:
- N/A
When opening Zed from GNOME on Wayland, the cursor can stay stuck in the
launch spinner state for several seconds because GPUI never tells the
compositor that the launched app's first window has loaded. Desktop
launchers pass this information through `XDG_ACTIVATION_TOKEN`, which
Wayland clients are expected to consume once their first toplevel
surface is ready.
This PR fixes that by reading and removing `XDG_ACTIVATION_TOKEN` during
Wayland client startup, storing it on the client state, and consuming it
on the first XDG toplevel surface via the existing
`xdg_activation_v1.activate(token, surface)` plumbing. Anthony manually
verified this on GNOME Wayland.
docs ref: https://wayland.app/protocols/xdg-activation-v1
Release Notes:
- Fixed Linux Wayland cursor being stuck as a spinner for a couple of
seconds.
This PR adds a conditional `allow(dead_code)` to the `wake_sender` field
to fix this error when building Collab:
```
error: field `wake_sender` is never read
--> crates/gpui_linux/src/linux/platform.rs:126:5
|
116 | pub(crate) struct LinuxCommon {
| ----------- field in this struct
...
126 | wake_sender: Sender<()>,
| ^^^^^^^^^^^
|
= note: `-D dead-code` implied by `-D warnings`
= help: to override `-D warnings` add `#[expect(dead_code)]` or `#[allow(dead_code)]`
error: could not compile `gpui_linux` (lib) due to 1 previous error
warning: build failed, waiting for other jobs to finish...
```
https://github.com/zed-industries/zed/actions/runs/28374439219/job/84060893437
Release Notes:
- N/A
This PR fixes the default behaviour of setting `app_id` to `None` which
breaks the rules based management by KWin as the first commit doesn't
have the actual `app_id` and apply initially setting stops checking for
the window by when it updates.
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
Closes#53962
Release Notes:
- Fixed KWin not respecting Zed's rules due to mismatch of toplevel
app_id at startup
Improves `gpui_linux` error messages for missing feature flags.
Currently, you'd need to look at the source & Rust backtrace to diagnose
the error shown when the feature flags are missing.
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:
- Improve `gpui_linux` error messages for missing feature flags
---------
Co-authored-by: Tom Houlé <tom@tomhoule.com>
# 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
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
Closes FR-44
Unlike X11, where Zed already times out clipboard reads, on Wayland we
had no timeout at all. We read the clipboard from a pipe the source app
writes into, and we were doing a blocking `read_to_end` on it. So if the
other app opened the pipe but never wrote anything, or stalled partway
through, the read would block forever and hang Zed.
This PR replaces it with a non-blocking read driven by `poll` with a 4s
timeout, so:
- a stalled writer fails cleanly instead of freezing us.
- a slow writer that's still making progress on a large payload keeps
working.
This roughly mirrors what the X11 path already does. I have tested
pasting text, large image, drag-n-drop, etc cases.
To Reproduce:
1. Copy text from a web page in Firefox.
2. `kill -STOP "$(pgrep -o firefox)"`
3. Paste into Zed.
Zed hangs indefinitely.
<img width="400"
src="https://github.com/user-attachments/assets/532d1c55-1500-4143-8227-127e0024efba"
/>
Release Notes:
- Fixed a freeze on Linux Wayland when reading the clipboard from a slow
or unresponsive application.
Self-Review Checklist:
- [X] I've reviewed my own diff for quality, security, and reliability
- [ ] 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
- [ ] Performance impact has been considered and is acceptable
Fix an issue introduced by PR #58237 where IME could not be enabled when
multiple windows were open.
The fix ensures that `update_ime_enabled` only updates IME state for the
active window.
Release Notes:
- N/A
Self-Review Checklist:
- [X] I've reviewed my own diff for quality, security, and reliability
- [ ] 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
- [ ] Performance impact has been considered and is acceptable
This PR follows the approach from #51041 and currently includes only the
Wayland implementation.
[录屏 2026-06-01
23-11-17.webm](https://github.com/user-attachments/assets/c9f87503-4d64-4868-b9d8-5b832ade3e5a)
Release Notes:
- On Linux wayland, the IME is disabled in Vim normal and visual modes.
## Summary
This PR fixes horizontal jumping of the IME candidate window during text
composition.
On the shared `selected_bounds` path used by Windows, the candidate
window no longer follows the preedit caret character-by-character.
Instead, it anchors to the start of the current visual line, which keeps
the candidate window stable while typing.
Linux Wayland uses a different IME positioning path, so it was not
affected by the original implementation in this PR. This PR now also
includes a Wayland-specific adjustment so that Wayland uses the same
visual-line anchoring behavior.
## Changes
1. **Shared / Windows path**
Updated `selected_bounds` in `crates/gpui/src/platform.rs` to use a
visual-line-aware anchor for preedit text.
This removes the distracting horizontal movement of the candidate window
while typing and keeps the anchor aligned with the active visual line.
2. **Linux Wayland path**
Updated the Wayland IME area calculation in
`crates/gpui_linux/src/linux/wayland/window.rs` to use the same
visual-line-start anchoring strategy for preedit text.
This brings Linux Wayland in line with the Windows behavior while
preserving Wayland's platform-specific IME handling.
## Notes
I had previously tried a separate Wayland-specific fix in
5d0c96872b, but later reverted it in
8cfe7a2a54 because the behavior was not
good enough and it regressed the original positioning behavior.
The current Wayland implementation is a new, simpler approach that keeps
the original behavior intact while also removing horizontal
candidate-window jumping.
## Visuals
### Windows / shared path
* **Before the fix:** (The candidate box moves along with the preedit
text, which is distracting)
<img width="918" height="675" alt="before"
src="https://github.com/user-attachments/assets/29cb05e4-3e99-4b54-9ce3-78710b307ce6"
/>
<img width="478" height="682" alt="before_1"
src="https://github.com/user-attachments/assets/cff38b65-96dd-4ed7-b06b-7fbcd448fab9"
/>
* **After the fix:** (Candidate box stays fixed at the line start during
typing, correctly jumps to new lines or follows active segments)
<img width="918" height="675" alt="after"
src="https://github.com/user-attachments/assets/4f50a710-dc0d-4959-a313-1184122ab759"
/>
<img width="478" height="683" alt="after_1"
src="https://github.com/user-attachments/assets/8e9df121-ffcc-45ad-ba0b-f1ad3cef87bc"
/>
### Linux Wayland
* **Before / previous behavior**
<img width="552" height="796" alt="before"
src="https://github.com/user-attachments/assets/e3e84312-c626-41cb-945f-66c13aa96df6"
/>
* **After / current implementation**
<img width="546" height="772" alt="after"
src="https://github.com/user-attachments/assets/a01ab6d7-a3b6-4d56-a1cb-b2b112b6b914"
/>
---
## 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 (manual verification only)
* [x] Performance impact has been considered and is acceptable
Release Notes:
- N/A
We have a lot of long blocking tasks on both the foreground and
background, this is a start of getting some insight into those.
We will now log tasks running longer then 100ms on the foreground or
background. Hanging actions will also be logged including their name. We
simultaneously collect statistics on task and action performance and
send those to telemetry. This includes quantiles and averages for each
hanging task.
Finally this adds tree dev actions:
- hang action
- hang foreground
- hang background
These cause a hang to check if hang reporting is working and in the
future telemetry.
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
Closes #ISSUE
Release Notes:
- Added logging and telemetry of tasks and actions with performance
issues
GPUI AccessKit integration
This PR is replacing #51097 , and is much more limited in scope. This PR
*ONLY* adds AccessKit support to GPUI, and doesn't touch Zed. Once this
lands, we can start adding aria attributes to Zed's components.
This PR is the first step to addressing #41138 .
Release Notes:
- N/A or Added/Fixed/Improved ...
---------
Co-authored-by: John Tur <john-tur@outlook.com>
Co-authored-by: Zed Zippy <234243425+zed-zippy[bot]@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
The WgpuRenderer defaults to VK_PRESENT_MODE_FIFO_KHR (vsync), which
blocks vkQueuePresentKHR until the compositor releases a buffer via
wl_surface.frame. On some Wayland compositor+driver combinations
(notably NVIDIA proprietary + Hyprland, but also observed on KDE/GNOME +
AMD RADV), these frame callbacks can be delayed or lost, stalling the
entire calloop event loop for tens of seconds.
VK_PRESENT_MODE_MAILBOX_KHR does not block on vblank: it replaces the
pending frame in a single-entry queue. This avoids the stall entirely.
The renderer already falls back to Fifo automatically if Mailbox is
unsupported by the driver.
The WgpuSurfaceConfig has had a preferred_present_mode field since
#50815 (added for Android lifecycle transitions with the same
rationale). This commit sets it to Mailbox in the Wayland window
creation path only. X11 is not affected.
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
Note on tests: This change is in the Wayland platform's window creation
path (WaylandWindowState::new). The surface configuration is delegated
to WgpuRenderer which already has test coverage for
preferred_present_mode fallback logic. A full integration test would
require a running Wayland compositor in CI. Verified manually and tested
against the renderer's unwrap_or(Fifo) safety net by inspecting
surface_caps.present_modes on both NVIDIA proprietary and Mesa RADV
drivers.
Closes: #50229Closes: #55345Closes: #39097Closes: #50734
Refs: #38497, #52009, #52403, #50574, #49961, #47750, #46203, #50195,
#50283, #42164, #39156, #39234, #35948, #32618
Release Notes:
- Fixed UI freezes on Linux (Wayland) when on certain GPU/driver
combinations
---------
Co-authored-by: Neel <neel@zed.dev>
I've been working on a GPUI application which has a button for copying
text. After starting the app in Wayland in Linux I noticed that when I
click the button to copy text it does not work. After interacting with
other buttons and/or copying text via keyboard shortcuts then my copy
button works.
I thought I was handling something wrong, but then I noticed that Zed
also exhibits this behavior: upon starting Zed if you highlight text and
right-click and select "Copy" then nothing happens. After you interact
with other UI elements and/or keyboard shortcuts then copy buttons seem
to work fine.
This paper cut has been annoying me and with the help of Claude Code I
arrived at this small fix. This solves the problem and this appears to
be the way the serial should be handled for clipboard actions via button
clicks in Wayland, as far as I've been able to learn. Here are a couple
related Wayland documentation pages that I double checked:
1.
https://wayland.app/protocols/wayland#wl_data_device:request:set_selection
2.
https://wayland.freedesktop.org/docs/html/ch04.html#sect-Protocol-data-sharing-devices
- [x] Added ~a solid test coverage and/or~ screenshots from doing manual
testing
- Here's a screen recording of the behavior without this fix:
[wayland-clipboard-problem.webm](https://github.com/user-attachments/assets/6c7d3b3b-56fe-4083-a011-4906ee9bfbec)
- Here's a screen recording of the behavior with this fix:
[wayland-clipboard-fix.webm](https://github.com/user-attachments/assets/167ee731-118a-4f67-8489-6cfaca4389ce)
- Here's a screen recording of the problem in Zed itself:
[wayland-clipboard-problem-zed.webm](https://github.com/user-attachments/assets/e41a40e1-54aa-4b3f-a2c6-f3e90d06d50f)
- [x] Done a self-review taking into account security and performance
aspects
- None that I could find or think of
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- I don't believe this is applicable...
Release Notes:
- Improved first mouse-driven text copy action on Wayland
Fixes ZED-5K1
Release Notes:
- Fixed a panic on windows when a monitor disappears from windows
monitor enumeration
---------
Co-authored-by: John Tur <john-tur@outlook.com>
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
Closes#32792Closes#38266Closes#54133
Release Notes:
- Fixed graphical corruption that could occur when using Wayland
----
**What**: Fixes flickering on Wayland (Sway/wlroots) under CPU load
(e.g. rust-analyzer running). The bug only reproduces in release builds
- debug builds are too slow to hit the race window.
Environment where this was reproduced: Intel GPU (both Xe KMD and i915),
Mesa 26.0.4, Sway 1.11.
**Why**: When wgpu presents a frame on Wayland+Vulkan, it calls
`vkQueuePresentKHR,` which - as required by the Vulkan spec -
synchronously issues `wl_surface.attach`, `wl_surface.damage`, and
`wl_surface.commit` to the compositor before returning. The commit also
picks up any pending frame callbacks.
Zed's `completed_frame()` independently calls `state.surface.commit()`.
This is a redundant second commit on the same surface. Under load, the
Wayland socket dispatch can be delayed enough that both commits are in
flight in close succession, and the ordering becomes timing-dependent.
When Zed's commit reaches the compositor before Mesa's attach+commit
sequence has been fully flushed, the compositor sees a commit with no
buffer attached, fires `wl_callback::Done` immediately, and Zed starts
the next frame too early - Mesa's real buffer arrives late, causing the
visible flicker.
Under no load, Mesa's synchronous commit consistently reaches the
compositor first, so the bug doesn't appear.
**Fix**: Track whether `renderer.draw()` actually called
`frame.present()`. When it did, Mesa owns the `wl_surface.commit()` for
that frame - skip Zed's commit in `completed_frame()`. Only commit
ourselves when wgpu didn't present (surface not configured, lost,
occluded, etc.) - in those cases Mesa won't commit, and we need to keep
the frame callback alive.
---------
Co-authored-by: Benjamin Laib <b5l@users.noreply.github.com>
Co-authored-by: John Tur <john-tur@outlook.com>
Updates #54349
There were two problems:
* The crash never happened, instead we'd always retry.
* When re-trying it seemed like we were picking llvmpipe. Claude's
suggestion was that immediately after wake, the real GPU isn't yet
awake, and so we pick llvm. Avoid this by disallowing llvmpipe on retry
Release Notes:
- linux: Reduced crash rate when recovering GPUs
Instead of manually handing hiding the cursor on keyboard input at the
editor level, GPUI will now take care of it.
This makes it significantly easier to handle the edge cases, and allows
delegating the cursor restoration to the platform itself in the macOS
case. On Linux and Windows, we still have to restore the cursor on
movement ourselves, but this now happens at the platform-specific level.
Bugs fixed by this change:
- No cursor when "Unsaved edits" prompt appears
- Cursor disappears when clicking a panel button if it contains a search
bar (e.g. collab panel)
### Setting rename
The `hide_mouse` setting value `"on_typing_and_movement"` has been
renamed to `"on_typing_and_action"` to better reflect what it actually
does — it hides the cursor when a keystroke resolves to an action (e.g.
cursor movement, deletion). Existing settings are migrated
automatically.
### Tested platforms
- [x] macOS
- [x] Wayland
- [x] X11
- [x] Windows
- [x] Web
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:
- Renamed the `hide_mouse` setting value `on_typing_and_movement` to
`on_typing_and_action` to better describe its behavior (existing
settings are auto-migrated)
- Fixed a few situations where the mouse cursor would be incorrectly
hidden
No, sadly, the title is not a typo. See
https://www.githubstatus.com/incidents/zsg1lk7w13cf for the context.
I'll read with joy and popcorn through that root cause analysis.
It makes literally zero sense what happened here, but for some completly
bonkers reason GitHub completely messed up the merge queue with
https://github.com/zed-industries/zed/pull/54632.
I have no idea how it happened. It makes literally zero sense. A PR
going into the merge queue should have the same LoC when getting out of
it. GitHub obviously does not check this. GitHub causes extra work with
a feature that is supposed to save time.
Thanks, I guess.
Release Notes:
- N/A
---------
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This PR brings back the button to filter remote branches when accessing
the title bar's branch picker with the mouse. It was unintentionally
removed when we introduced the new worktree picker.
Release Notes:
- N/A
This PR adds support for rendering **Netpbm** image formats (`.pbm`,
`.ppm`, `.pgm`) within Zed's built-in image viewer.
These formats are particularly useful for projects that want minimal
external dependencies, a common scenario in academic environments and
low-level graphics programming.
Since the underlying `image` crate and `GPUI` already provide support
for these codecs, this change explicitly exposes the `Pnm` variant
within `gpui::ImageFormat` by mapping it to `image::ImageFormat::Pnm`.
## Screenshots/Examples
Below is an example of `.pbm`, `.ppm`, and `.pgm` files being rendered
correctly in the image preview (images taken from
https://filesamples.com):
<img width="1917" height="1012" alt="pnm_example"
src="https://github.com/user-attachments/assets/0056133f-908c-4c91-ba9d-53aef0657b05"
/>
Release Notes:
- Added support for PNM image previews (`.pbm`, `.ppm`, `.pgm`).
Closes#49329
This area has regressed a few times across #34514, #35361, and #44234.
The problem is that we were still mixing XKB client-side and server-side
state handling on the same `xkb::State`.
On X11, `XkbStateNotify` already keeps the client state synchronized.
But the key event path was still doing two extra things on the shared
`state.xkb`:
- calling `update_key()`
- rewriting the same state from `KeyPress` / `KeyRelease.state`
The libxkbcommon X11 docs say that `XkbStateNotify` is the more accurate
source of truth for X11 clients and that there is no need to call
`xkb_state_update_key()` once the client state is synchronized. This
follows the libxkbcommon X11 client model more closely by keeping
persistent state notify-driven and using event-local state only for
lookup.
This PR fixes that by:
- removing the remaining `update_key()` calls from X11 `KeyPress` /
`KeyRelease`
- keeping the long-lived `state.xkb` driven only by `XkbStateNotify` and
keymap notifications
- creating a temporary event-local `xkb::State` for per-event lookup
I also added regression tests around the historical bugs in this area:
- #14282: Caps Lock / Neo 2 regressions from the earlier X11 fixes
- #31193: German key resolution
- #26468: `space` with Cyrillic and Czech layouts / non-locked layout
groups
- #40678: macro-style shifted input like `Shift+]`
Release Notes:
- Fixed issue on Linux X11 where you coundn't input space key in some
cases.
## Summary
- Fix crash on startup when the X11 server supports XInput < 2.4 (e.g.
XInput 2.3)
- Gesture event mask bits (pinch begin/update/end) are now only
requested when the server advertises XInput >= 2.4
- Zed previously failed to open any window on affected systems, printing
`Zed failed to open a window: X11 XiSelectEvents failed`
## 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
## Problem
On X11 systems where the XInput extension version is older than 2.4, Zed
crashes immediately on startup with:
```
Zed failed to open a window: X11 XiSelectEvents failed.
Caused by:
X11 error X11Error { error_kind: Value, error_code: 2, sequence: 277,
bad_value: 27, minor_opcode: 46, major_opcode: 131,
extension_name: Some("XInputExtension"),
request_name: Some("XISelectEvents") }
```
This makes Zed completely unusable on any X11 display server that only
supports XInput 2.3 or earlier, which includes many current Ubuntu
20.04/22.04 systems, remote X11 sessions, and VNC/Xvfb setups.
### Root cause
During window creation, `X11WindowState::new` calls `XISelectEvents`
with an event mask that unconditionally includes gesture event bits
(`GESTURE_PINCH_BEGIN`, `GESTURE_PINCH_UPDATE`, `GESTURE_PINCH_END`).
These gesture events were introduced in **XInput 2.4**.
When the X server only supports XInput 2.3 (or older), it does not
recognize these mask bits and rejects the entire `XISelectEvents`
request with a `BadValue` error. This is fatal because the error is
propagated up and prevents the window from being created.
A comment in the original code stated:
> If the server only supports an older version, gesture events simply
won't be delivered.
This is incorrect. The X11 protocol does **not** silently ignore unknown
mask bits in `XISelectEvents` — it rejects the whole request.
### How XInput version negotiation works
The client calls `XIQueryVersion(2, 4)` to announce the highest version
it supports. The server responds with the highest version **it**
supports (e.g. `2.3`). The client is then responsible for not using
features beyond the negotiated version. The existing code ignored the
server's response and used 2.4 features unconditionally.
## Fix
### Approach
Check the XInput version returned by the server. Only include gesture
event mask bits in `XISelectEvents` when the negotiated version is >=
2.4. On older servers, basic input events (motion, button press/release,
enter, leave) still work normally — only touchpad pinch gestures are
unavailable.
### Changed files
**`crates/gpui_linux/src/linux/x11/client.rs`**
1. Added `supports_xinput_gestures: bool` field to `X11ClientState`.
2. After the existing `xinput_xi_query_version(2, 4)` call, compute
whether the server version is >= 2.4:
```rust
let supports_xinput_gestures = xinput_version.major_version > 2
|| (xinput_version.major_version == 2 && xinput_version.minor_version >=
4);
```
3. Added an `info!` log line reporting the detected XInput version and
gesture support status.
4. Pass `supports_xinput_gestures` through `open_window` into
`X11Window::new`.
**`crates/gpui_linux/src/linux/x11/window.rs`**
1. Added `supports_xinput_gestures: bool` parameter to both
`X11Window::new` and `X11WindowState::new`.
2. The `XISelectEvents` call now builds the event mask conditionally:
- Always includes: `MOTION`, `BUTTON_PRESS`, `BUTTON_RELEASE`, `ENTER`,
`LEAVE`
- Only when `supports_xinput_gestures` is true: `GESTURE_PINCH_BEGIN`,
`GESTURE_PINCH_UPDATE`, `GESTURE_PINCH_END`
### What is NOT changed
- The gesture event **handlers** in `client.rs`
(`XinputGesturePinchBegin`, `XinputGesturePinchUpdate`,
`XinputGesturePinchEnd`) are left as-is. They simply won't be triggered
on servers without gesture support, since the events are never
registered.
- No behavioral change on systems with XInput >= 2.4 — gesture events
continue to work exactly as before.
## Testing
| Test | Before fix | After fix |
|------|-----------|-----------|
| `./target/release/zed .` on XInput 2.3 | Immediate crash (exit code 1)
| Window opens successfully (runs until killed) |
| XInput version detection | Version queried but response ignored |
Version checked and logged |
Verified on an X11 system with XInput 2.3 (X.Org 1.20.13, Ubuntu 20.04).
## Test plan
- [x] Build succeeds (`cargo build --release`)
- [x] Zed launches and opens a window on XInput 2.3 system
- [x] No regression on the basic input event path (motion, clicks,
enter/leave still registered)
- [ ] Verify gesture pinch events still work on a system with XInput >=
2.4
Release Notes:
- Fixed Zed failing to start on X11 systems with XInput version older
than 2.4, which includes many Linux distributions and remote desktop
setups.