Fix crash on startup when the X11 server supports XInput < 2.4 (#53582)

## 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.
This commit is contained in:
CanWang authored and GitHub committed 2026-04-10 08:05:39 +00:00
1 parent 967c1472d2
commit 909ac66c0a
2 files changed
+34 -14

No files matched your search

+16 -1
View File
@@ -214,6 +214,8 @@ pub struct X11ClientState {
pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,
pub(crate) supports_xinput_gestures: bool,
pub(crate) common: LinuxCommon,
pub(crate) clipboard: Clipboard,
pub(crate) clipboard_item: Option<ClipboardItem>,
@@ -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",
+18 -13
View File
@@ -423,6 +423,7 @@ impl X11WindowState {
scale_factor: f32,
appearance: WindowAppearance,
parent_window: Option<X11WindowStatePtr>,
supports_xinput_gestures: bool,
) -> anyhow::Result<Self> {
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<X11WindowStatePtr>,
supports_xinput_gestures: bool,
) -> anyhow::Result<Self> {
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(),