Skip to content

Commit

Permalink
Add back Xlib-Xinput2 device handling
Browse files Browse the repository at this point in the history
It seems that Xlib's event code ignores device events if I don't do
this. Once rust-windowing#2767 is merged we can work around this issue.
  • Loading branch information
notgull committed Aug 5, 2023
1 parent 626834b commit 0efdb15
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 51 deletions.
23 changes: 11 additions & 12 deletions src/platform_impl/linux/x11/event_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,9 @@ impl<T: 'static> EventProcessor<T> {
pub(super) fn init_device(&self, device: xinput::DeviceId) {
let wt = get_xtarget(&self.target);
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
for info in info.info.iter() {
devices.insert(
DeviceId(info.deviceid),
Device::new(info).expect("Failed to get device info"),
);
if let Some(info) = DeviceInfo::get(&wt.xconn, device as _) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid as _), Device::new(info));
}
}
}
Expand Down Expand Up @@ -875,17 +872,19 @@ impl<T: 'static> EventProcessor<T> {
let window_id = mkwid(window);
let device_id = mkdid(xev.deviceid as xinput::DeviceId);

if let Some(all_info) = DeviceInfo::get(&wt.xconn, super::ALL_DEVICES) {
if let Some(all_info) =
DeviceInfo::get(&wt.xconn, super::ALL_DEVICES.into())
{
let mut devices = self.devices.borrow_mut();
for device_info in all_info.info.iter() {
if device_info.deviceid == xev.sourceid as xinput::DeviceId
for device_info in all_info.iter() {
if device_info.deviceid == xev.sourceid
// This is needed for resetting to work correctly on i3, and
// presumably some other WMs. On those, `XI_Enter` doesn't include
// the physical device ID, so both `sourceid` and `deviceid` are
// the virtual device.
|| device_info.attachment == xev.sourceid as xinput::DeviceId
|| device_info.attachment == xev.sourceid
{
let device_id = DeviceId(device_info.deviceid);
let device_id = DeviceId(device_info.deviceid as _);
if let Some(device) = devices.get_mut(&device_id) {
device.reset_scroll_position(device_info);
}
Expand Down Expand Up @@ -979,7 +978,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
device_id: mkdid(pointer_id as _),
position,
},
});
Expand Down
111 changes: 72 additions & 39 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ use std::{
collections::{HashMap, HashSet, VecDeque},
ffi::CStr,
fmt,
mem::MaybeUninit,
mem::{self, MaybeUninit},
ops::Deref,
os::{
raw::*,
unix::io::{AsRawFd, RawFd},
},
ptr,
rc::Rc,
str,
slice, str,
sync::{mpsc, Arc, Weak},
time::{Duration, Instant},
};
Expand Down Expand Up @@ -689,22 +689,43 @@ impl<T: 'static> EventLoopProxy<T> {
}
}

struct DeviceInfo {
info: Vec<xinput::XIDeviceInfo>,
struct DeviceInfo<'a> {
xconn: &'a XConnection,
info: *const ffi::XIDeviceInfo,
count: usize,
}

impl DeviceInfo {
fn get(xconn: &XConnection, device: xinput::DeviceId) -> Option<Self> {
let device_data = xconn
.xcb_connection()
.xinput_xi_query_device(device)
.ok()?
.reply()
.ok()?;
impl<'a> DeviceInfo<'a> {
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
unsafe {
let mut count = 0;
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
xconn.check_errors().ok()?;

if info.is_null() || count == 0 {
None
} else {
Some(DeviceInfo {
xconn,
info,
count: count as usize,
})
}
}
}
}

Some(DeviceInfo {
info: device_data.infos,
})
impl<'a> Drop for DeviceInfo<'a> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}

impl<'a> Deref for DeviceInfo<'a> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.info, self.count) }
}
}

