Skip to content

Commit

Permalink
XI2 Smooth Scrolling for X11 - Attempt 2 (#11110)
Browse files Browse the repository at this point in the history
This should have fixed the problems that some users were reporting with
#10695 .

The problem was with devices which send more than one valuator axis in a
single event whereas the original PR assumed there would only ever be
one axis per event. This version also does away with the complicated
device selection and instead just uses the master pointer device, which
automatically uses all sub-pointers.

Edit: Confirmed working for one of the user's which the first attempt
was broken for.

Release Notes:

- Added smooth scrolling for X11 on Linux
- Added horizontal scrolling for X11 on Linux
  • Loading branch information
someone13574 committed Apr 29, 2024
1 parent ff8e7f9 commit ec95605
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 20 deletions.
2 changes: 1 addition & 1 deletion crates/gpui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
140 changes: 125 additions & 15 deletions crates/gpui/src/platform/linux/x11/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ 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;

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};
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
})))
Expand Down Expand Up @@ -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:?}");
}
Expand All @@ -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);
Expand Down Expand Up @@ -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
}
16 changes: 15 additions & 1 deletion crates/gpui/src/platform/linux/x11/event.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use x11rb::protocol::xproto;
use x11rb::protocol::{
xinput,
xproto::{self, ModMask},
};

use crate::{Modifiers, MouseButton, NavigationDirection};

Expand All @@ -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<MouseButton> {
Some(if state.contains(xproto::KeyButMask::BUTTON1) {
MouseButton::Left
Expand Down
19 changes: 16 additions & 3 deletions crates/gpui/src/platform/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
);

Expand All @@ -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
Expand Down

0 comments on commit ec95605

Please sign in to comment.