XI2 Smooth Scrolling for X11 - Attempt 2 (#11110)
This should have fixed the problems that some users were reporting with https://github.com/zed-industries/zed/pull/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
This commit is contained in:
parent
ff8e7f91c1
commit
ec95605fec
4 changed files with 157 additions and 20 deletions
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<MouseButton> {
|
||||
Some(if state.contains(xproto::KeyButMask::BUTTON1) {
|
||||
MouseButton::Left
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue