#![cfg(x11_platform)] mod dnd; mod event_processor; pub mod ffi; mod ime; mod monitor; pub mod util; mod window; mod xdisplay; pub(crate) use self::{ monitor::{MonitorHandle, VideoMode}, window::UnownedWindow, xdisplay::XConnection, }; pub use self::xdisplay::{XError, XNotSupported}; use std::{ cell::{Cell, RefCell}, collections::{HashMap, HashSet}, ffi::CStr, mem::{self, MaybeUninit}, ops::Deref, os::raw::*, ptr, rc::Rc, slice, sync::mpsc::{Receiver, Sender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; use libc::{self, setlocale, LC_CTYPE}; use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker}; use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}, }; use super::common::xkb_state::KbdState; use crate::{ error::OsError as RootOsError, event::{Event, StartCause}, event_loop::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, platform_impl::{ platform::{sticky_exit_callback, WindowId}, PlatformSpecificWindowBuilderAttributes, }, window::WindowAttributes, }; const X_TOKEN: Token = Token(0); const USER_REDRAW_TOKEN: Token = Token(1); struct WakeSender { sender: Sender, waker: Arc, } struct PeekableReceiver { recv: Receiver, first: Option, } impl PeekableReceiver { pub fn from_recv(recv: Receiver) -> Self { Self { recv, first: None } } pub fn has_incoming(&mut self) -> bool { if self.first.is_some() { return true; } match self.recv.try_recv() { Ok(v) => { self.first = Some(v); true } Err(TryRecvError::Empty) => false, Err(TryRecvError::Disconnected) => { warn!("Channel was disconnected when checking incoming"); false } } } pub fn try_recv(&mut self) -> Result { if let Some(first) = self.first.take() { return Ok(first); } self.recv.try_recv() } } pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: ffi::Atom, net_wm_ping: ffi::Atom, ime_sender: ImeSender, root: ffi::Window, ime: RefCell, windows: RefCell>>, redraw_sender: WakeSender, device_event_filter: Cell, _marker: ::std::marker::PhantomData, } pub struct EventLoop { poll: Poll, waker: Arc, event_processor: EventProcessor, redraw_receiver: PeekableReceiver, user_receiver: PeekableReceiver, //waker.wake needs to be called whenever something gets sent user_sender: Sender, target: Rc>, } pub struct EventLoopProxy { user_sender: Sender, waker: Arc, } impl Clone for EventLoopProxy { fn clone(&self) -> Self { EventLoopProxy { user_sender: self.user_sender.clone(), waker: self.waker.clone(), } } } impl EventLoop { pub(crate) fn new(xconn: Arc) -> EventLoop { let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") }; let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\0") }; let dnd = Dnd::new(Arc::clone(&xconn)) .expect("Failed to call XInternAtoms when initializing drag and drop"); let (ime_sender, ime_receiver) = mpsc::channel(); let (ime_event_sender, ime_event_receiver) = mpsc::channel(); // Input methods will open successfully without setting the locale, but it won't be // possible to actually commit pre-edit sequences. unsafe { // Remember default locale to restore it if target locale is unsupported // by Xlib let default_locale = setlocale(LC_CTYPE, ptr::null()); setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); // Check if set locale is supported by Xlib. // If not, calls to some Xlib functions like `XSetLocaleModifiers` // will fail. let locale_supported = (xconn.xlib.XSupportsLocale)() == 1; if !locale_supported { let unsupported_locale = setlocale(LC_CTYPE, ptr::null()); warn!( "Unsupported locale \"{}\". Restoring default locale \"{}\".", CStr::from_ptr(unsupported_locale).to_string_lossy(), CStr::from_ptr(default_locale).to_string_lossy() ); // Restore default locale setlocale(LC_CTYPE, default_locale); } } let ime = RefCell::new({ let result = Ime::new(Arc::clone(&xconn), ime_event_sender); if let Err(ImeCreationError::OpenFailure(ref state)) = result { panic!("Failed to open input method: {state:#?}"); } result.expect("Failed to set input method destruction callback") }); let randr_event_offset = xconn .select_xrandr_input(root) .expect("Failed to query XRandR extension"); let xi2ext = unsafe { let mut ext = XExtension::default(); let res = (xconn.xlib.XQueryExtension)( xconn.display, b"XInputExtension\0".as_ptr() as *const c_char, &mut ext.opcode, &mut ext.first_event_id, &mut ext.first_error_id, ); if res == ffi::False { panic!("X server missing XInput extension"); } ext }; let xkbext = { let mut ext = XExtension::default(); let res = unsafe { (xconn.xlib.XkbQueryExtension)( xconn.display, &mut ext.opcode, &mut ext.first_event_id, &mut ext.first_error_id, &mut 1, &mut 0, ) }; if res == ffi::False { panic!("X server missing XKB extension"); } ext }; unsafe { let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; if (xconn.xinput2.XIQueryVersion)( xconn.display, &mut xinput_major_ver, &mut xinput_minor_ver, ) != ffi::Success as libc::c_int { panic!( "X server has XInput extension {xinput_major_ver}.{xinput_minor_ver} but does not support XInput2", ); } } xconn.update_cached_wm_info(root); let poll = Poll::new().unwrap(); let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); poll.registry() .register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE) .unwrap(); let (user_sender, user_channel) = std::sync::mpsc::channel(); let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); let kb_state = KbdState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }) .unwrap(); let window_target = EventLoopWindowTarget { ime, root, windows: Default::default(), _marker: ::std::marker::PhantomData, ime_sender, xconn, wm_delete_window, net_wm_ping, redraw_sender: WakeSender { sender: redraw_sender, // not used again so no clone waker: waker.clone(), }, device_event_filter: Default::default(), }; // Set initial device event filter. window_target.update_device_event_filter(true); let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(window_target), _marker: ::std::marker::PhantomData, }); let event_processor = EventProcessor { target: target.clone(), dnd, devices: Default::default(), randr_event_offset, ime_receiver, ime_event_receiver, xi2ext, xkbext, kb_state, num_touch: 0, first_touch: None, active_window: None, is_composing: false, }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) get_xtarget(&target) .xconn .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) .queue(); get_xtarget(&target) .xconn .select_xkb_events( 0x100, // Use the "core keyboard device" ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask, ) .unwrap() .queue(); event_processor.init_device(ffi::XIAllDevices); EventLoop { poll, waker, event_processor, redraw_receiver: PeekableReceiver::from_recv(redraw_channel), user_receiver: PeekableReceiver::from_recv(user_channel), user_sender, target, } } pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: self.user_sender.clone(), waker: self.waker.clone(), } } pub(crate) fn window_target(&self) -> &RootELW { &self.target } pub fn run_return(&mut self, mut callback: F) -> i32 where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { struct IterationResult { deadline: Option, timeout: Option, wait_start: Instant, } fn single_iteration( this: &mut EventLoop, control_flow: &mut ControlFlow, cause: &mut StartCause, callback: &mut F, ) -> IterationResult where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { sticky_exit_callback( crate::event::Event::NewEvents(*cause), &this.target, control_flow, callback, ); // NB: For consistency all platforms must emit a 'resumed' event even though X11 // applications don't themselves have a formal suspend/resume lifecycle. if *cause == StartCause::Init { sticky_exit_callback( crate::event::Event::Resumed, &this.target, control_flow, callback, ); } // Process all pending events this.drain_events(callback, control_flow); // Empty the user event buffer { while let Ok(event) = this.user_receiver.try_recv() { sticky_exit_callback( crate::event::Event::UserEvent(event), &this.target, control_flow, callback, ); } } // send MainEventsCleared { sticky_exit_callback( crate::event::Event::MainEventsCleared, &this.target, control_flow, callback, ); } // Empty the redraw requests { let mut windows = HashSet::new(); while let Ok(window_id) = this.redraw_receiver.try_recv() { windows.insert(window_id); } for window_id in windows { let window_id = crate::window::WindowId(window_id); sticky_exit_callback( Event::RedrawRequested(window_id), &this.target, control_flow, callback, ); } } // send RedrawEventsCleared { sticky_exit_callback( crate::event::Event::RedrawEventsCleared, &this.target, control_flow, callback, ); } let start = Instant::now(); let (deadline, timeout); match control_flow { ControlFlow::ExitWithCode(_) => { return IterationResult { wait_start: start, deadline: None, timeout: None, }; } ControlFlow::Poll => { *cause = StartCause::Poll; deadline = None; timeout = Some(Duration::from_millis(0)); } ControlFlow::Wait => { *cause = StartCause::WaitCancelled { start, requested_resume: None, }; deadline = None; timeout = None; } ControlFlow::WaitUntil(wait_deadline) => { *cause = StartCause::ResumeTimeReached { start, requested_resume: *wait_deadline, }; timeout = if *wait_deadline > start { Some(*wait_deadline - start) } else { Some(Duration::from_millis(0)) }; deadline = Some(*wait_deadline); } } IterationResult { wait_start: start, deadline, timeout, } } let mut control_flow = ControlFlow::default(); let mut events = Events::with_capacity(8); let mut cause = StartCause::Init; // run the initial loop iteration let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); let exit_code = loop { if let ControlFlow::ExitWithCode(code) = control_flow { break code; } let has_pending = self.event_processor.poll() || self.user_receiver.has_incoming() || self.redraw_receiver.has_incoming(); if !has_pending { // Wait until if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) { if e.raw_os_error() != Some(libc::EINTR) { panic!("epoll returned an error: {e:?}"); } } events.clear(); if control_flow == ControlFlow::Wait { // We don't go straight into executing the event loop iteration, we instead go // to the start of this loop and check again if there's any pending event. We // must do this because during the execution of the iteration we sometimes wake // the mio waker, and if the waker is already awaken before we call poll(), // then poll doesn't block, but it returns immediately. This caused the event // loop to run continuously even if the control_flow was `Wait` continue; } } let wait_cancelled = iter_result .deadline .map_or(false, |deadline| Instant::now() < deadline); if wait_cancelled { cause = StartCause::WaitCancelled { start: iter_result.wait_start, requested_resume: iter_result.deadline, }; } iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback); }; callback( crate::event::Event::LoopDestroyed, &self.target, &mut control_flow, ); exit_code } pub fn run(mut self, callback: F) -> ! where F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let exit_code = self.run_return(callback); ::std::process::exit(exit_code); } fn drain_events(&mut self, callback: &mut F, control_flow: &mut ControlFlow) where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let target = &self.target; let mut xev = MaybeUninit::uninit(); let wt = get_xtarget(&self.target); while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { let mut xev = unsafe { xev.assume_init() }; self.event_processor.process_event(&mut xev, |event| { sticky_exit_callback( event, target, control_flow, &mut |event, window_target, control_flow| { if let Event::RedrawRequested(crate::window::WindowId(wid)) = event { wt.redraw_sender.sender.send(wid).unwrap(); wt.redraw_sender.waker.wake().unwrap(); } else { callback(event, window_target, control_flow); } }, ); }); } } } pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget { match target.p { super::EventLoopWindowTarget::X(ref target) => target, #[cfg(wayland_platform)] _ => unreachable!(), } } impl EventLoopWindowTarget { /// Returns the `XConnection` of this events loop. #[inline] pub(crate) fn x_connection(&self) -> &Arc { &self.xconn } pub fn set_device_event_filter(&self, filter: DeviceEventFilter) { self.device_event_filter.set(filter); } /// Update the device event filter based on window focus. pub fn update_device_event_filter(&self, focus: bool) { let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never || (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus); let mut mask = 0; if !filter_events { mask = ffi::XI_RawMotionMask | ffi::XI_RawButtonPressMask | ffi::XI_RawButtonReleaseMask | ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask; } self.xconn .select_xinput_events(self.root, ffi::XIAllMasterDevices, mask) .queue(); } pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle { let mut display_handle = XlibDisplayHandle::empty(); display_handle.display = self.xconn.display as *mut _; display_handle.screen = unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) }; RawDisplayHandle::Xlib(display_handle) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_sender .send(event) .map_err(|e| EventLoopClosed(e.0)) .map(|_| self.waker.wake().unwrap()) } } struct DeviceInfo<'a> { xconn: &'a XConnection, info: *const ffi::XIDeviceInfo, count: usize, } impl<'a> DeviceInfo<'a> { fn get(xconn: &'a XConnection, device: c_int) -> Option { 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, }) } } } } 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) } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); impl DeviceId { #[allow(unused)] pub const unsafe fn dummy() -> Self { DeviceId(0) } } pub(crate) struct Window(Arc); impl Deref for Window { type Target = UnownedWindow; #[inline] fn deref(&self) -> &UnownedWindow { &self.0 } } impl Window { pub(crate) fn new( event_loop: &EventLoopWindowTarget, attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let window = Arc::new(UnownedWindow::new(event_loop, attribs, pl_attribs)?); event_loop .windows .borrow_mut() .insert(window.id(), Arc::downgrade(&window)); Ok(Window(window)) } } impl Drop for Window { fn drop(&mut self) { let window = self.deref(); let xconn = &window.xconn; unsafe { (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window); // If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about. let _ = xconn.check_errors(); } } } /// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to /// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed struct GenericEventCookie<'a> { xconn: &'a XConnection, cookie: ffi::XGenericEventCookie, } impl<'a> GenericEventCookie<'a> { fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option> { unsafe { let mut cookie: ffi::XGenericEventCookie = From::from(event); if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True { Some(GenericEventCookie { xconn, cookie }) } else { None } } } } impl<'a> Drop for GenericEventCookie<'a> { fn drop(&mut self) { unsafe { (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); } } } #[derive(Debug, Default, Copy, Clone)] struct XExtension { opcode: c_int, first_event_id: c_int, first_error_id: c_int, } fn mkwid(w: ffi::Window) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) } fn mkdid(w: c_int) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] struct Device { _name: String, scroll_axes: Vec<(i32, ScrollAxis)>, // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. attachment: c_int, } #[derive(Debug, Copy, Clone)] struct ScrollAxis { increment: f64, orientation: ScrollOrientation, position: f64, } #[derive(Debug, Copy, Clone)] enum ScrollOrientation { Vertical, Horizontal, } impl Device { 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_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: info.increment, orientation: match info.scroll_type { ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, _ => unreachable!(), }, position: 0.0, }, )); } } } let mut device = Device { _name: name.into_owned(), scroll_axes, attachment: info.attachment, }; device.reset_scroll_position(info); device } fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) { if Device::physical_device(info) { 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 = info.value; } } } } } #[inline] fn physical_device(info: &ffi::XIDeviceInfo) -> bool { info._use == ffi::XISlaveKeyboard || info._use == ffi::XISlavePointer || info._use == ffi::XIFloatingSlave } #[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, ) } } }