@@ -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<xproto::Window>,
pub(crate) xkb: xkbc::State,
+ pub(crate) scroll_class_data: Vec<xinput::DeviceClassDataScroll>,
+ pub(crate) scroll_x: Option<f32>,
+ pub(crate) scroll_y: Option<f32>,
+
pub(crate) common: LinuxCommon,
pub(crate) clipboard: X11ClipboardContext<Clipboard>,
pub(crate) primary: X11ClipboardContext<Primary>,
@@ -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::<Vec<_>>();
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::<Vec<_>>();
+
+ 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
+}
@@ -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