diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index eb259dd41c..71c877c458 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -114,7 +114,7 @@ wayland-protocols = { version = "0.31.2", features = [ oo7 = "0.3.0" open = "5.1.2" filedescriptor = "0.8.2" -x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr"] } +x11rb = { version = "0.13.0", features = ["allow-unsafe-code", "xkb", "randr", "xinput"] } xkbcommon = { version = "0.7", features = ["wayland", "x11"] } [target.'cfg(windows)'.dependencies] diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index d4a0044f79..7b440b5f60 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -12,9 +12,10 @@ use util::ResultExt; use x11rb::connection::{Connection, RequestConnection}; use x11rb::errors::ConnectionError; use x11rb::protocol::randr::ConnectionExt as _; +use x11rb::protocol::xinput::{ConnectionExt, ScrollClass}; use x11rb::protocol::xkb::ConnectionExt as _; use x11rb::protocol::xproto::ConnectionExt as _; -use x11rb::protocol::{randr, xkb, xproto, Event}; +use x11rb::protocol::{randr, xinput, xkb, xproto, Event}; use x11rb::xcb_ffi::XCBConnection; use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION}; use xkbcommon::xkb as xkbc; @@ -22,8 +23,9 @@ use xkbcommon::xkb as xkbc; use crate::platform::linux::LinuxClient; use crate::platform::{LinuxCommon, PlatformWindow}; use crate::{ - px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Modifiers, ModifiersChangedEvent, Pixels, - PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window, + modifiers_from_xinput_info, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId, Modifiers, + ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, + TouchPhase, WindowParams, X11Window, }; use super::{super::SCROLL_LINES, X11Display, X11WindowStatePtr, XcbAtoms}; @@ -63,6 +65,10 @@ pub struct X11ClientState { pub(crate) focused_window: Option, pub(crate) xkb: xkbc::State, + pub(crate) scroll_class_data: Vec, + pub(crate) scroll_x: Option, + pub(crate) scroll_y: Option, + pub(crate) common: LinuxCommon, pub(crate) clipboard: X11ClipboardContext, pub(crate) primary: X11ClipboardContext, @@ -110,6 +116,35 @@ impl X11Client { xcb_connection .prefetch_extension_information(randr::X11_EXTENSION_NAME) .unwrap(); + xcb_connection + .prefetch_extension_information(xinput::X11_EXTENSION_NAME) + .unwrap(); + + let xinput_version = xcb_connection + .xinput_xi_query_version(2, 0) + .unwrap() + .reply() + .unwrap(); + assert!( + xinput_version.major_version >= 2, + "XInput Extension v2 not supported." + ); + + let master_device_query = xcb_connection + .xinput_xi_query_device(1_u16) + .unwrap() + .reply() + .unwrap(); + let scroll_class_data = master_device_query + .infos + .iter() + .find(|info| info.type_ == xinput::DeviceType::MASTER_POINTER) + .unwrap() + .classes + .iter() + .filter_map(|class| class.data.as_scroll()) + .map(|class| *class) + .collect::>(); let atoms = XcbAtoms::new(&xcb_connection).unwrap(); let xkb = xcb_connection @@ -184,6 +219,11 @@ impl X11Client { windows: HashMap::default(), focused_window: None, xkb: xkb_state, + + scroll_class_data, + scroll_x: None, + scroll_y: None, + clipboard, primary, }))) @@ -330,18 +370,6 @@ impl X11Client { click_count: current_count, first_mouse: false, })); - } else if event.detail >= 4 && event.detail <= 5 { - // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11 - let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 }; - let scroll_y = SCROLL_LINES * scroll_direction; - - drop(state); - window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent { - position, - delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)), - modifiers, - touch_phase: TouchPhase::Moved, - })); } else { log::warn!("Unknown button press: {event:?}"); } @@ -363,6 +391,84 @@ impl X11Client { })); } } + Event::XinputMotion(event) => { + let window = self.get_window(event.event)?; + + let position = Point::new( + (event.event_x as f32 / u16::MAX as f32).into(), + (event.event_y as f32 / u16::MAX as f32).into(), + ); + let modifiers = modifiers_from_xinput_info(event.mods); + + let axisvalues = event + .axisvalues + .iter() + .map(|axisvalue| fp3232_to_f32(*axisvalue)) + .collect::>(); + + if event.valuator_mask[0] & 3 != 0 { + window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent { + position, + pressed_button: None, + modifiers, + })); + } + + let mut valuator_idx = 0; + let scroll_class_data = self.0.borrow().scroll_class_data.clone(); + for shift in 0..32 { + if (event.valuator_mask[0] >> shift) & 1 == 0 { + continue; + } + + for scroll_class in &scroll_class_data { + if scroll_class.scroll_type == xinput::ScrollType::HORIZONTAL + && scroll_class.number == shift + { + let new_scroll = axisvalues[valuator_idx] + / fp3232_to_f32(scroll_class.increment) + * SCROLL_LINES as f32; + let old_scroll = self.0.borrow().scroll_x; + self.0.borrow_mut().scroll_x = Some(new_scroll); + + if let Some(old_scroll) = old_scroll { + let delta_scroll = old_scroll - new_scroll; + window.handle_input(PlatformInput::ScrollWheel( + crate::ScrollWheelEvent { + position, + delta: ScrollDelta::Lines(Point::new(delta_scroll, 0.0)), + modifiers, + touch_phase: TouchPhase::default(), + }, + )); + } + } else if scroll_class.scroll_type == xinput::ScrollType::VERTICAL + && scroll_class.number == shift + { + // the `increment` is the valuator delta equivalent to one positive unit of scrolling. Here that means SCROLL_LINES lines. + let new_scroll = axisvalues[valuator_idx] + / fp3232_to_f32(scroll_class.increment) + * SCROLL_LINES as f32; + let old_scroll = self.0.borrow().scroll_y; + self.0.borrow_mut().scroll_y = Some(new_scroll); + + if let Some(old_scroll) = old_scroll { + let delta_scroll = old_scroll - new_scroll; + window.handle_input(PlatformInput::ScrollWheel( + crate::ScrollWheelEvent { + position, + delta: ScrollDelta::Lines(Point::new(0.0, delta_scroll)), + modifiers, + touch_phase: TouchPhase::default(), + }, + )); + } + } + } + + valuator_idx += 1; + } + } Event::MotionNotify(event) => { let window = self.get_window(event.event)?; let pressed_button = super::button_from_state(event.state); @@ -573,3 +679,7 @@ pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration { log::info!("Refreshing at {} micros", micros); Duration::from_micros(micros) } + +fn fp3232_to_f32(value: xinput::Fp3232) -> f32 { + value.integral as f32 + value.frac as f32 / u32::MAX as f32 +} diff --git a/crates/gpui/src/platform/linux/x11/event.rs b/crates/gpui/src/platform/linux/x11/event.rs index 3c173b3393..29427db022 100644 --- a/crates/gpui/src/platform/linux/x11/event.rs +++ b/crates/gpui/src/platform/linux/x11/event.rs @@ -1,4 +1,7 @@ -use x11rb::protocol::xproto; +use x11rb::protocol::{ + xinput, + xproto::{self, ModMask}, +}; use crate::{Modifiers, MouseButton, NavigationDirection}; @@ -23,6 +26,17 @@ pub(crate) fn modifiers_from_state(state: xproto::KeyButMask) -> Modifiers { } } +pub(crate) fn modifiers_from_xinput_info(modifier_info: xinput::ModifierInfo) -> Modifiers { + Modifiers { + control: modifier_info.effective as u16 & ModMask::CONTROL.bits() + == ModMask::CONTROL.bits(), + alt: modifier_info.effective as u16 & ModMask::M1.bits() == ModMask::M1.bits(), + shift: modifier_info.effective as u16 & ModMask::SHIFT.bits() == ModMask::SHIFT.bits(), + platform: modifier_info.effective as u16 & ModMask::M4.bits() == ModMask::M4.bits(), + function: false, + } +} + pub(crate) fn button_from_state(state: xproto::KeyButMask) -> Option { Some(if state.contains(xproto::KeyButMask::BUTTON1) { MouseButton::Left diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index da0f6d2a58..88b0d0a70a 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -13,7 +13,10 @@ use raw_window_handle as rwh; use util::ResultExt; use x11rb::{ connection::Connection, - protocol::xproto::{self, ConnectionExt as _, CreateWindowAux}, + protocol::{ + xinput, + xproto::{self, ConnectionExt as _, CreateWindowAux}, + }, wrapper::ConnectionExt, xcb_ffi::XCBConnection, }; @@ -153,8 +156,6 @@ impl X11WindowState { | xproto::EventMask::BUTTON1_MOTION | xproto::EventMask::BUTTON2_MOTION | xproto::EventMask::BUTTON3_MOTION - | xproto::EventMask::BUTTON4_MOTION - | xproto::EventMask::BUTTON5_MOTION | xproto::EventMask::BUTTON_MOTION, ); @@ -174,6 +175,18 @@ impl X11WindowState { ) .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