Skip to content

Commit

Permalink
feat(linux): add DeviceEvent::Key (#600)
Browse files Browse the repository at this point in the history
Co-authored-by: Wu Yu Wei <wusyong9104@gmail.com>
  • Loading branch information
wusyong and Wu Yu Wei committed Oct 21, 2022
1 parent 0d76094 commit 775974d
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changes/linux-device.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tao": patch
---

On Linux, add DeviceEvent::Key.

84 changes: 84 additions & 0 deletions src/platform_impl/linux/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::{
os::raw::{c_int, c_uchar},
ptr,
};

use x11_dl::{xinput2, xlib};

use crate::{
event::{DeviceEvent, ElementState, RawKeyEvent},
event_loop::EventLoopWindowTarget,
};

use super::keycode_from_scancode;

/// Spawn Device event thread. Only works on x11 since wayland doesn't have such global events.
pub fn spawn<T>(window_target: &EventLoopWindowTarget<T>, device_tx: glib::Sender<DeviceEvent>) {
if !window_target.p.is_wayland() {
std::thread::spawn(move || unsafe {
let xlib = xlib::Xlib::open().unwrap();
let xinput2 = xinput2::XInput2::open().unwrap();
let display = (xlib.XOpenDisplay)(ptr::null());
let root = (xlib.XDefaultRootWindow)(display);
// TODO Add more device event mask
let mask = xinput2::XI_RawKeyPressMask | xinput2::XI_RawKeyReleaseMask;
let mut event_mask = xinput2::XIEventMask {
deviceid: xinput2::XIAllMasterDevices,
mask: &mask as *const _ as *mut c_uchar,
mask_len: std::mem::size_of_val(&mask) as c_int,
};
(xinput2.XISelectEvents)(display, root, &mut event_mask as *mut _, 1);

#[allow(clippy::uninit_assumed_init)]
let mut event: xlib::XEvent = std::mem::MaybeUninit::uninit().assume_init();
loop {
(xlib.XNextEvent)(display, &mut event);

// XFilterEvent tells us when an event has been discarded by the input method.
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
// along with an extra copy of the KeyRelease events. This also prevents backspace and
// arrow keys from being detected twice.
if xlib::True == {
(xlib.XFilterEvent)(&mut event, {
let xev: &xlib::XAnyEvent = event.as_ref();
xev.window
})
} {
continue;
}

let event_type = event.get_type();
match event_type {
xlib::GenericEvent => {
let mut xev = event.generic_event_cookie;
if (xlib.XGetEventData)(display, &mut xev) == xlib::True {
match xev.evtype {
xinput2::XI_RawKeyPress | xinput2::XI_RawKeyRelease => {
let xev: &xinput2::XIRawEvent = &*(xev.data as *const _);
let physical_key = keycode_from_scancode(xev.detail as u32);
let state = match xev.evtype {
xinput2::XI_RawKeyPress => ElementState::Pressed,
xinput2::XI_RawKeyRelease => ElementState::Released,
_ => unreachable!(),
};

let event = RawKeyEvent {
physical_key,
state,
};

if let Err(e) = device_tx.send(DeviceEvent::Key(event)) {
log::info!("Failed to send device event {} since receiver is closed. Closing x11 thread along with it", e);
break;
}
}
_ => {}
}
}
}
_ => {}
}
}
});
}
}
22 changes: 20 additions & 2 deletions src/platform_impl/linux/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
error::Error,
process,
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
time::Instant,
};

Expand All @@ -30,7 +31,7 @@ use crate::{
keyboard::ModifiersState,
menu::{MenuItem, MenuType},
monitor::MonitorHandle as RootMonitorHandle,
platform_impl::platform::{window::hit_test, DEVICE_ID},
platform_impl::platform::{device, window::hit_test, DEVICE_ID},
window::{CursorIcon, Fullscreen, WindowId as RootWindowId},
};

Expand Down Expand Up @@ -100,7 +101,7 @@ pub struct EventLoop<T: 'static> {
/// Window target.
window_target: RootELW<T>,
/// User event sender for EventLoopProxy
user_event_tx: crossbeam_channel::Sender<Event<'static, T>>,
pub(crate) user_event_tx: crossbeam_channel::Sender<Event<'static, T>>,
/// Event queue of EventLoop
events: crossbeam_channel::Receiver<Event<'static, T>>,
/// Draw queue of EventLoop
Expand Down Expand Up @@ -917,7 +918,23 @@ impl<T: 'static> EventLoop<T> {
DrawQueue,
}

// Spawn x11 thread to receive Device events.
let context = MainContext::default();
let user_event_tx = self.user_event_tx.clone();
let (device_tx, device_rx) = glib::MainContext::channel(glib::Priority::default());
let run_device_thread = Rc::new(AtomicBool::new(true));
let run = run_device_thread.clone();
device::spawn(&self.window_target, device_tx);
device_rx.attach(Some(&context), move |event| {
if let Err(e) = user_event_tx.send(Event::DeviceEvent {
device_id: DEVICE_ID,
event,
}) {
log::warn!("Fail to send device event to event channel: {}", e);
}
Continue(run.load(Ordering::Relaxed))
});

context
.with_thread_default(|| {
let mut control_flow = ControlFlow::default();
Expand Down Expand Up @@ -1022,6 +1039,7 @@ impl<T: 'static> EventLoop<T> {
}
gtk::main_iteration_do(blocking);
};
run_device_thread.store(false, Ordering::Relaxed);
exit_code
})
.unwrap_or(1)
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
))]

mod clipboard;
mod device;
mod event_loop;
mod global_shortcut;
mod icon;
Expand Down

0 comments on commit 775974d

Please sign in to comment.