From 23830d59469e906153c5ce9edbb89ff30bd87629 Mon Sep 17 00:00:00 2001 From: CanWang Date: Fri, 10 Apr 2026 16:05:39 +0800 Subject: [PATCH] Fix crash on startup when the X11 server supports XInput < 2.4 (#53582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. --- crates/gpui_linux/src/linux/x11/client.rs | 17 ++++++++++++- crates/gpui_linux/src/linux/x11/window.rs | 31 +++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 57871e6ef32b937a7a47662f8022293a57bc3fe2..bc7c9594734d4fae9a8dda4056bc02d515fbab48 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -214,6 +214,8 @@ pub struct X11ClientState { pointer_device_states: BTreeMap, + pub(crate) supports_xinput_gestures: bool, + pub(crate) common: LinuxCommon, pub(crate) clipboard: Clipboard, pub(crate) clipboard_item: Option, @@ -345,7 +347,8 @@ impl X11Client { // Announce to X server that XInput up to 2.4 is supported. // Version 2.4 is needed for gesture events (GesturePinchBegin/Update/End). - // If the server only supports an older version, gesture events simply won't be delivered. + // The server responds with the highest version it supports; if < 2.4, + // we must not request gesture event masks in XISelectEvents. let xinput_version = get_reply( || "XInput XiQueryVersion failed", xcb_connection.xinput_xi_query_version(2, 4), @@ -354,6 +357,14 @@ impl X11Client { xinput_version.major_version >= 2, "XInput version >= 2 required." ); + let supports_xinput_gestures = xinput_version.major_version > 2 + || (xinput_version.major_version == 2 && xinput_version.minor_version >= 4); + log::info!( + "XInput version: {}.{}, gesture support: {}", + xinput_version.major_version, + xinput_version.minor_version, + supports_xinput_gestures, + ); let pointer_device_states = current_pointer_device_states(&xcb_connection, &BTreeMap::new()).unwrap_or_default(); @@ -535,6 +546,8 @@ impl X11Client { pointer_device_states, + supports_xinput_gestures, + clipboard, clipboard_item: None, xdnd_state: Xdnd::default(), @@ -1593,6 +1606,7 @@ impl LinuxClient for X11Client { let scale_factor = state.scale_factor; let appearance = state.common.appearance; let compositor_gpu = state.compositor_gpu.take(); + let supports_xinput_gestures = state.supports_xinput_gestures; let window = X11Window::new( handle, X11ClientStatePtr(Rc::downgrade(&self.0)), @@ -1608,6 +1622,7 @@ impl LinuxClient for X11Client { scale_factor, appearance, parent_window, + supports_xinput_gestures, )?; check_reply( || "Failed to set XdndAware property", diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 1974cc0bb28f62da4d7dcb3e9fca92b6324470bb..f29ba49fb2498dd49a5f025aad4dc2584a8a8a42 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -423,6 +423,7 @@ impl X11WindowState { scale_factor: f32, appearance: WindowAppearance, parent_window: Option, + supports_xinput_gestures: bool, ) -> anyhow::Result { let x_screen_index = params .display_id @@ -660,25 +661,27 @@ impl X11WindowState { ), )?; + let mut xi_event_mask = xinput::XIEventMask::MOTION + | xinput::XIEventMask::BUTTON_PRESS + | xinput::XIEventMask::BUTTON_RELEASE + | xinput::XIEventMask::ENTER + | xinput::XIEventMask::LEAVE; + if supports_xinput_gestures { + // x11rb 0.13 doesn't define XIEventMask constants for gesture + // events, so we construct them from the event opcodes (each + // XInput event type N maps to mask bit N). + xi_event_mask |= + xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_BEGIN_EVENT) + | xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_UPDATE_EVENT) + | xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_END_EVENT); + } check_reply( || "X11 XiSelectEvents failed.", xcb.xinput_xi_select_events( x_window, &[xinput::EventMask { deviceid: XINPUT_ALL_DEVICE_GROUPS, - mask: vec![ - xinput::XIEventMask::MOTION - | xinput::XIEventMask::BUTTON_PRESS - | xinput::XIEventMask::BUTTON_RELEASE - | xinput::XIEventMask::ENTER - | xinput::XIEventMask::LEAVE - // x11rb 0.13 doesn't define XIEventMask constants for gesture - // events, so we construct them from the event opcodes (each - // XInput event type N maps to mask bit N). - | xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_BEGIN_EVENT) - | xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_UPDATE_EVENT) - | xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_END_EVENT), - ], + mask: vec![xi_event_mask], }], ), )?; @@ -855,6 +858,7 @@ impl X11Window { scale_factor: f32, appearance: WindowAppearance, parent_window: Option, + supports_xinput_gestures: bool, ) -> anyhow::Result { let ptr = X11WindowStatePtr { state: Rc::new(RefCell::new(X11WindowState::new( @@ -872,6 +876,7 @@ impl X11Window { scale_factor, appearance, parent_window, + supports_xinput_gestures, )?)), callbacks: Rc::new(RefCell::new(Callbacks::default())), xcb: xcb.clone(),