Skip to content

Commit

Permalink
fix(linux): spawn device thread only once (#678)
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir authored Jan 23, 2023
1 parent e114956 commit ca1ed5d
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 77 deletions.
5 changes: 5 additions & 0 deletions .changes/linux-device-thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

On Linux, spawn device event thread only once instead of a new thread on each iteration of the event loop.
117 changes: 56 additions & 61 deletions src/platform_impl/linux/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,75 @@ use std::{

use x11_dl::{xinput2, xlib};

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

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);
pub fn spawn(device_tx: glib::Sender<DeviceEvent>) {
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);
#[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;
}
// 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_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,
};
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;
}
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;
}
_ => {}
}
_ => {}
}
}
_ => {}
}
_ => {}
}
});
}
}
});
}
48 changes: 32 additions & 16 deletions src/platform_impl/linux/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ impl<T> EventLoopWindowTarget<T> {
self.display.backend().is_wayland()
}

pub fn is_x11(&self) -> bool {
self.display.backend().is_x11()
}

#[inline]
pub fn cursor_position(&self) -> Result<PhysicalPosition<f64>, ExternalError> {
util::cursor_position()
Expand All @@ -117,6 +121,8 @@ pub struct EventLoop<T: 'static> {
events: crossbeam_channel::Receiver<Event<'static, T>>,
/// Draw queue of EventLoop
draws: crossbeam_channel::Receiver<WindowId>,
/// Boolean to control device event thread
run_device_thread: Option<Rc<AtomicBool>>,
}

impl<T: 'static> EventLoop<T> {
Expand Down Expand Up @@ -165,6 +171,27 @@ impl<T: 'static> EventLoop<T> {
_marker: std::marker::PhantomData,
};

// Spawn x11 thread to receive Device events.
let run_device_thread = if window_target.is_x11() {
let (device_tx, device_rx) = glib::MainContext::channel(glib::Priority::default());
let user_event_tx = user_event_tx.clone();
let run_device_thread = Rc::new(AtomicBool::new(true));
let run = run_device_thread.clone();
device::spawn(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))
});
Some(run_device_thread)
} else {
None
};

// Window Request
window_requests_rx.attach(Some(&context), move |(id, request)| {
if let Some(window) = app_.window_by_id(id.0) {
Expand Down Expand Up @@ -878,6 +905,7 @@ impl<T: 'static> EventLoop<T> {
user_event_tx,
events: event_rx,
draws: draw_rx,
run_device_thread,
};

Ok(event_loop)
Expand Down Expand Up @@ -931,22 +959,8 @@ 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))
});
let run_device_thread = self.run_device_thread.clone();

context
.with_thread_default(|| {
Expand Down Expand Up @@ -1052,7 +1066,9 @@ impl<T: 'static> EventLoop<T> {
}
gtk::main_iteration_do(blocking);
};
run_device_thread.store(false, Ordering::Relaxed);
if let Some(run_device_thread) = run_device_thread {
run_device_thread.store(false, Ordering::Relaxed);
}
exit_code
})
.unwrap_or(1)
Expand Down

0 comments on commit ca1ed5d

Please sign in to comment.