From bb9b629bc34c6db8fa1ebda24a03ce0ddbc1d51e Mon Sep 17 00:00:00 2001 From: John Nunley Date: Tue, 29 Aug 2023 14:01:25 -0700 Subject: [PATCH] Implement X11 extensions using x11rb instead of Xlib Removes Xlib code by replacing it with the x11rb equivalent, the commit handles xrandr, xinput, xinput2, and xkb. Signed-off-by: John Nunley --- Cargo.toml | 2 +- src/platform_impl/linux/mod.rs | 3 +- .../linux/x11/event_processor.rs | 89 +++--- src/platform_impl/linux/x11/ffi.rs | 1 - src/platform_impl/linux/x11/mod.rs | 121 +++----- src/platform_impl/linux/x11/monitor.rs | 278 ++++++++++-------- src/platform_impl/linux/x11/util/input.rs | 69 +++-- src/platform_impl/linux/x11/util/randr.rs | 164 ++++------- src/platform_impl/linux/x11/window.rs | 33 ++- src/platform_impl/linux/x11/xdisplay.rs | 33 ++- 10 files changed, 399 insertions(+), 394 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7fcd29c..6eb5d619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,7 @@ wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = tr calloop = "0.10.5" rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } x11-dl = { version = "2.18.5", optional = true } -x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "xinput", "xkb"], optional = true } +x11rb = { version = "0.12.0", default-features = false, features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } xkbcommon-dl = "0.4.0" memmap2 = { version = "0.5.0", optional = true } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index a226748f..ff28701a 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -851,6 +851,7 @@ impl EventLoopWindowTarget { .x_connection() .available_monitors() .into_iter() + .flatten() .map(MonitorHandle::X) .collect(), } @@ -863,7 +864,7 @@ impl EventLoopWindowTarget { EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(), #[cfg(x11_platform)] EventLoopWindowTarget::X(ref evlp) => { - let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor()); + let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor().ok()?); Some(primary_monitor) } } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index fc752a2e..03941201 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -7,13 +7,18 @@ use std::{ sync::{Arc, Mutex}, }; -use x11rb::protocol::xproto::{self, ConnectionExt as _}; use x11rb::x11_utils::Serialize; +use x11rb::{ + protocol::{ + xinput, + xproto::{self, ConnectionExt as _}, + }, + x11_utils::ExtensionInformation, +}; use super::{ - atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId, - DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, - WindowId, XExtension, + atoms::*, ffi, get_xtarget, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo, + Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, }; use crate::{ @@ -35,10 +40,10 @@ pub(super) struct EventProcessor { pub(super) dnd: Dnd, pub(super) ime_receiver: ImeReceiver, pub(super) ime_event_receiver: ImeEventReceiver, - pub(super) randr_event_offset: c_int, + pub(super) randr_event_offset: u8, pub(super) devices: RefCell>, - pub(super) xi2ext: XExtension, - pub(super) xkbext: XExtension, + pub(super) xi2ext: ExtensionInformation, + pub(super) xkbext: ExtensionInformation, pub(super) target: Rc>, pub(super) kb_state: KbdState, // Number of touch events currently in progress @@ -55,12 +60,12 @@ pub(super) struct EventProcessor { } impl EventProcessor { - pub(super) fn init_device(&self, device: c_int) { + 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) { + if let Some(info) = DeviceInfo::get(&wt.xconn, device as _) { for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(info)); + devices.insert(DeviceId(info.deviceid as _), Device::new(info)); } } } @@ -420,7 +425,10 @@ impl EventProcessor { let last_scale_factor = shared_state_lock.last_monitor.scale_factor; let new_scale_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - let monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); + let monitor = wt + .xconn + .get_monitor_for_window(Some(window_rect)) + .expect("Failed to find monitor for window"); if monitor.is_dummy() { // Avoid updating monitor using a dummy monitor handle @@ -596,7 +604,7 @@ impl EventProcessor { }; let window_id = mkwid(window); - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into()); + let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); let keycode = xkev.keycode as _; @@ -672,7 +680,7 @@ impl EventProcessor { return; }; let xev = &guard.cookie; - if self.xi2ext.opcode != xev.extension { + if self.xi2ext.major_opcode != xev.extension as u8 { return; } @@ -691,7 +699,7 @@ impl EventProcessor { ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; let window_id = mkwid(xev.event as xproto::Window); - let device_id = mkdid(xev.deviceid); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); @@ -787,7 +795,7 @@ impl EventProcessor { // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - let device_id = mkdid(xev.deviceid); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); let window = xev.event as xproto::Window; let window_id = mkwid(window); let new_cursor_pos = (xev.event_x, xev.event_y); @@ -820,7 +828,9 @@ impl EventProcessor { ) }; let mut devices = self.devices.borrow_mut(); - let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + let physical_device = match devices + .get_mut(&DeviceId(xev.sourceid as xinput::DeviceId)) + { Some(device) => device, None => return, }; @@ -832,7 +842,7 @@ impl EventProcessor { if let Some(&mut (_, ref mut info)) = physical_device .scroll_axes .iter_mut() - .find(|&&mut (axis, _)| axis == i) + .find(|&&mut (axis, _)| axis == i as _) { let delta = (x - info.position) / info.increment; info.position = x; @@ -879,9 +889,11 @@ impl EventProcessor { let window = xev.event as xproto::Window; let window_id = mkwid(window); - let device_id = mkdid(xev.deviceid); + let device_id = mkdid(xev.deviceid as xinput::DeviceId); - if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) { + 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.iter() { if device_info.deviceid == xev.sourceid @@ -891,7 +903,7 @@ impl EventProcessor { // the virtual device. || 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); } @@ -930,7 +942,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id: mkwid(window), event: CursorLeft { - device_id: mkdid(xev.deviceid), + device_id: mkdid(xev.deviceid as xinput::DeviceId), }, }); } @@ -978,14 +990,14 @@ impl EventProcessor { let pointer_id = self .devices .borrow() - .get(&DeviceId(xev.deviceid)) + .get(&DeviceId(xev.deviceid as xinput::DeviceId)) .map(|device| device.attachment) .unwrap_or(2); callback(Event::WindowEvent { window_id, event: CursorMoved { - device_id: mkdid(pointer_id), + device_id: mkdid(pointer_id as _), position, }, }); @@ -1076,7 +1088,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { - device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()), + device_id: mkdid(util::VIRTUAL_CORE_POINTER), position: location.cast(), }, }); @@ -1085,7 +1097,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), + device_id: mkdid(xev.deviceid as xinput::DeviceId), phase, location, force: None, // TODO @@ -1103,7 +1115,7 @@ impl EventProcessor { if xev.flags & ffi::XIPointerEmulated == 0 { callback(Event::DeviceEvent { - device_id: mkdid(xev.deviceid), + device_id: mkdid(xev.deviceid as xinput::DeviceId), event: DeviceEvent::Button { button: xev.detail as u32, state: match xev.evtype { @@ -1122,7 +1134,7 @@ impl EventProcessor { // Set the timestamp. wt.xconn.set_timestamp(xev.time as xproto::Timestamp); - let did = mkdid(xev.deviceid); + let did = mkdid(xev.deviceid as xinput::DeviceId); let mask = unsafe { slice::from_raw_parts( @@ -1182,7 +1194,7 @@ impl EventProcessor { _ => unreachable!(), }; - let device_id = mkdid(xev.sourceid); + let device_id = mkdid(xev.sourceid as xinput::DeviceId); let keycode = xev.detail as u32; if keycode < KEYCODE_OFFSET as u32 { return; @@ -1208,19 +1220,19 @@ impl EventProcessor { unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } { if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid); + self.init_device(info.deviceid as xinput::DeviceId); callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), + device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Added, }); } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) { callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), + device_id: mkdid(info.deviceid as xinput::DeviceId), event: DeviceEvent::Removed, }); let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid)); + devices.remove(&DeviceId(info.deviceid as xinput::DeviceId)); } } } @@ -1229,7 +1241,7 @@ impl EventProcessor { } } _ => { - if event_type == self.xkbext.first_event_id { + if event_type == self.xkbext.first_event as _ { let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) }; match xev.xkb_type { ffi::XkbNewKeyboardNotify => { @@ -1285,11 +1297,14 @@ impl EventProcessor { _ => {} } } - if event_type == self.randr_event_offset { + if event_type == self.randr_event_offset as c_int { // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); + let prev_list = wt.xconn.invalidate_cached_monitor_list(); if let Some(prev_list) = prev_list { - let new_list = wt.xconn.available_monitors(); + let new_list = wt + .xconn + .available_monitors() + .expect("Failed to get monitor list"); for new_monitor in new_list { // Previous list may be empty, in case of disconnecting and // reconnecting the only one monitor. We still need to emit events in @@ -1419,7 +1434,7 @@ impl EventProcessor { ) where F: FnMut(Event), { - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD.into()); + let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); // Update modifiers state and emit key events based on which keys are currently pressed. for keycode in wt diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs index 8027e569..58abdb72 100644 --- a/src/platform_impl/linux/x11/ffi.rs +++ b/src/platform_impl/linux/x11/ffi.rs @@ -1,7 +1,6 @@ use x11_dl::xmd::CARD32; pub use x11_dl::{ error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, - xrandr::*, xrender::*, }; // Isn't defined by x11_dl diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 0689b37b..e67488aa 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -36,7 +36,7 @@ use std::{ }, ptr, rc::Rc, - slice, + slice, str, sync::mpsc::{Receiver, Sender, TryRecvError}, sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, @@ -47,11 +47,15 @@ use libc::{self, setlocale, LC_CTYPE}; use atoms::*; use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; -use x11rb::protocol::{ - xinput, - xproto::{self, ConnectionExt}, -}; use x11rb::x11_utils::X11Error as LogicalError; +use x11rb::{ + connection::RequestConnection, + protocol::{ + xinput::{self, ConnectionExt as _}, + xkb, + xproto::{self, ConnectionExt as _}, + }, +}; use x11rb::{ errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}, xcb_ffi::ReplyOrIdError, @@ -75,6 +79,10 @@ use crate::{ window::WindowAttributes, }; +// Xinput constants not defined in x11rb +const ALL_DEVICES: u16 = 0; +const ALL_MASTER_DEVICES: u16 = 1; + type X11Source = Generic; struct WakeSender { @@ -229,71 +237,27 @@ impl EventLoop { }); let randr_event_offset = xconn - .select_xrandr_input(root as ffi::Window) + .select_xrandr_input(root) .expect("Failed to query XRandR extension"); - let xi2ext = unsafe { - let mut ext = XExtension::default(); + let xi2ext = xconn + .xcb_connection() + .extension_information(xinput::X11_EXTENSION_NAME) + .expect("Failed to query XInput extension") + .expect("X server missing XInput extension"); + let xkbext = xconn + .xcb_connection() + .extension_information(xkb::X11_EXTENSION_NAME) + .expect("Failed to query XKB extension") + .expect("X server missing XKB extension"); - 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"); - } - - // Enable detectable auto repeat. - let mut supported = 0; - unsafe { - (xconn.xlib.XkbSetDetectableAutoRepeat)(xconn.display, 1, &mut supported); - } - if supported == 0 { - warn!("Detectable auto repeart is not supported"); - } - - 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 std::os::raw::c_int - { - panic!( - "X server has XInput extension {xinput_major_ver}.{xinput_minor_ver} but does not support XInput2", - ); - } - } + // Check for XInput2 support. + xconn + .xcb_connection() + .xinput_xi_query_version(2, 3) + .expect("Failed to send XInput2 query version request") + .reply() + .expect("Error while checking for XInput2 query version reply"); xconn.update_cached_wm_info(root); @@ -387,7 +351,7 @@ impl EventLoop { .xconn .select_xinput_events( root, - ffi::XIAllDevices as _, + ALL_DEVICES, x11rb::protocol::xinput::XIEventMask::HIERARCHY, ) .expect_then_ignore_error("Failed to register for XInput2 device hotplug events"); @@ -396,11 +360,11 @@ impl EventLoop { .xconn .select_xkb_events( 0x100, // Use the "core keyboard device" - ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask, + xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY, ) .unwrap(); - event_processor.init_device(ffi::XIAllDevices); + event_processor.init_device(ALL_DEVICES); EventLoop { loop_running: false, @@ -761,7 +725,7 @@ impl EventLoopWindowTarget { } self.xconn - .select_xinput_events(self.root, ffi::XIAllMasterDevices as _, mask) + .select_xinput_events(self.root, ALL_MASTER_DEVICES, mask) .expect_then_ignore_error("Failed to update device event filter"); } @@ -822,7 +786,7 @@ impl<'a> Deref for DeviceInfo<'a> { } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(c_int); +pub struct DeviceId(xinput::DeviceId); impl DeviceId { #[allow(unused)] @@ -894,6 +858,9 @@ pub enum X11Error { /// Got an invalid activation token. InvalidActivationToken(Vec), + /// An extension that we rely on is not available. + MissingExtension(&'static str), + /// Could not find a matching X11 visual for this visualid NoSuchVisual(xproto::Visualid), } @@ -912,6 +879,7 @@ impl fmt::Display for X11Error { "Invalid activation token: {}", std::str::from_utf8(s).unwrap_or("") ), + X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s), X11Error::NoSuchVisual(visualid) => { write!( f, @@ -1033,17 +1001,10 @@ impl<'a> Drop for GenericEventCookie<'a> { } } -#[derive(Debug, Default, Copy, Clone)] -struct XExtension { - opcode: c_int, - first_event_id: c_int, - first_error_id: c_int, -} - fn mkwid(w: xproto::Window) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) } -fn mkdid(w: c_int) -> crate::event::DeviceId { +fn mkdid(w: xinput::DeviceId) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 06716264..98adfa9e 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -1,29 +1,24 @@ -use std::os::raw::*; -use std::slice; -use std::sync::Mutex; - -use once_cell::sync::Lazy; - -use super::{ - ffi::{ - self, RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, - RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources, - }, - util, X11Error, XConnection, -}; +use super::{util, X11Error, XConnection}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, }; +use x11rb::{ + connection::RequestConnection, + protocol::{ + randr::{self, ConnectionExt as _}, + xproto, + }, +}; // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; -static MONITORS: Lazy>>> = Lazy::new(Mutex::default); - -pub fn invalidate_cached_monitor_list() -> Option> { - // We update this lazily. - (*MONITORS.lock().unwrap()).take() +impl XConnection { + pub fn invalidate_cached_monitor_list(&self) -> Option> { + // We update this lazily. + self.monitor_handles.lock().unwrap().take() + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -31,7 +26,7 @@ pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate_millihertz: u32, - pub(crate) native_mode: RRMode, + pub(crate) native_mode: randr::Mode, pub(crate) monitor: Option, } @@ -60,7 +55,7 @@ impl VideoMode { #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - pub(crate) id: RRCrtc, + pub(crate) id: randr::Crtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor @@ -106,10 +101,10 @@ impl std::hash::Hash for MonitorHandle { } #[inline] -pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option { - if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 { +pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option { + if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 { #[allow(clippy::unnecessary_cast)] - Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32) + Some((mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32) } else { None } @@ -118,19 +113,18 @@ pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option { impl MonitorHandle { fn new( xconn: &XConnection, - resources: *mut XRRScreenResources, - id: RRCrtc, - crtc: *mut XRRCrtcInfo, + resources: &ScreenResources, + id: randr::Crtc, + crtc: &randr::GetCrtcInfoReply, primary: bool, ) -> Option { - let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; - let dimensions = unsafe { ((*crtc).width, (*crtc).height) }; - let position = unsafe { ((*crtc).x, (*crtc).y) }; + let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?; + let dimensions = (crtc.width as u32, crtc.height as u32); + let position = (crtc.x as i32, crtc.y as i32); // Get the refresh rate of the current video mode. - let current_mode = unsafe { (*crtc).mode }; - let screen_modes = - unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) }; + let current_mode = crtc.mode; + let screen_modes = resources.modes(); let refresh_rate_millihertz = screen_modes .iter() .find(|mode| mode.id == current_mode) @@ -207,19 +201,22 @@ impl MonitorHandle { } impl XConnection { - pub fn get_monitor_for_window(&self, window_rect: Option) -> MonitorHandle { - let monitors = self.available_monitors(); + pub fn get_monitor_for_window( + &self, + window_rect: Option, + ) -> Result { + let monitors = self.available_monitors()?; if monitors.is_empty() { // Return a dummy monitor to avoid panicking - return MonitorHandle::dummy(); + return Ok(MonitorHandle::dummy()); } let default = monitors.get(0).unwrap(); let window_rect = match window_rect { Some(rect) => rect, - None => return default.to_owned(), + None => return Ok(default.to_owned()), }; let mut largest_overlap = 0; @@ -232,110 +229,153 @@ impl XConnection { } } - matched_monitor.to_owned() + Ok(matched_monitor.to_owned()) } - fn query_monitor_list(&self) -> Vec { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + fn query_monitor_list(&self) -> Result, X11Error> { + let root = self.default_root(); + let resources = ScreenResources::from_connection(self.xcb_connection(), root)?; - let root = self.default_root().root; - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window) - } else { - // WARNING: this function is supposedly very slow, on the order of hundreds of ms. - // Upon failure, `resources` will be null. - (self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window) - }; - - if resources.is_null() { - panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); - } - - let mut has_primary = false; - - let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root as ffi::Window); - let mut available = Vec::with_capacity((*resources).ncrtc as usize); - - for crtc_index in 0..(*resources).ncrtc { - let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; - if is_active { - let is_primary = *(*crtc).outputs.offset(0) == primary; - has_primary |= is_primary; - if let Some(monitor_id) = - MonitorHandle::new(self, resources, crtc_id, crtc, is_primary) - { - available.push(monitor_id) - } - } - (self.xrandr.XRRFreeCrtcInfo)(crtc); - } - - // If no monitors were detected as being primary, we just pick one ourselves! - if !has_primary { - if let Some(ref mut fallback) = available.first_mut() { - // Setting this here will come in handy if we ever add an `is_primary` method. - fallback.primary = true; - } - } - - (self.xrandr.XRRFreeScreenResources)(resources); - available + // Pipeline all of the get-crtc requests. + let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len()); + for &crtc in resources.crtcs() { + crtc_cookies.push( + self.xcb_connection() + .randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?, + ); } + + // Do this here so we do all of our requests in one shot. + let primary = self + .xcb_connection() + .randr_get_output_primary(root.root)? + .reply()? + .output; + + let mut crtc_infos = Vec::with_capacity(crtc_cookies.len()); + for cookie in crtc_cookies { + let reply = cookie.reply()?; + crtc_infos.push(reply); + } + + let mut has_primary = false; + let mut available_monitors = Vec::with_capacity(resources.crtcs().len()); + for (crtc_id, crtc) in resources.crtcs().iter().zip(crtc_infos.iter()) { + if crtc.width == 0 || crtc.height == 0 || crtc.outputs.is_empty() { + continue; + } + + let is_primary = crtc.outputs[0] == primary; + has_primary |= is_primary; + let monitor = MonitorHandle::new(self, &resources, *crtc_id, crtc, is_primary); + available_monitors.extend(monitor); + } + + // If we don't have a primary monitor, just pick one ourselves! + if !has_primary { + if let Some(ref mut fallback) = available_monitors.first_mut() { + // Setting this here will come in handy if we ever add an `is_primary` method. + fallback.primary = true; + } + } + + Ok(available_monitors) } - pub fn available_monitors(&self) -> Vec { - let mut monitors_lock = MONITORS.lock().unwrap(); + pub fn available_monitors(&self) -> Result, X11Error> { + let mut monitors_lock = self.monitor_handles.lock().unwrap(); (*monitors_lock) .as_ref() .cloned() + .map(Ok) .or_else(|| { - let monitors = Some(self.query_monitor_list()); - if !DISABLE_MONITOR_LIST_CACHING { - (*monitors_lock) = monitors.clone(); - } - monitors + self.query_monitor_list() + .map(|mon_list| { + let monitors = Some(mon_list); + if !DISABLE_MONITOR_LIST_CACHING { + (*monitors_lock) = monitors.clone(); + } + monitors + }) + .transpose() }) .unwrap() } #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - self.available_monitors() + pub fn primary_monitor(&self) -> Result { + Ok(self + .available_monitors()? .into_iter() .find(|monitor| monitor.primary) - .unwrap_or_else(MonitorHandle::dummy) + .unwrap_or_else(MonitorHandle::dummy)) } - pub fn select_xrandr_input(&self, root: Window) -> Result { - let has_xrandr = unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) - }; - assert!( - has_xrandr == True, - "[winit] XRandR extension not available." - ); + pub fn select_xrandr_input(&self, root: xproto::Window) -> Result { + use randr::NotifyMask; - let mut event_offset = 0; - let mut error_offset = 0; - let status = unsafe { - (self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset) - }; + // Get extension info. + let info = self + .xcb_connection() + .extension_information(randr::X11_EXTENSION_NAME)? + .ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?; - if status != True { - self.check_errors()?; - unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); - } + // Select input data. + let event_mask = + NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE; + self.xcb_connection().randr_select_input(root, event_mask)?; - let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask; - unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) }; - - Ok(event_offset) + Ok(info.first_event) + } +} + +pub(crate) struct ScreenResources { + /// List of attached modes. + modes: Vec, + + /// List of attached CRTCs. + crtcs: Vec, +} + +impl ScreenResources { + pub(crate) fn modes(&self) -> &[randr::ModeInfo] { + &self.modes + } + + pub(crate) fn crtcs(&self) -> &[randr::Crtc] { + &self.crtcs + } + + pub(crate) fn from_connection( + conn: &impl x11rb::connection::Connection, + root: &x11rb::protocol::xproto::Screen, + ) -> Result { + let version = conn.randr_query_version(0, 0)?.reply()?; + + if (version.major_version == 1 && version.minor_version >= 3) || version.major_version > 1 { + let reply = conn + .randr_get_screen_resources_current(root.root)? + .reply()?; + Ok(Self::from_get_screen_resources_current_reply(reply)) + } else { + let reply = conn.randr_get_screen_resources(root.root)?.reply()?; + Ok(Self::from_get_screen_resources_reply(reply)) + } + } + + pub(crate) fn from_get_screen_resources_reply(reply: randr::GetScreenResourcesReply) -> Self { + Self { + modes: reply.modes, + crtcs: reply.crtcs, + } + } + + pub(crate) fn from_get_screen_resources_current_reply( + reply: randr::GetScreenResourcesCurrentReply, + ) -> Self { + Self { + modes: reply.modes, + crtcs: reply.crtcs, + } } } diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 08e69f8d..3124eeda 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,5 +1,9 @@ use std::{slice, str}; -use x11rb::protocol::xinput::{self, ConnectionExt as _}; +use x11rb::errors::{ConnectionError, ReplyError}; +use x11rb::protocol::{ + xinput::{self, ConnectionExt as _}, + xkb::{self, ConnectionExt as _}, +}; use super::*; @@ -11,31 +15,6 @@ pub const VIRTUAL_CORE_KEYBOARD: u16 = 3; // To test if `lookup_utf8` works correctly, set this to 1. const TEXT_BUFFER_SIZE: usize = 1024; -// NOTE: Some of these fields are not used, but may be of use in the future. -pub struct PointerState<'a> { - xconn: &'a XConnection, - pub root: xproto::Window, - pub child: xproto::Window, - pub root_x: c_double, - pub root_y: c_double, - pub win_x: c_double, - pub win_y: c_double, - buttons: ffi::XIButtonState, - pub group: ffi::XIGroupState, - pub relative_to_window: bool, -} - -impl<'a> Drop for PointerState<'a> { - fn drop(&mut self) { - if !self.buttons.mask.is_null() { - unsafe { - // This is why you need to read the docs carefully... - (self.xconn.xlib.XFree)(self.buttons.mask as _); - } - } - } -} - impl XConnection { pub fn select_xinput_events( &self, @@ -54,16 +33,36 @@ impl XConnection { .map_err(Into::into) } - pub fn select_xkb_events(&self, device_id: c_int, mask: c_ulong) -> Result { - let status = - unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) }; + pub fn select_xkb_events( + &self, + device_id: xkb::DeviceSpec, + mask: xkb::EventType, + ) -> Result { + let result = self + .xcb_connection() + .xkb_select_events( + device_id, + xkb::EventType::from(0u8), + mask, + xkb::MapPart::from(u16::from(mask)), + xkb::MapPart::EXPLICIT_COMPONENTS + | xkb::MapPart::KEY_ACTIONS + | xkb::MapPart::KEY_BEHAVIORS + | xkb::MapPart::VIRTUAL_MODS + | xkb::MapPart::MODIFIER_MAP + | xkb::MapPart::VIRTUAL_MOD_MAP, + &xkb::SelectEventsAux::new(), + ) + .map_err(ReplyError::from) + .and_then(|x| x.check()); - if status == ffi::True { - self.flush_requests()?; - Ok(true) - } else { - error!("Could not select XKB events: The XKB extension is not initialized!"); - Ok(false) + match result { + Ok(()) => Ok(true), + Err(ReplyError::ConnectionError(ConnectionError::UnsupportedExtension)) => { + error!("Could not select XKB events: The XKB extension is not initialized!"); + Ok(false) + } + Err(e) => Err(e.into()), } } diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 765c9a00..564d5dd5 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,12 +1,11 @@ -use std::{env, slice, str::FromStr}; +use std::{env, str, str::FromStr}; -use super::{ - ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, - *, -}; +use super::*; use crate::platform_impl::platform::x11::monitor; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; +use x11rb::protocol::randr::{self, ConnectionExt as _}; + /// Represents values of `WINIT_HIDPI_FACTOR`. pub enum EnvVarDPI { Randr, @@ -37,42 +36,32 @@ pub fn calc_dpi_factor( impl XConnection { // Retrieve DPI from Xft.dpi property - pub unsafe fn get_xft_dpi(&self) -> Option { - (self.xlib.XrmInitialize)(); - let resource_manager_str = (self.xlib.XResourceManagerString)(self.display); - if resource_manager_str.is_null() { - return None; - } - if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() { - let name: &str = "Xft.dpi:\t"; - for pair in res.split('\n') { - if let Some(stripped) = pair.strip_prefix(name) { - return f64::from_str(stripped).ok(); - } - } - } - None + pub fn get_xft_dpi(&self) -> Option { + self.database() + .get_string("Xfi.dpi", "") + .and_then(|s| f64::from_str(s).ok()) } - pub unsafe fn get_output_info( + pub fn get_output_info( &self, - resources: *mut XRRScreenResources, - crtc: *mut XRRCrtcInfo, + resources: &monitor::ScreenResources, + crtc: &randr::GetCrtcInfoReply, ) -> Option<(String, f64, Vec)> { - let output_info = - (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0)); - if output_info.is_null() { - // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) - // it's possible for it to return null. - // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596 - let _ = self.check_errors(); // discard `BadRROutput` error - return None; - } + let output_info = match self + .xcb_connection() + .randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME) + .map_err(X11Error::from) + .and_then(|r| r.reply().map_err(X11Error::from)) + { + Ok(output_info) => output_info, + Err(err) => { + warn!("Failed to get output info: {:?}", err); + return None; + } + }; let bit_depth = self.default_root().root_depth; - - let output_modes = - slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize); - let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize); + let output_modes = &output_info.modes; + let resource_modes = resources.modes(); let modes = resource_modes .iter() @@ -81,7 +70,7 @@ impl XConnection { .filter(|x| output_modes.iter().any(|id| x.id == *id)) .map(|mode| { VideoMode { - size: (mode.width, mode.height), + size: (mode.width.into(), mode.height.into()), refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) .unwrap_or(0), bit_depth: bit_depth as u16, @@ -93,11 +82,13 @@ impl XConnection { }) .collect(); - let name_slice = slice::from_raw_parts( - (*output_info).name as *mut u8, - (*output_info).nameLen as usize, - ); - let name = String::from_utf8_lossy(name_slice).into(); + let name = match str::from_utf8(&output_info.name) { + Ok(name) => name.to_owned(), + Err(err) => { + warn!("Failed to get output name: {:?}", err); + return None; + } + }; // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); if deprecated_dpi_override.is_some() { @@ -124,8 +115,8 @@ impl XConnection { let scale_factor = match dpi_env { EnvVarDPI::Randr => calc_dpi_factor( - ((*crtc).width, (*crtc).height), - ((*output_info).mm_width as _, (*output_info).mm_height as _), + (crtc.width.into(), crtc.height.into()), + (output_info.mm_width as _, output_info.mm_height as _), ), EnvVarDPI::Scale(dpi_override) => { if !validate_scale_factor(dpi_override) { @@ -140,74 +131,47 @@ impl XConnection { dpi / 96. } else { calc_dpi_factor( - ((*crtc).width, (*crtc).height), - ((*output_info).mm_width as _, (*output_info).mm_height as _), + (crtc.width.into(), crtc.height.into()), + (output_info.mm_width as _, output_info.mm_height as _), ) } } }; - (self.xrandr.XRRFreeOutputInfo)(output_info); Some((name, scale_factor, modes)) } - #[must_use] - pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + pub fn set_crtc_config( + &self, + crtc_id: randr::Crtc, + mode_id: randr::Mode, + ) -> Result<(), X11Error> { + let crtc = self + .xcb_connection() + .randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)? + .reply()?; - let root = self.default_root().root; - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window) - }; - - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let status = (self.xrandr.XRRSetCrtcConfig)( - self.display, - resources, + self.xcb_connection() + .randr_set_crtc_config( crtc_id, - CurrentTime, - (*crtc).x, - (*crtc).y, + crtc.timestamp, + x11rb::CURRENT_TIME, + crtc.x, + crtc.y, mode_id, - (*crtc).rotation, - (*crtc).outputs.offset(0), - 1, - ); - - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); - - if status == Success as i32 { - Some(()) - } else { - None - } - } + crtc.rotation, + &crtc.outputs, + )? + .reply() + .map(|_| ()) + .map_err(Into::into) } - pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - - let root = self.default_root().root; - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root as ffi::Window) - }; - - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let mode = (*crtc).mode; - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); - mode - } + pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result { + Ok(self + .xcb_connection() + .randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)? + .reply()? + .mode) } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 65e51eb1..916aad01 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -10,12 +10,11 @@ use std::{ use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use x11rb::{ connection::Connection, - properties::WmHintsState, - protocol::xproto::{self, ConnectionExt as _}, -}; -use x11rb::{ - properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}, - protocol::xinput, + properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification}, + protocol::{ + randr, xinput, + xproto::{self, ConnectionExt as _}, + }, }; use crate::{ @@ -55,7 +54,7 @@ pub struct SharedState { // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, // Used to restore video mode after exiting fullscreen - pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>, + pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>, pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, @@ -151,7 +150,7 @@ impl UnownedWindow { None => event_loop.root, }; - let mut monitors = xconn.available_monitors(); + let mut monitors = leap!(xconn.available_monitors()); let guessed_monitor = if monitors.is_empty() { X11MonitorHandle::dummy() } else { @@ -524,7 +523,7 @@ impl UnownedWindow { | xinput::XIEventMask::TOUCH_BEGIN | xinput::XIEventMask::TOUCH_UPDATE | xinput::XIEventMask::TOUCH_END; - leap!(xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices as u16, mask)) + leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) .ignore_error(); { @@ -740,8 +739,12 @@ impl UnownedWindow { &Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode))), ) => { let monitor = video_mode.monitor.as_ref().unwrap(); - shared_state_lock.desktop_video_mode = - Some((monitor.id, self.xconn.get_crtc_mode(monitor.id))); + shared_state_lock.desktop_video_mode = Some(( + monitor.id, + self.xconn + .get_crtc_mode(monitor.id) + .expect("Failed to get desktop video mode"), + )); } // Restore desktop video mode upon exiting exclusive fullscreen (&Some(Fullscreen::Exclusive(_)), &None) @@ -877,11 +880,15 @@ impl UnownedWindow { } pub fn available_monitors(&self) -> Vec { - self.xconn.available_monitors() + self.xconn + .available_monitors() + .expect("Failed to get available monitors") } pub fn primary_monitor(&self) -> X11MonitorHandle { - self.xconn.primary_monitor() + self.xconn + .primary_monitor() + .expect("Failed to get primary monitor") } #[inline] diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 7535daba..c47f09fe 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -10,17 +10,20 @@ use std::{ use crate::window::CursorIcon; -use super::{atoms::Atoms, ffi}; -use x11rb::{connection::Connection, protocol::xproto, xcb_ffi::XCBConnection}; +use super::{atoms::Atoms, ffi, monitor::MonitorHandle}; +use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection}; /// A connection to an X server. pub(crate) struct XConnection { pub xlib: ffi::Xlib, - /// Exposes XRandR functions from version < 1.5 - pub xrandr: ffi::Xrandr_2_2_0, pub xcursor: ffi::Xcursor, + + // TODO(notgull): 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. /// /// The `Option` ensures that we can drop it before we close the `Display`. @@ -38,6 +41,12 @@ pub(crate) struct XConnection { /// The last timestamp received by this connection. timestamp: AtomicU32, + /// List of monitor handles. + pub monitor_handles: Mutex>>, + + /// The resource database. + database: resource_manager::Database, + pub latest_error: Mutex>, pub cursor_cache: Mutex, ffi::Cursor>>, } @@ -53,9 +62,8 @@ impl XConnection { // opening the libraries let xlib = ffi::Xlib::open()?; let xcursor = ffi::Xcursor::open()?; - let xrandr = ffi::Xrandr_2_2_0::open()?; - let xinput2 = ffi::XInput2::open()?; let xlib_xcb = ffi::Xlib_xcb::open()?; + let xinput2 = ffi::XInput2::open()?; unsafe { (xlib.XInitThreads)() }; unsafe { (xlib.XSetErrorHandler)(error_handler) }; @@ -92,9 +100,12 @@ impl XConnection { .reply() .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; + // Load the database. + let database = resource_manager::new_from_default(&xcb) + .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; + Ok(XConnection { xlib, - xrandr, xcursor, xinput2, display, @@ -103,6 +114,8 @@ impl XConnection { default_screen, timestamp: AtomicU32::new(0), latest_error: Mutex::new(None), + monitor_handles: Mutex::new(None), + database, cursor_cache: Default::default(), }) } @@ -144,6 +157,12 @@ impl XConnection { &self.xcb_connection().setup().roots[self.default_screen] } + /// Get the resource database. + #[inline] + pub fn database(&self) -> &resource_manager::Database { + &self.database + } + /// Get the latest timestamp. #[inline] pub fn timestamp(&self) -> u32 {