Expand Down Expand Up @@ -931,10 +952,10 @@ fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId {
#[derive(Debug)]
struct Device {
_name: String,
scroll_axes: Vec<(u16, ScrollAxis)>,
scroll_axes: Vec<(i32, ScrollAxis)>,
// For master devices, this is the paired device (pointer <-> keyboard).
// For slave devices, this is the master.
attachment: u16,
attachment: c_int,
}

#[derive(Debug, Copy, Clone)]
Expand All @@ -951,21 +972,25 @@ enum ScrollOrientation {
}

impl Device {
fn new(info: &xinput::XIDeviceInfo) -> Result<Self, X11Error> {
let name = str::from_utf8(&info.name).expect("device name is not valid utf8");
fn new(info: &ffi::XIDeviceInfo) -> Self {
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
let mut scroll_axes = Vec::new();

if Device::physical_device(info) {
// Identify scroll axes
for class in info.classes.iter() {
if let xinput::DeviceClassData::Scroll(info) = &class.data {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
if class._type == ffi::XIScrollClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
};
scroll_axes.push((
info.number,
ScrollAxis {
increment: xinput_fp3232_to_float(info.increment),
increment: info.increment,
orientation: match info.scroll_type {
xinput::ScrollType::HORIZONTAL => ScrollOrientation::Horizontal,
xinput::ScrollType::VERTICAL => ScrollOrientation::Vertical,
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => unreachable!(),
},
position: 0.0,
Expand All @@ -976,42 +1001,50 @@ impl Device {
}

let mut device = Device {
_name: name.to_string(),
_name: name.into_owned(),
scroll_axes,
attachment: info.attachment,
};
device.reset_scroll_position(info);
Ok(device)
device
}

fn reset_scroll_position(&mut self, info: &xinput::XIDeviceInfo) {
fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
if Device::physical_device(info) {
for class in info.classes.iter() {
if let xinput::DeviceClassData::Valuator(info) = &class.data {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
if class._type == ffi::XIValuatorClass {
let info = unsafe {
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
};
if let Some(&mut (_, ref mut axis)) = self
.scroll_axes
.iter_mut()
.find(|&&mut (axis, _)| axis == info.number)
{
axis.position = xinput_fp3232_to_float(info.value);
axis.position = info.value;
}
}
}
}
}

#[inline]
fn physical_device(info: &xinput::XIDeviceInfo) -> bool {
use xinput::DeviceType;
info.type_ == DeviceType::SLAVE_KEYBOARD
|| info.type_ == DeviceType::SLAVE_POINTER
|| info.type_ == DeviceType::FLOATING_SLAVE
fn physical_device(info: &ffi::XIDeviceInfo) -> bool {
info._use == ffi::XISlaveKeyboard
|| info._use == ffi::XISlavePointer
|| info._use == ffi::XIFloatingSlave
}
}

fn xinput_fp3232_to_float(fp: xinput::Fp3232) -> f64 {
let xinput::Fp3232 { integral, frac } = fp;
integral as f64 + (frac as f64 / (1u64 << 32) as f64)
#[inline]
fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] {
unsafe {
slice::from_raw_parts(
info.classes as *const *const ffi::XIAnyClassInfo,
info.num_classes as usize,
)
}
}
}

// Xinput constants not defined in x11rb
Expand Down
7 changes: 7 additions & 0 deletions src/platform_impl/linux/x11/xdisplay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi:
pub(crate) struct XConnection {
pub xlib: ffi::Xlib,
pub xcursor: ffi::Xcursor,

/// I'd like to remove this, but apparently Xlib and Xinput2 are tied together
/// for some reason.
pub xinput2: ffi::XInput2,

pub display: *mut ffi::Display,

/// The manager for the XCB connection.
Expand Down Expand Up @@ -58,6 +63,7 @@ impl XConnection {
let xlib = ffi::Xlib::open()?;
let xcursor = ffi::Xcursor::open()?;
let xlib_xcb = ffi::Xlib_xcb::open()?;
let xinput2 = ffi::XInput2::open()?;

unsafe { (xlib.XInitThreads)() };
unsafe { (xlib.XSetErrorHandler)(error_handler) };
Expand Down Expand Up @@ -101,6 +107,7 @@ impl XConnection {
Ok(XConnection {
xlib,
xcursor,
xinput2,
display,
xcb: Some(xcb),
atoms: Box::new(atoms),
Expand Down

0 comments on commit 0efdb15

Please sign in to comment.