diff --git a/.changes/linux-device-thread.md b/.changes/linux-device-thread.md new file mode 100644 index 000000000..ecf1d0fa6 --- /dev/null +++ b/.changes/linux-device-thread.md @@ -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. \ No newline at end of file diff --git a/src/platform_impl/linux/device.rs b/src/platform_impl/linux/device.rs index d9bd079cf..a2784d03e 100644 --- a/src/platform_impl/linux/device.rs +++ b/src/platform_impl/linux/device.rs @@ -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(window_target: &EventLoopWindowTarget, device_tx: glib::Sender) { - 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) { + 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; } - _ => {} } + _ => {} } } - _ => {} } + _ => {} } - }); - } + } + }); } diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 339af7e95..a7012a1c6 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -102,6 +102,10 @@ impl EventLoopWindowTarget { self.display.backend().is_wayland() } + pub fn is_x11(&self) -> bool { + self.display.backend().is_x11() + } + #[inline] pub fn cursor_position(&self) -> Result, ExternalError> { util::cursor_position() @@ -117,6 +121,8 @@ pub struct EventLoop { events: crossbeam_channel::Receiver>, /// Draw queue of EventLoop draws: crossbeam_channel::Receiver, + /// Boolean to control device event thread + run_device_thread: Option>, } impl EventLoop { @@ -165,6 +171,27 @@ impl EventLoop { _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) { @@ -878,6 +905,7 @@ impl EventLoop { user_event_tx, events: event_rx, draws: draw_rx, + run_device_thread, }; Ok(event_loop) @@ -931,22 +959,8 @@ impl EventLoop { 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(|| { @@ -1052,7 +1066,9 @@ impl EventLoop { } 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)