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 <dev@notgull.net>
This commit is contained in:
John Nunley 2023-08-29 14:01:25 -07:00 committed by GitHub
parent 0c8cf94a70
commit bb9b629bc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 399 additions and 394 deletions

View file

@ -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 }

View file

@ -851,6 +851,7 @@ impl<T> EventLoopWindowTarget<T> {
.x_connection()
.available_monitors()
.into_iter()
.flatten()
.map(MonitorHandle::X)
.collect(),
}
@ -863,7 +864,7 @@ impl<T> EventLoopWindowTarget<T> {
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)
}
}

View file

@ -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<T: 'static> {
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<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension,
pub(super) xkbext: XExtension,
pub(super) xi2ext: ExtensionInformation,
pub(super) xkbext: ExtensionInformation,
pub(super) target: Rc<RootELW<T>>,
pub(super) kb_state: KbdState,
// Number of touch events currently in progress
@ -55,12 +60,12 @@ pub(super) struct EventProcessor<T: 'static> {
}
impl<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
};
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
// 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<T: 'static> EventProcessor<T> {
)
};
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
// 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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
// 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<T: 'static> EventProcessor<T> {
_ => 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<T: 'static> EventProcessor<T> {
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<T: 'static> EventProcessor<T> {
}
}
_ => {
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<T: 'static> EventProcessor<T> {
_ => {}
}
}
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<T: 'static> EventProcessor<T> {
) where
F: FnMut(Event<T>),
{
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

View file

@ -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

View file

@ -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<RawFd>;
struct WakeSender<T> {
@ -229,71 +237,27 @@ impl<T: 'static> EventLoop<T> {
});
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<T: 'static> EventLoop<T> {
.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<T: 'static> EventLoop<T> {
.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<T> EventLoopWindowTarget<T> {
}
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<u8>),
/// 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("<invalid utf8>")
),
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)))
}

View file

@ -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<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default);
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
// We update this lazily.
(*MONITORS.lock().unwrap()).take()
impl XConnection {
pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
// 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<MonitorHandle>,
}
@ -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<u32> {
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option<u32> {
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<u32> {
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<Self> {
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<util::AaRect>) -> MonitorHandle {
let monitors = self.available_monitors();
pub fn get_monitor_for_window(
&self,
window_rect: Option<util::AaRect>,
) -> Result<MonitorHandle, X11Error> {
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<MonitorHandle> {
unsafe {
let mut major = 0;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, 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<MonitorHandle> {
let mut monitors_lock = MONITORS.lock().unwrap();
pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, 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<MonitorHandle, X11Error> {
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<c_int, X11Error> {
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<u8, X11Error> {
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<randr::ModeInfo>,
/// List of attached CRTCs.
crtcs: Vec<randr::Crtc>,
}
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<Self, X11Error> {
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,
}
}
}

View file

@ -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<bool, X11Error> {
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<bool, X11Error> {
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()),
}
}

View file

@ -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<f64> {
(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<f64> {
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<VideoMode>)> {
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<randr::Mode, X11Error> {
Ok(self
.xcb_connection()
.randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?
.reply()?
.mode)
}
}

View file

@ -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<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>,
@ -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<X11MonitorHandle> {
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]

View file

@ -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<Option<Vec<MonitorHandle>>>,
/// The resource database.
database: resource_manager::Database,
pub latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, 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 {