Replace all X11 mouse events with XI2 equivalents (#11235)

Owen Law and Mikayla Maki created

This PR replaces all pointer events on X11 with their XI2 equivalents,
which fixes problems with scroll events not being reported when a mouse
button is down. Additionally it closes #11206 by resetting the tracked
global scroll valulator position with `None` on a leave event to prevent
a large scroll delta if scrolling is done outside the window. Lastly, it
resolves the bad window issue kvark was having.

Release Notes:

- Fixed X11 Scroll snapping (#11206 ).

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/gpui/src/platform/linux/x11/client.rs | 55 ++++++++-------------
crates/gpui/src/platform/linux/x11/event.rs  |  8 +-
crates/gpui/src/platform/linux/x11/window.rs | 42 +++++++---------
3 files changed, 45 insertions(+), 60 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -35,7 +35,7 @@ use super::{
     super::{open_uri_internal, SCROLL_LINES},
     X11Display, X11WindowStatePtr, XcbAtoms,
 };
-use super::{button_of_key, modifiers_from_state};
+use super::{button_from_mask, button_of_key, modifiers_from_state};
 use crate::platform::linux::is_within_click_distance;
 use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
 
@@ -390,16 +390,16 @@ impl X11Client {
                 drop(state);
                 window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
             }
-            Event::ButtonPress(event) => {
+            Event::XinputButtonPress(event) => {
                 let window = self.get_window(event.event)?;
                 let mut state = self.0.borrow_mut();
 
-                let modifiers = modifiers_from_state(event.state);
+                let modifiers = modifiers_from_xinput_info(event.mods);
                 let position = point(
-                    px(event.event_x as f32 / state.scale_factor),
-                    px(event.event_y as f32 / state.scale_factor),
+                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
+                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
-                if let Some(button) = button_of_key(event.detail) {
+                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
                     let click_elapsed = state.last_click.elapsed();
 
                     if click_elapsed < DOUBLE_CLICK_INTERVAL
@@ -426,15 +426,15 @@ impl X11Client {
                     log::warn!("Unknown button press: {event:?}");
                 }
             }
-            Event::ButtonRelease(event) => {
+            Event::XinputButtonRelease(event) => {
                 let window = self.get_window(event.event)?;
                 let state = self.0.borrow();
-                let modifiers = modifiers_from_state(event.state);
+                let modifiers = modifiers_from_xinput_info(event.mods);
                 let position = point(
-                    px(event.event_x as f32 / state.scale_factor),
-                    px(event.event_y as f32 / state.scale_factor),
+                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
+                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
-                if let Some(button) = button_of_key(event.detail) {
+                if let Some(button) = button_of_key(event.detail.try_into().unwrap()) {
                     let click_count = state.current_count;
                     drop(state);
                     window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
@@ -448,6 +448,7 @@ impl X11Client {
             Event::XinputMotion(event) => {
                 let window = self.get_window(event.event)?;
                 let state = self.0.borrow();
+                let pressed_button = button_from_mask(event.button_mask[0]);
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
                     px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
@@ -464,7 +465,7 @@ impl X11Client {
                 if event.valuator_mask[0] & 3 != 0 {
                     window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
                         position,
-                        pressed_button: None,
+                        pressed_button,
                         modifiers,
                     }));
                 }
@@ -524,32 +525,20 @@ impl X11Client {
                     valuator_idx += 1;
                 }
             }
-            Event::MotionNotify(event) => {
-                let window = self.get_window(event.event)?;
-                let state = self.0.borrow();
-                let pressed_button = super::button_from_state(event.state);
-                let position = point(
-                    px(event.event_x as f32 / state.scale_factor),
-                    px(event.event_y as f32 / state.scale_factor),
-                );
-                let modifiers = modifiers_from_state(event.state);
-                drop(state);
-                window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
-                    pressed_button,
-                    position,
-                    modifiers,
-                }));
-            }
-            Event::LeaveNotify(event) => {
+            Event::XinputLeave(event) => {
+                self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
+                self.0.borrow_mut().scroll_y = None;
+
                 let window = self.get_window(event.event)?;
                 let state = self.0.borrow();
-                let pressed_button = super::button_from_state(event.state);
+                let pressed_button = button_from_mask(event.buttons[0]);
                 let position = point(
-                    px(event.event_x as f32 / state.scale_factor),
-                    px(event.event_y as f32 / state.scale_factor),
+                    px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
+                    px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
                 );
-                let modifiers = modifiers_from_state(event.state);
+                let modifiers = modifiers_from_xinput_info(event.mods);
                 drop(state);
+
                 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
                     pressed_button,
                     position,

crates/gpui/src/platform/linux/x11/event.rs 🔗

@@ -37,12 +37,12 @@ pub(crate) fn modifiers_from_xinput_info(modifier_info: xinput::ModifierInfo) ->
     }
 }
 
-pub(crate) fn button_from_state(state: xproto::KeyButMask) -> Option<MouseButton> {
-    Some(if state.contains(xproto::KeyButMask::BUTTON1) {
+pub(crate) fn button_from_mask(button_mask: u32) -> Option<MouseButton> {
+    Some(if button_mask & 2 == 2 {
         MouseButton::Left
-    } else if state.contains(xproto::KeyButMask::BUTTON2) {
+    } else if button_mask & 4 == 4 {
         MouseButton::Middle
-    } else if state.contains(xproto::KeyButMask::BUTTON3) {
+    } else if button_mask & 8 == 8 {
         MouseButton::Right
     } else {
         return None;

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -16,11 +16,11 @@ use x11rb::{
     connection::{Connection as _, RequestConnection as _},
     protocol::{
         render::{self, ConnectionExt as _},
-        xinput,
-        xproto::{self, ConnectionExt as _},
+        xinput::{self, ConnectionExt as _},
+        xproto::{self, ConnectionExt as _, CreateWindowAux},
     },
     resource_manager::Database,
-    wrapper::ConnectionExt,
+    wrapper::ConnectionExt as _,
     xcb_ffi::XCBConnection,
 };
 
@@ -262,14 +262,7 @@ impl X11WindowState {
                     | xproto::EventMask::LEAVE_WINDOW
                     | xproto::EventMask::FOCUS_CHANGE
                     | xproto::EventMask::KEY_PRESS
-                    | xproto::EventMask::KEY_RELEASE
-                    | xproto::EventMask::BUTTON_PRESS
-                    | xproto::EventMask::BUTTON_RELEASE
-                    | xproto::EventMask::POINTER_MOTION
-                    | xproto::EventMask::BUTTON1_MOTION
-                    | xproto::EventMask::BUTTON2_MOTION
-                    | xproto::EventMask::BUTTON3_MOTION
-                    | xproto::EventMask::BUTTON_MOTION,
+                    | xproto::EventMask::KEY_RELEASE,
             );
 
         xcb_connection
@@ -290,18 +283,6 @@ impl X11WindowState {
             .check()
             .unwrap();
 
-        xinput::ConnectionExt::xinput_xi_select_events(
-            &xcb_connection,
-            x_window,
-            &[xinput::EventMask {
-                deviceid: 1,
-                mask: vec![xinput::XIEventMask::MOTION],
-            }],
-        )
-        .unwrap()
-        .check()
-        .unwrap();
-
         if let Some(titlebar) = params.titlebar {
             if let Some(title) = titlebar.title {
                 xcb_connection
@@ -326,6 +307,21 @@ impl X11WindowState {
             )
             .unwrap();
 
+        xcb_connection
+            .xinput_xi_select_events(
+                x_window,
+                &[xinput::EventMask {
+                    deviceid: 1,
+                    mask: vec![
+                        xinput::XIEventMask::MOTION
+                            | xinput::XIEventMask::BUTTON_PRESS
+                            | xinput::XIEventMask::BUTTON_RELEASE
+                            | xinput::XIEventMask::LEAVE,
+                    ],
+                }],
+            )
+            .unwrap();
+
         xcb_connection.map_window(x_window).unwrap();
         xcb_connection.flush().unwrap();