From 527c9097f848feef78cfb6cfd36d32eb048e6bb5 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Tue, 1 Oct 2024 01:14:40 -0600 Subject: [PATCH] linux: Various X11 scroll improvements (#18484) Closes #14089, #14416, #15970, #17230, #18485 Release Notes: - Fixed some cases where Linux X11 mouse scrolling doesn't work at all (#14089, ##15970, #17230) - Fixed handling of switching between Linux X11 devices used for scrolling (#14416, #18485) Change details: Also includes the commit from PR #18317 so I don't have to deal with merge conflicts. * Now uses valuator info from slave pointers rather than master. This hopefully fixes remaining cases where scrolling is fully broken. https://github.com/zed-industries/zed/issues/14089, https://github.com/zed-industries/zed/issues/15970, https://github.com/zed-industries/zed/issues/17230 * Per-device recording of "last scroll position" used to calculate deltas. This meant that swithing scroll devices would cause a sudden jump of scroll position, often to the beginning or end of the file (https://github.com/zed-industries/zed/issues/14416). * Re-queries device metadata when devices change, so that newly plugged in devices will work, and re-use of device-ids don't use old metadata with a new device. * xinput 2 documentation describes support for multiple master devices. I believe this implementation will support that, since now it just uses `DeviceInfo` from slave devices. The concept of master devices is only used in registering for events. * Uses popcount+bit masking to resolve axis indexes, instead of iterating bit indices. --------- Co-authored-by: Thorsten Ball --- crates/gpui/src/platform/linux/platform.rs | 2 +- .../gpui/src/platform/linux/wayland/client.rs | 8 +- crates/gpui/src/platform/linux/x11/client.rs | 406 ++++++++++++------ crates/gpui/src/platform/linux/x11/event.rs | 116 ++++- crates/gpui/src/platform/linux/x11/window.rs | 19 +- 5 files changed, 408 insertions(+), 143 deletions(-) diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 67f1a43cbe322..6e09badb493a6 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -45,7 +45,7 @@ use crate::{ use super::x11::X11Client; -pub(crate) const SCROLL_LINES: f64 = 3.0; +pub(crate) const SCROLL_LINES: f32 = 3.0; // Values match the defaults on GTK. // Taken from https://github.com/GNOME/gtk/blob/main/gtk/gtksettings.c#L320 diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 4b7816a73ac36..f0015a7e5820b 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -1634,10 +1634,10 @@ impl Dispatch for WaylandClientStatePtr { let scroll_delta = state.discrete_scroll_delta.get_or_insert(point(0.0, 0.0)); match axis { wl_pointer::Axis::VerticalScroll => { - scroll_delta.y += discrete as f32 * axis_modifier * SCROLL_LINES as f32; + scroll_delta.y += discrete as f32 * axis_modifier * SCROLL_LINES; } wl_pointer::Axis::HorizontalScroll => { - scroll_delta.x += discrete as f32 * axis_modifier * SCROLL_LINES as f32; + scroll_delta.x += discrete as f32 * axis_modifier * SCROLL_LINES; } _ => unreachable!(), } @@ -1662,10 +1662,10 @@ impl Dispatch for WaylandClientStatePtr { let wheel_percent = value120 as f32 / 120.0; match axis { wl_pointer::Axis::VerticalScroll => { - scroll_delta.y += wheel_percent * axis_modifier * SCROLL_LINES as f32; + scroll_delta.y += wheel_percent * axis_modifier * SCROLL_LINES; } wl_pointer::Axis::HorizontalScroll => { - scroll_delta.x += wheel_percent * axis_modifier * SCROLL_LINES as f32; + scroll_delta.x += wheel_percent * axis_modifier * SCROLL_LINES; } _ => unreachable!(), } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 5339cc95fd1b1..459f2045bb732 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -1,6 +1,6 @@ use core::str; use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::ops::Deref; use std::path::PathBuf; use std::rc::{Rc, Weak}; @@ -42,7 +42,10 @@ use crate::{ WindowParams, X11Window, }; -use super::{button_of_key, modifiers_from_state, pressed_button_from_mask}; +use super::{ + button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state, + pressed_button_from_mask, ButtonOrScroll, ScrollDirection, +}; use super::{X11Display, X11WindowStatePtr, XcbAtoms}; use super::{XimCallbackEvent, XimHandler}; use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES}; @@ -51,7 +54,15 @@ use crate::platform::linux::{ get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal, }; -pub(super) const XINPUT_MASTER_DEVICE: u16 = 1; +/// Value for DeviceId parameters which selects all devices. +pub(crate) const XINPUT_ALL_DEVICES: xinput::DeviceId = 0; + +/// Value for DeviceId parameters which selects all device groups. Events that +/// occur within the group are emitted by the group itself. +/// +/// In XInput 2's interface, these are referred to as "master devices", but that +/// terminology is both archaic and unclear. +pub(crate) const XINPUT_ALL_DEVICE_GROUPS: xinput::DeviceId = 1; pub(crate) struct WindowRef { window: X11WindowStatePtr, @@ -117,6 +128,26 @@ pub struct Xdnd { position: Point, } +#[derive(Debug)] +struct PointerDeviceState { + horizontal: ScrollAxisState, + vertical: ScrollAxisState, +} + +#[derive(Debug, Default)] +struct ScrollAxisState { + /// Valuator number for looking up this axis's scroll value. + valuator_number: Option, + /// Conversion factor from scroll units to lines. + multiplier: f32, + /// Last scroll value for calculating scroll delta. + /// + /// This gets set to `None` whenever it might be invalid - when devices change or when window focus changes. + /// The logic errs on the side of invalidating this, since the consequence is just skipping the delta of one scroll event. + /// The consequence of not invalidating it can be large invalid deltas, which are much more user visible. + scroll_value: Option, +} + pub struct X11ClientState { pub(crate) loop_handle: LoopHandle<'static, X11Client>, pub(crate) event_loop: Option>, @@ -152,9 +183,7 @@ pub struct X11ClientState { pub(crate) cursor_styles: HashMap, pub(crate) cursor_cache: HashMap, - pub(crate) scroll_class_data: Vec, - pub(crate) scroll_x: Option, - pub(crate) scroll_y: Option, + pointer_device_states: BTreeMap, pub(crate) common: LinuxCommon, pub(crate) clipboard: x11_clipboard::Clipboard, @@ -266,31 +295,21 @@ impl X11Client { .prefetch_extension_information(xinput::X11_EXTENSION_NAME) .unwrap(); + // Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and + // beyond, support for touch events would need to be added. let xinput_version = xcb_connection - .xinput_xi_query_version(2, 0) + .xinput_xi_query_version(2, 1) .unwrap() .reply() .unwrap(); + // XInput 1.x is not supported. assert!( xinput_version.major_version >= 2, - "XInput Extension v2 not supported." + "XInput version >= 2 required." ); - let master_device_query = xcb_connection - .xinput_xi_query_device(XINPUT_MASTER_DEVICE) - .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 pointer_device_states = + get_new_pointer_device_states(&xcb_connection, &BTreeMap::new()); let atoms = XcbAtoms::new(&xcb_connection).unwrap().reply().unwrap(); @@ -434,9 +453,7 @@ impl X11Client { cursor_styles: HashMap::default(), cursor_cache: HashMap::default(), - scroll_class_data, - scroll_x: None, - scroll_y: None, + pointer_device_states, clipboard, clipboard_item: None, @@ -950,35 +967,56 @@ impl X11Client { window.handle_ime_commit(text); state = self.0.borrow_mut(); } - 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 - && state - .last_mouse_button - .is_some_and(|prev_button| prev_button == button) - && is_within_click_distance(state.last_location, position) - { - state.current_count += 1; - } else { - state.current_count = 1; - } - - state.last_click = Instant::now(); - state.last_mouse_button = Some(button); - state.last_location = position; - let current_count = state.current_count; + match button_or_scroll_from_event_detail(event.detail) { + Some(ButtonOrScroll::Button(button)) => { + let click_elapsed = state.last_click.elapsed(); + if click_elapsed < DOUBLE_CLICK_INTERVAL + && state + .last_mouse_button + .is_some_and(|prev_button| prev_button == button) + && is_within_click_distance(state.last_location, position) + { + state.current_count += 1; + } else { + state.current_count = 1; + } - drop(state); - window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent { - button, - position, - modifiers, - click_count: current_count, - first_mouse: false, - })); - } else { - log::warn!("Unknown button press: {event:?}"); + state.last_click = Instant::now(); + state.last_mouse_button = Some(button); + state.last_location = position; + let current_count = state.current_count; + + drop(state); + window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent { + button, + position, + modifiers, + click_count: current_count, + first_mouse: false, + })); + } + Some(ButtonOrScroll::Scroll(direction)) => { + drop(state); + // Emulated scroll button presses are sent simultaneously with smooth scrolling XinputMotion events. + // Since handling those events does the scrolling, they are skipped here. + if !event + .flags + .contains(xinput::PointerEventFlags::POINTER_EMULATED) + { + let scroll_delta = match direction { + ScrollDirection::Up => Point::new(0.0, SCROLL_LINES), + ScrollDirection::Down => Point::new(0.0, -SCROLL_LINES), + ScrollDirection::Left => Point::new(SCROLL_LINES, 0.0), + ScrollDirection::Right => Point::new(-SCROLL_LINES, 0.0), + }; + window.handle_input(PlatformInput::ScrollWheel( + make_scroll_wheel_event(position, scroll_delta, modifiers), + )); + } + } + None => { + log::error!("Unknown x11 button: {}", event.detail); + } } } Event::XinputButtonRelease(event) => { @@ -991,15 +1029,19 @@ impl X11Client { 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.try_into().unwrap()) { - let click_count = state.current_count; - drop(state); - window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent { - button, - position, - modifiers, - click_count, - })); + match button_or_scroll_from_event_detail(event.detail) { + Some(ButtonOrScroll::Button(button)) => { + let click_count = state.current_count; + drop(state); + window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent { + button, + position, + modifiers, + click_count, + })); + } + Some(ButtonOrScroll::Scroll(_)) => {} + None => {} } } Event::XinputMotion(event) => { @@ -1014,12 +1056,6 @@ impl X11Client { state.modifiers = modifiers; drop(state); - 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, @@ -1028,64 +1064,17 @@ impl X11Client { })); } - 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; - let (x, y) = if !modifiers.shift { - (0.0, delta_scroll) - } else { - (delta_scroll, 0.0) - }; - window.handle_input(PlatformInput::ScrollWheel( - crate::ScrollWheelEvent { - position, - delta: ScrollDelta::Lines(Point::new(x, y)), - modifiers, - touch_phase: TouchPhase::default(), - }, - )); - } - } + state = self.0.borrow_mut(); + if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) { + let scroll_delta = get_scroll_delta_and_update_state(&mut pointer, &event); + drop(state); + if let Some(scroll_delta) = scroll_delta { + window.handle_input(PlatformInput::ScrollWheel(make_scroll_wheel_event( + position, + scroll_delta, + modifiers, + ))); } - - valuator_idx += 1; } } Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => { @@ -1095,10 +1084,10 @@ impl X11Client { state.mouse_focused_window = Some(event.event); } Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => { - 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 mut state = self.0.borrow_mut(); + + // Set last scroll values to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global) + reset_all_pointer_device_scroll_positions(&mut state.pointer_device_states); state.mouse_focused_window = None; let pressed_button = pressed_button_from_mask(event.buttons[0]); let position = point( @@ -1117,6 +1106,26 @@ impl X11Client { })); window.set_hovered(false); } + Event::XinputHierarchy(event) => { + let mut state = self.0.borrow_mut(); + // Temporarily use `state.pointer_device_states` to only store pointers that still have valid scroll values. + // Any change to a device invalidates its scroll values. + for info in event.infos { + if is_pointer_device(info.type_) { + state.pointer_device_states.remove(&info.deviceid); + } + } + state.pointer_device_states = get_new_pointer_device_states( + &state.xcb_connection, + &state.pointer_device_states, + ); + } + Event::XinputDeviceChanged(event) => { + let mut state = self.0.borrow_mut(); + if let Some(mut pointer) = state.pointer_device_states.get_mut(&event.sourceid) { + reset_pointer_device_scroll_positions(&mut pointer); + } + } _ => {} }; @@ -1742,3 +1751,142 @@ fn xdnd_send_status( .send_event(false, target, EventMask::default(), message) .unwrap(); } + +/// Recomputes `pointer_device_states` by querying all pointer devices. +/// When a device is present in `scroll_values_to_preserve`, its value for `ScrollAxisState.scroll_value` is used. +fn get_new_pointer_device_states( + xcb_connection: &XCBConnection, + scroll_values_to_preserve: &BTreeMap, +) -> BTreeMap { + let devices_query_result = xcb_connection + .xinput_xi_query_device(XINPUT_ALL_DEVICES) + .unwrap() + .reply() + .unwrap(); + + let mut pointer_device_states = BTreeMap::new(); + pointer_device_states.extend( + devices_query_result + .infos + .iter() + .filter(|info| is_pointer_device(info.type_)) + .filter_map(|info| { + let scroll_data = info + .classes + .iter() + .filter_map(|class| class.data.as_scroll()) + .map(|class| *class) + .rev() + .collect::>(); + let old_state = scroll_values_to_preserve.get(&info.deviceid); + let old_horizontal = old_state.map(|state| &state.horizontal); + let old_vertical = old_state.map(|state| &state.vertical); + let horizontal = scroll_data + .iter() + .find(|data| data.scroll_type == xinput::ScrollType::HORIZONTAL) + .map(|data| scroll_data_to_axis_state(data, old_horizontal)); + let vertical = scroll_data + .iter() + .find(|data| data.scroll_type == xinput::ScrollType::VERTICAL) + .map(|data| scroll_data_to_axis_state(data, old_vertical)); + if horizontal.is_none() && vertical.is_none() { + None + } else { + Some(( + info.deviceid, + PointerDeviceState { + horizontal: horizontal.unwrap_or_else(Default::default), + vertical: vertical.unwrap_or_else(Default::default), + }, + )) + } + }), + ); + if pointer_device_states.is_empty() { + log::error!("Found no xinput mouse pointers."); + } + return pointer_device_states; +} + +/// Returns true if the device is a pointer device. Does not include pointer device groups. +fn is_pointer_device(type_: xinput::DeviceType) -> bool { + type_ == xinput::DeviceType::SLAVE_POINTER +} + +fn scroll_data_to_axis_state( + data: &xinput::DeviceClassDataScroll, + old_axis_state_with_valid_scroll_value: Option<&ScrollAxisState>, +) -> ScrollAxisState { + ScrollAxisState { + valuator_number: Some(data.number), + multiplier: SCROLL_LINES / fp3232_to_f32(data.increment), + scroll_value: old_axis_state_with_valid_scroll_value.and_then(|state| state.scroll_value), + } +} + +fn reset_all_pointer_device_scroll_positions( + pointer_device_states: &mut BTreeMap, +) { + pointer_device_states + .iter_mut() + .for_each(|(_, device_state)| reset_pointer_device_scroll_positions(device_state)); +} + +fn reset_pointer_device_scroll_positions(pointer: &mut PointerDeviceState) { + pointer.horizontal.scroll_value = None; + pointer.vertical.scroll_value = None; +} + +/// Returns the scroll delta for a smooth scrolling motion event, or `None` if no scroll data is present. +fn get_scroll_delta_and_update_state( + pointer: &mut PointerDeviceState, + event: &xinput::MotionEvent, +) -> Option> { + let delta_x = get_axis_scroll_delta_and_update_state(event, &mut pointer.horizontal); + let delta_y = get_axis_scroll_delta_and_update_state(event, &mut pointer.vertical); + if delta_x.is_some() || delta_y.is_some() { + Some(Point::new(delta_x.unwrap_or(0.0), delta_y.unwrap_or(0.0))) + } else { + None + } +} + +fn get_axis_scroll_delta_and_update_state( + event: &xinput::MotionEvent, + axis: &mut ScrollAxisState, +) -> Option { + let axis_index = get_valuator_axis_index(&event.valuator_mask, axis.valuator_number?)?; + if let Some(axis_value) = event.axisvalues.get(axis_index) { + let new_scroll = fp3232_to_f32(*axis_value); + let delta_scroll = axis + .scroll_value + .map(|old_scroll| (old_scroll - new_scroll) * axis.multiplier); + axis.scroll_value = Some(new_scroll); + delta_scroll + } else { + log::error!("Encountered invalid XInput valuator_mask, scrolling may not work properly."); + None + } +} + +fn make_scroll_wheel_event( + position: Point, + scroll_delta: Point, + modifiers: Modifiers, +) -> crate::ScrollWheelEvent { + // When shift is held down, vertical scrolling turns into horizontal scrolling. + let delta = if modifiers.shift { + Point { + x: scroll_delta.y, + y: 0.0, + } + } else { + scroll_delta + }; + crate::ScrollWheelEvent { + position, + delta: ScrollDelta::Lines(delta), + modifiers, + touch_phase: TouchPhase::default(), + } +} diff --git a/crates/gpui/src/platform/linux/x11/event.rs b/crates/gpui/src/platform/linux/x11/event.rs index 18ec392fc657e..cd4cef24a33f3 100644 --- a/crates/gpui/src/platform/linux/x11/event.rs +++ b/crates/gpui/src/platform/linux/x11/event.rs @@ -5,13 +5,29 @@ use x11rb::protocol::{ use crate::{Modifiers, MouseButton, NavigationDirection}; -pub(crate) fn button_of_key(detail: xproto::Button) -> Option { +pub(crate) enum ButtonOrScroll { + Button(MouseButton), + Scroll(ScrollDirection), +} + +pub(crate) enum ScrollDirection { + Up, + Down, + Left, + Right, +} + +pub(crate) fn button_or_scroll_from_event_detail(detail: u32) -> Option { Some(match detail { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 8 => MouseButton::Navigate(NavigationDirection::Back), - 9 => MouseButton::Navigate(NavigationDirection::Forward), + 1 => ButtonOrScroll::Button(MouseButton::Left), + 2 => ButtonOrScroll::Button(MouseButton::Middle), + 3 => ButtonOrScroll::Button(MouseButton::Right), + 4 => ButtonOrScroll::Scroll(ScrollDirection::Up), + 5 => ButtonOrScroll::Scroll(ScrollDirection::Down), + 6 => ButtonOrScroll::Scroll(ScrollDirection::Left), + 7 => ButtonOrScroll::Scroll(ScrollDirection::Right), + 8 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Back)), + 9 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Forward)), _ => return None, }) } @@ -48,3 +64,91 @@ pub(crate) fn pressed_button_from_mask(button_mask: u32) -> Option return None; }) } + +pub(crate) fn get_valuator_axis_index( + valuator_mask: &Vec, + valuator_number: u16, +) -> Option { + // XInput valuator masks have a 1 at the bit indexes corresponding to each + // valuator present in this event's axisvalues. Axisvalues is ordered from + // lowest valuator number to highest, so counting bits before the 1 bit for + // this valuator yields the index in axisvalues. + if bit_is_set_in_vec(&valuator_mask, valuator_number) { + Some(popcount_upto_bit_index(&valuator_mask, valuator_number) as usize) + } else { + None + } +} + +/// Returns the number of 1 bits in `bit_vec` for all bits where `i < bit_index`. +fn popcount_upto_bit_index(bit_vec: &Vec, bit_index: u16) -> u32 { + let array_index = bit_index as usize / 32; + let popcount: u32 = bit_vec + .get(array_index) + .map_or(0, |bits| keep_bits_upto(*bits, bit_index % 32).count_ones()); + if array_index == 0 { + popcount + } else { + // Valuator numbers over 32 probably never occur for scroll position, but may as well + // support it. + let leading_popcount: u32 = bit_vec + .iter() + .take(array_index) + .map(|bits| bits.count_ones()) + .sum(); + popcount + leading_popcount + } +} + +fn bit_is_set_in_vec(bit_vec: &Vec, bit_index: u16) -> bool { + let array_index = bit_index as usize / 32; + bit_vec + .get(array_index) + .map_or(false, |bits| bit_is_set(*bits, bit_index % 32)) +} + +fn bit_is_set(bits: u32, bit_index: u16) -> bool { + bits & (1 << bit_index) != 0 +} + +/// Sets every bit with `i >= bit_index` to 0. +fn keep_bits_upto(bits: u32, bit_index: u16) -> u32 { + if bit_index == 0 { + 0 + } else if bit_index >= 32 { + u32::MAX + } else { + bits & ((1 << bit_index) - 1) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_valuator_axis_index() { + assert!(get_valuator_axis_index(&vec![0b11], 0) == Some(0)); + assert!(get_valuator_axis_index(&vec![0b11], 1) == Some(1)); + assert!(get_valuator_axis_index(&vec![0b11], 2) == None); + + assert!(get_valuator_axis_index(&vec![0b100], 0) == None); + assert!(get_valuator_axis_index(&vec![0b100], 1) == None); + assert!(get_valuator_axis_index(&vec![0b100], 2) == Some(0)); + assert!(get_valuator_axis_index(&vec![0b100], 3) == None); + + assert!(get_valuator_axis_index(&vec![0b1010, 0], 0) == None); + assert!(get_valuator_axis_index(&vec![0b1010, 0], 1) == Some(0)); + assert!(get_valuator_axis_index(&vec![0b1010, 0], 2) == None); + assert!(get_valuator_axis_index(&vec![0b1010, 0], 3) == Some(1)); + + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 0) == None); + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 1) == Some(0)); + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 2) == None); + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 3) == Some(1)); + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 32) == Some(2)); + assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 33) == None); + + assert!(get_valuator_axis_index(&vec![0b1010, 0b101], 34) == Some(3)); + } +} diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 62b895d01f426..2884c7ea91a51 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -29,7 +29,7 @@ use std::{ sync::Arc, }; -use super::{X11Display, XINPUT_MASTER_DEVICE}; +use super::{X11Display, XINPUT_ALL_DEVICES, XINPUT_ALL_DEVICE_GROUPS}; x11rb::atom_manager! { pub XcbAtoms: AtomsCookie { XA_ATOM, @@ -475,7 +475,7 @@ impl X11WindowState { .xinput_xi_select_events( x_window, &[xinput::EventMask { - deviceid: XINPUT_MASTER_DEVICE, + deviceid: XINPUT_ALL_DEVICE_GROUPS, mask: vec![ xinput::XIEventMask::MOTION | xinput::XIEventMask::BUTTON_PRESS @@ -487,6 +487,19 @@ impl X11WindowState { ) .unwrap(); + xcb_connection + .xinput_xi_select_events( + x_window, + &[xinput::EventMask { + deviceid: XINPUT_ALL_DEVICES, + mask: vec![ + xinput::XIEventMask::HIERARCHY, + xinput::XIEventMask::DEVICE_CHANGED, + ], + }], + ) + .unwrap(); + xcb_connection.flush().unwrap(); let raw = RawWindow { @@ -1253,7 +1266,7 @@ impl PlatformWindow for X11Window { self.0.x_window, state.atoms._GTK_SHOW_WINDOW_MENU, [ - XINPUT_MASTER_DEVICE as u32, + XINPUT_ALL_DEVICE_GROUPS as u32, coords.dst_x as u32, coords.dst_y as u32, 0,