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" calloop = "0.10.5"
rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] } rustix = { version = "0.38.4", default-features = false, features = ["std", "system", "thread", "process"] }
x11-dl = { version = "2.18.5", optional = true } 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" xkbcommon-dl = "0.4.0"
memmap2 = { version = "0.5.0", optional = true } memmap2 = { version = "0.5.0", optional = true }

View file

@ -851,6 +851,7 @@ impl<T> EventLoopWindowTarget<T> {
.x_connection() .x_connection()
.available_monitors() .available_monitors()
.into_iter() .into_iter()
.flatten()
.map(MonitorHandle::X) .map(MonitorHandle::X)
.collect(), .collect(),
} }
@ -863,7 +864,7 @@ impl<T> EventLoopWindowTarget<T> {
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(), EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
#[cfg(x11_platform)] #[cfg(x11_platform)]
EventLoopWindowTarget::X(ref evlp) => { 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) Some(primary_monitor)
} }
} }

View file

@ -7,13 +7,18 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::x11_utils::Serialize; use x11rb::x11_utils::Serialize;
use x11rb::{
protocol::{
xinput,
xproto::{self, ConnectionExt as _},
},
x11_utils::ExtensionInformation,
};
use super::{ use super::{
atoms::*, ffi, get_xtarget, mkdid, mkwid, monitor, util, CookieResultExt, Device, DeviceId, atoms::*, ffi, get_xtarget, mkdid, mkwid, util, CookieResultExt, Device, DeviceId, DeviceInfo,
DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
WindowId, XExtension,
}; };
use crate::{ use crate::{
@ -35,10 +40,10 @@ pub(super) struct EventProcessor<T: 'static> {
pub(super) dnd: Dnd, pub(super) dnd: Dnd,
pub(super) ime_receiver: ImeReceiver, pub(super) ime_receiver: ImeReceiver,
pub(super) ime_event_receiver: ImeEventReceiver, 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) devices: RefCell<HashMap<DeviceId, Device>>,
pub(super) xi2ext: XExtension, pub(super) xi2ext: ExtensionInformation,
pub(super) xkbext: XExtension, pub(super) xkbext: ExtensionInformation,
pub(super) target: Rc<RootELW<T>>, pub(super) target: Rc<RootELW<T>>,
pub(super) kb_state: KbdState, pub(super) kb_state: KbdState,
// Number of touch events currently in progress // Number of touch events currently in progress
@ -55,12 +60,12 @@ pub(super) struct EventProcessor<T: 'static> {
} }
impl<T: 'static> EventProcessor<T> { 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 wt = get_xtarget(&self.target);
let mut devices = self.devices.borrow_mut(); 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() { 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 last_scale_factor = shared_state_lock.last_monitor.scale_factor;
let new_scale_factor = { let new_scale_factor = {
let window_rect = util::AaRect::new(new_outer_position, new_inner_size); 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() { if monitor.is_dummy() {
// Avoid updating monitor using a dummy monitor handle // Avoid updating monitor using a dummy monitor handle
@ -596,7 +604,7 @@ impl<T: 'static> EventProcessor<T> {
}; };
let window_id = mkwid(window); 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 _; let keycode = xkev.keycode as _;
@ -672,7 +680,7 @@ impl<T: 'static> EventProcessor<T> {
return; return;
}; };
let xev = &guard.cookie; let xev = &guard.cookie;
if self.xi2ext.opcode != xev.extension { if self.xi2ext.major_opcode != xev.extension as u8 {
return; return;
} }
@ -691,7 +699,7 @@ impl<T: 'static> EventProcessor<T> {
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event as xproto::Window); 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. // Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::Timestamp); wt.xconn.set_timestamp(xev.time as xproto::Timestamp);
@ -787,7 +795,7 @@ impl<T: 'static> EventProcessor<T> {
// Set the timestamp. // Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::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 = xev.event as xproto::Window;
let window_id = mkwid(window); let window_id = mkwid(window);
let new_cursor_pos = (xev.event_x, xev.event_y); 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 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, Some(device) => device,
None => return, None => return,
}; };
@ -832,7 +842,7 @@ impl<T: 'static> EventProcessor<T> {
if let Some(&mut (_, ref mut info)) = physical_device if let Some(&mut (_, ref mut info)) = physical_device
.scroll_axes .scroll_axes
.iter_mut() .iter_mut()
.find(|&&mut (axis, _)| axis == i) .find(|&&mut (axis, _)| axis == i as _)
{ {
let delta = (x - info.position) / info.increment; let delta = (x - info.position) / info.increment;
info.position = x; info.position = x;
@ -879,9 +889,11 @@ impl<T: 'static> EventProcessor<T> {
let window = xev.event as xproto::Window; let window = xev.event as xproto::Window;
let window_id = mkwid(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(); let mut devices = self.devices.borrow_mut();
for device_info in all_info.iter() { for device_info in all_info.iter() {
if device_info.deviceid == xev.sourceid if device_info.deviceid == xev.sourceid
@ -891,7 +903,7 @@ impl<T: 'static> EventProcessor<T> {
// the virtual device. // the virtual device.
|| device_info.attachment == xev.sourceid || 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) { if let Some(device) = devices.get_mut(&device_id) {
device.reset_scroll_position(device_info); device.reset_scroll_position(device_info);
} }
@ -930,7 +942,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id: mkwid(window), window_id: mkwid(window),
event: CursorLeft { 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 let pointer_id = self
.devices .devices
.borrow() .borrow()
.get(&DeviceId(xev.deviceid)) .get(&DeviceId(xev.deviceid as xinput::DeviceId))
.map(|device| device.attachment) .map(|device| device.attachment)
.unwrap_or(2); .unwrap_or(2);
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id, window_id,
event: CursorMoved { event: CursorMoved {
device_id: mkdid(pointer_id), device_id: mkdid(pointer_id as _),
position, position,
}, },
}); });
@ -1076,7 +1088,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id, window_id,
event: WindowEvent::CursorMoved { event: WindowEvent::CursorMoved {
device_id: mkdid(util::VIRTUAL_CORE_POINTER.into()), device_id: mkdid(util::VIRTUAL_CORE_POINTER),
position: location.cast(), position: location.cast(),
}, },
}); });
@ -1085,7 +1097,7 @@ impl<T: 'static> EventProcessor<T> {
callback(Event::WindowEvent { callback(Event::WindowEvent {
window_id, window_id,
event: WindowEvent::Touch(Touch { event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid), device_id: mkdid(xev.deviceid as xinput::DeviceId),
phase, phase,
location, location,
force: None, // TODO force: None, // TODO
@ -1103,7 +1115,7 @@ impl<T: 'static> EventProcessor<T> {
if xev.flags & ffi::XIPointerEmulated == 0 { if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::DeviceEvent { callback(Event::DeviceEvent {
device_id: mkdid(xev.deviceid), device_id: mkdid(xev.deviceid as xinput::DeviceId),
event: DeviceEvent::Button { event: DeviceEvent::Button {
button: xev.detail as u32, button: xev.detail as u32,
state: match xev.evtype { state: match xev.evtype {
@ -1122,7 +1134,7 @@ impl<T: 'static> EventProcessor<T> {
// Set the timestamp. // Set the timestamp.
wt.xconn.set_timestamp(xev.time as xproto::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 { let mask = unsafe {
slice::from_raw_parts( slice::from_raw_parts(
@ -1182,7 +1194,7 @@ impl<T: 'static> EventProcessor<T> {
_ => unreachable!(), _ => unreachable!(),
}; };
let device_id = mkdid(xev.sourceid); let device_id = mkdid(xev.sourceid as xinput::DeviceId);
let keycode = xev.detail as u32; let keycode = xev.detail as u32;
if keycode < KEYCODE_OFFSET as u32 { if keycode < KEYCODE_OFFSET as u32 {
return; return;
@ -1208,19 +1220,19 @@ impl<T: 'static> EventProcessor<T> {
unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }
{ {
if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) {
self.init_device(info.deviceid); self.init_device(info.deviceid as xinput::DeviceId);
callback(Event::DeviceEvent { callback(Event::DeviceEvent {
device_id: mkdid(info.deviceid), device_id: mkdid(info.deviceid as xinput::DeviceId),
event: DeviceEvent::Added, event: DeviceEvent::Added,
}); });
} else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved)
{ {
callback(Event::DeviceEvent { callback(Event::DeviceEvent {
device_id: mkdid(info.deviceid), device_id: mkdid(info.deviceid as xinput::DeviceId),
event: DeviceEvent::Removed, event: DeviceEvent::Removed,
}); });
let mut devices = self.devices.borrow_mut(); 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) }; let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) };
match xev.xkb_type { match xev.xkb_type {
ffi::XkbNewKeyboardNotify => { 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. // 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 { 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 { for new_monitor in new_list {
// Previous list may be empty, in case of disconnecting and // Previous list may be empty, in case of disconnecting and
// reconnecting the only one monitor. We still need to emit events in // reconnecting the only one monitor. We still need to emit events in
@ -1419,7 +1434,7 @@ impl<T: 'static> EventProcessor<T> {
) where ) where
F: FnMut(Event<T>), 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. // Update modifiers state and emit key events based on which keys are currently pressed.
for keycode in wt for keycode in wt

View file

@ -1,7 +1,6 @@
use x11_dl::xmd::CARD32; use x11_dl::xmd::CARD32;
pub use x11_dl::{ pub use x11_dl::{
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
xrandr::*, xrender::*,
}; };
// Isn't defined by x11_dl // Isn't defined by x11_dl

View file

@ -36,7 +36,7 @@ use std::{
}, },
ptr, ptr,
rc::Rc, rc::Rc,
slice, slice, str,
sync::mpsc::{Receiver, Sender, TryRecvError}, sync::mpsc::{Receiver, Sender, TryRecvError},
sync::{mpsc, Arc, Weak}, sync::{mpsc, Arc, Weak},
time::{Duration, Instant}, time::{Duration, Instant},
@ -47,11 +47,15 @@ use libc::{self, setlocale, LC_CTYPE};
use atoms::*; use atoms::*;
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle}; use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
use x11rb::protocol::{
xinput,
xproto::{self, ConnectionExt},
};
use x11rb::x11_utils::X11Error as LogicalError; use x11rb::x11_utils::X11Error as LogicalError;
use x11rb::{
connection::RequestConnection,
protocol::{
xinput::{self, ConnectionExt as _},
xkb,
xproto::{self, ConnectionExt as _},
},
};
use x11rb::{ use x11rb::{
errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}, errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError},
xcb_ffi::ReplyOrIdError, xcb_ffi::ReplyOrIdError,
@ -75,6 +79,10 @@ use crate::{
window::WindowAttributes, window::WindowAttributes,
}; };
// Xinput constants not defined in x11rb
const ALL_DEVICES: u16 = 0;
const ALL_MASTER_DEVICES: u16 = 1;
type X11Source = Generic<RawFd>; type X11Source = Generic<RawFd>;
struct WakeSender<T> { struct WakeSender<T> {
@ -229,71 +237,27 @@ impl<T: 'static> EventLoop<T> {
}); });
let randr_event_offset = xconn let randr_event_offset = xconn
.select_xrandr_input(root as ffi::Window) .select_xrandr_input(root)
.expect("Failed to query XRandR extension"); .expect("Failed to query XRandR extension");
let xi2ext = unsafe { let xi2ext = xconn
let mut ext = XExtension::default(); .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)( // Check for XInput2 support.
xconn.display, xconn
b"XInputExtension\0".as_ptr() as *const c_char, .xcb_connection()
&mut ext.opcode, .xinput_xi_query_version(2, 3)
&mut ext.first_event_id, .expect("Failed to send XInput2 query version request")
&mut ext.first_error_id, .reply()
); .expect("Error while checking for XInput2 query version reply");
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",
);
}
}
xconn.update_cached_wm_info(root); xconn.update_cached_wm_info(root);
@ -387,7 +351,7 @@ impl<T: 'static> EventLoop<T> {
.xconn .xconn
.select_xinput_events( .select_xinput_events(
root, root,
ffi::XIAllDevices as _, ALL_DEVICES,
x11rb::protocol::xinput::XIEventMask::HIERARCHY, x11rb::protocol::xinput::XIEventMask::HIERARCHY,
) )
.expect_then_ignore_error("Failed to register for XInput2 device hotplug events"); .expect_then_ignore_error("Failed to register for XInput2 device hotplug events");
@ -396,11 +360,11 @@ impl<T: 'static> EventLoop<T> {
.xconn .xconn
.select_xkb_events( .select_xkb_events(
0x100, // Use the "core keyboard device" 0x100, // Use the "core keyboard device"
ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask, xkb::EventType::NEW_KEYBOARD_NOTIFY | xkb::EventType::STATE_NOTIFY,
) )
.unwrap(); .unwrap();
event_processor.init_device(ffi::XIAllDevices); event_processor.init_device(ALL_DEVICES);
EventLoop { EventLoop {
loop_running: false, loop_running: false,
@ -761,7 +725,7 @@ impl<T> EventLoopWindowTarget<T> {
} }
self.xconn 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"); .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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int); pub struct DeviceId(xinput::DeviceId);
impl DeviceId { impl DeviceId {
#[allow(unused)] #[allow(unused)]
@ -894,6 +858,9 @@ pub enum X11Error {
/// Got an invalid activation token. /// Got an invalid activation token.
InvalidActivationToken(Vec<u8>), 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 /// Could not find a matching X11 visual for this visualid
NoSuchVisual(xproto::Visualid), NoSuchVisual(xproto::Visualid),
} }
@ -912,6 +879,7 @@ impl fmt::Display for X11Error {
"Invalid activation token: {}", "Invalid activation token: {}",
std::str::from_utf8(s).unwrap_or("<invalid utf8>") std::str::from_utf8(s).unwrap_or("<invalid utf8>")
), ),
X11Error::MissingExtension(s) => write!(f, "Missing X11 extension: {}", s),
X11Error::NoSuchVisual(visualid) => { X11Error::NoSuchVisual(visualid) => {
write!( write!(
f, 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 { fn mkwid(w: xproto::Window) -> crate::window::WindowId {
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as _)) 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))) crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
} }

View file

@ -1,29 +1,24 @@
use std::os::raw::*; use super::{util, X11Error, XConnection};
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 crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, 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. // Used for testing. This should always be committed as false.
const DISABLE_MONITOR_LIST_CACHING: bool = false; const DISABLE_MONITOR_LIST_CACHING: bool = false;
static MONITORS: Lazy<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default); impl XConnection {
pub fn invalidate_cached_monitor_list(&self) -> Option<Vec<MonitorHandle>> {
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> { // We update this lazily.
// We update this lazily. self.monitor_handles.lock().unwrap().take()
(*MONITORS.lock().unwrap()).take() }
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -31,7 +26,7 @@ pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32, pub(crate) refresh_rate_millihertz: u32,
pub(crate) native_mode: RRMode, pub(crate) native_mode: randr::Mode,
pub(crate) monitor: Option<MonitorHandle>, pub(crate) monitor: Option<MonitorHandle>,
} }
@ -60,7 +55,7 @@ impl VideoMode {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MonitorHandle { pub struct MonitorHandle {
/// The actual id /// The actual id
pub(crate) id: RRCrtc, pub(crate) id: randr::Crtc,
/// The name of the monitor /// The name of the monitor
pub(crate) name: String, pub(crate) name: String,
/// The size of the monitor /// The size of the monitor
@ -106,10 +101,10 @@ impl std::hash::Hash for MonitorHandle {
} }
#[inline] #[inline]
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> { pub fn mode_refresh_rate_millihertz(mode: &randr::ModeInfo) -> Option<u32> {
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 { if mode.dot_clock > 0 && mode.htotal > 0 && mode.vtotal > 0 {
#[allow(clippy::unnecessary_cast)] #[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 { } else {
None None
} }
@ -118,19 +113,18 @@ pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
impl MonitorHandle { impl MonitorHandle {
fn new( fn new(
xconn: &XConnection, xconn: &XConnection,
resources: *mut XRRScreenResources, resources: &ScreenResources,
id: RRCrtc, id: randr::Crtc,
crtc: *mut XRRCrtcInfo, crtc: &randr::GetCrtcInfoReply,
primary: bool, primary: bool,
) -> Option<Self> { ) -> Option<Self> {
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; let (name, scale_factor, video_modes) = xconn.get_output_info(resources, crtc)?;
let dimensions = unsafe { ((*crtc).width, (*crtc).height) }; let dimensions = (crtc.width as u32, crtc.height as u32);
let position = unsafe { ((*crtc).x, (*crtc).y) }; let position = (crtc.x as i32, crtc.y as i32);
// Get the refresh rate of the current video mode. // Get the refresh rate of the current video mode.
let current_mode = unsafe { (*crtc).mode }; let current_mode = crtc.mode;
let screen_modes = let screen_modes = resources.modes();
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
let refresh_rate_millihertz = screen_modes let refresh_rate_millihertz = screen_modes
.iter() .iter()
.find(|mode| mode.id == current_mode) .find(|mode| mode.id == current_mode)
@ -207,19 +201,22 @@ impl MonitorHandle {
} }
impl XConnection { impl XConnection {
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle { pub fn get_monitor_for_window(
let monitors = self.available_monitors(); &self,
window_rect: Option<util::AaRect>,
) -> Result<MonitorHandle, X11Error> {
let monitors = self.available_monitors()?;
if monitors.is_empty() { if monitors.is_empty() {
// Return a dummy monitor to avoid panicking // Return a dummy monitor to avoid panicking
return MonitorHandle::dummy(); return Ok(MonitorHandle::dummy());
} }
let default = monitors.get(0).unwrap(); let default = monitors.get(0).unwrap();
let window_rect = match window_rect { let window_rect = match window_rect {
Some(rect) => rect, Some(rect) => rect,
None => return default.to_owned(), None => return Ok(default.to_owned()),
}; };
let mut largest_overlap = 0; 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> { fn query_monitor_list(&self) -> Result<Vec<MonitorHandle>, X11Error> {
unsafe { let root = self.default_root();
let mut major = 0; let resources = ScreenResources::from_connection(self.xcb_connection(), root)?;
let mut minor = 0;
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
let root = self.default_root().root; // Pipeline all of the get-crtc requests.
let resources = if (major == 1 && minor >= 3) || major > 1 { let mut crtc_cookies = Vec::with_capacity(resources.crtcs().len());
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root as ffi::Window) for &crtc in resources.crtcs() {
} else { crtc_cookies.push(
// WARNING: this function is supposedly very slow, on the order of hundreds of ms. self.xcb_connection()
// Upon failure, `resources` will be null. .randr_get_crtc_info(crtc, x11rb::CURRENT_TIME)?,
(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
} }
// 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> { pub fn available_monitors(&self) -> Result<Vec<MonitorHandle>, X11Error> {
let mut monitors_lock = MONITORS.lock().unwrap(); let mut monitors_lock = self.monitor_handles.lock().unwrap();
(*monitors_lock) (*monitors_lock)
.as_ref() .as_ref()
.cloned() .cloned()
.map(Ok)
.or_else(|| { .or_else(|| {
let monitors = Some(self.query_monitor_list()); self.query_monitor_list()
if !DISABLE_MONITOR_LIST_CACHING { .map(|mon_list| {
(*monitors_lock) = monitors.clone(); let monitors = Some(mon_list);
} if !DISABLE_MONITOR_LIST_CACHING {
monitors (*monitors_lock) = monitors.clone();
}
monitors
})
.transpose()
}) })
.unwrap() .unwrap()
} }
#[inline] #[inline]
pub fn primary_monitor(&self) -> MonitorHandle { pub fn primary_monitor(&self) -> Result<MonitorHandle, X11Error> {
self.available_monitors() Ok(self
.available_monitors()?
.into_iter() .into_iter()
.find(|monitor| monitor.primary) .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> { pub fn select_xrandr_input(&self, root: xproto::Window) -> Result<u8, X11Error> {
let has_xrandr = unsafe { use randr::NotifyMask;
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."
);
let mut event_offset = 0; // Get extension info.
let mut error_offset = 0; let info = self
let status = unsafe { .xcb_connection()
(self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset) .extension_information(randr::X11_EXTENSION_NAME)?
}; .ok_or_else(|| X11Error::MissingExtension(randr::X11_EXTENSION_NAME))?;
if status != True { // Select input data.
self.check_errors()?; let event_mask =
unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); NotifyMask::CRTC_CHANGE | NotifyMask::OUTPUT_PROPERTY | NotifyMask::SCREEN_CHANGE;
} self.xcb_connection().randr_select_input(root, event_mask)?;
let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask; Ok(info.first_event)
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) }; }
}
Ok(event_offset)
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 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::*; use super::*;
@ -11,31 +15,6 @@ pub const VIRTUAL_CORE_KEYBOARD: u16 = 3;
// To test if `lookup_utf8` works correctly, set this to 1. // To test if `lookup_utf8` works correctly, set this to 1.
const TEXT_BUFFER_SIZE: usize = 1024; 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 { impl XConnection {
pub fn select_xinput_events( pub fn select_xinput_events(
&self, &self,
@ -54,16 +33,36 @@ impl XConnection {
.map_err(Into::into) .map_err(Into::into)
} }
pub fn select_xkb_events(&self, device_id: c_int, mask: c_ulong) -> Result<bool, X11Error> { pub fn select_xkb_events(
let status = &self,
unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) }; 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 { match result {
self.flush_requests()?; Ok(()) => Ok(true),
Ok(true) Err(ReplyError::ConnectionError(ConnectionError::UnsupportedExtension)) => {
} else { error!("Could not select XKB events: The XKB extension is not initialized!");
error!("Could not select XKB events: The XKB extension is not initialized!"); Ok(false)
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::{ use super::*;
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
*,
};
use crate::platform_impl::platform::x11::monitor; use crate::platform_impl::platform::x11::monitor;
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
use x11rb::protocol::randr::{self, ConnectionExt as _};
/// Represents values of `WINIT_HIDPI_FACTOR`. /// Represents values of `WINIT_HIDPI_FACTOR`.
pub enum EnvVarDPI { pub enum EnvVarDPI {
Randr, Randr,
@ -37,42 +36,32 @@ pub fn calc_dpi_factor(
impl XConnection { impl XConnection {
// Retrieve DPI from Xft.dpi property // Retrieve DPI from Xft.dpi property
pub unsafe fn get_xft_dpi(&self) -> Option<f64> { pub fn get_xft_dpi(&self) -> Option<f64> {
(self.xlib.XrmInitialize)(); self.database()
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display); .get_string("Xfi.dpi", "")
if resource_manager_str.is_null() { .and_then(|s| f64::from_str(s).ok())
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 unsafe fn get_output_info( pub fn get_output_info(
&self, &self,
resources: *mut XRRScreenResources, resources: &monitor::ScreenResources,
crtc: *mut XRRCrtcInfo, crtc: &randr::GetCrtcInfoReply,
) -> Option<(String, f64, Vec<VideoMode>)> { ) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info = let output_info = match self
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0)); .xcb_connection()
if output_info.is_null() { .randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) .map_err(X11Error::from)
// it's possible for it to return null. .and_then(|r| r.reply().map_err(X11Error::from))
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596 {
let _ = self.check_errors(); // discard `BadRROutput` error Ok(output_info) => output_info,
return None; Err(err) => {
} warn!("Failed to get output info: {:?}", err);
return None;
}
};
let bit_depth = self.default_root().root_depth; let bit_depth = self.default_root().root_depth;
let output_modes = &output_info.modes;
let output_modes = let resource_modes = resources.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 modes = resource_modes let modes = resource_modes
.iter() .iter()
@ -81,7 +70,7 @@ impl XConnection {
.filter(|x| output_modes.iter().any(|id| x.id == *id)) .filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|mode| { .map(|mode| {
VideoMode { VideoMode {
size: (mode.width, mode.height), size: (mode.width.into(), mode.height.into()),
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode) refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
.unwrap_or(0), .unwrap_or(0),
bit_depth: bit_depth as u16, bit_depth: bit_depth as u16,
@ -93,11 +82,13 @@ impl XConnection {
}) })
.collect(); .collect();
let name_slice = slice::from_raw_parts( let name = match str::from_utf8(&output_info.name) {
(*output_info).name as *mut u8, Ok(name) => name.to_owned(),
(*output_info).nameLen as usize, Err(err) => {
); warn!("Failed to get output name: {:?}", err);
let name = String::from_utf8_lossy(name_slice).into(); return None;
}
};
// Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set
let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
if deprecated_dpi_override.is_some() { if deprecated_dpi_override.is_some() {
@ -124,8 +115,8 @@ impl XConnection {
let scale_factor = match dpi_env { let scale_factor = match dpi_env {
EnvVarDPI::Randr => calc_dpi_factor( EnvVarDPI::Randr => calc_dpi_factor(
((*crtc).width, (*crtc).height), (crtc.width.into(), crtc.height.into()),
((*output_info).mm_width as _, (*output_info).mm_height as _), (output_info.mm_width as _, output_info.mm_height as _),
), ),
EnvVarDPI::Scale(dpi_override) => { EnvVarDPI::Scale(dpi_override) => {
if !validate_scale_factor(dpi_override) { if !validate_scale_factor(dpi_override) {
@ -140,74 +131,47 @@ impl XConnection {
dpi / 96. dpi / 96.
} else { } else {
calc_dpi_factor( calc_dpi_factor(
((*crtc).width, (*crtc).height), (crtc.width.into(), crtc.height.into()),
((*output_info).mm_width as _, (*output_info).mm_height as _), (output_info.mm_width as _, output_info.mm_height as _),
) )
} }
} }
}; };
(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, scale_factor, modes)) Some((name, scale_factor, modes))
} }
#[must_use] pub fn set_crtc_config(
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> { &self,
unsafe { crtc_id: randr::Crtc,
let mut major = 0; mode_id: randr::Mode,
let mut minor = 0; ) -> Result<(), X11Error> {
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); let crtc = self
.xcb_connection()
.randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?
.reply()?;
let root = self.default_root().root; self.xcb_connection()
let resources = if (major == 1 && minor >= 3) || major > 1 { .randr_set_crtc_config(
(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,
crtc_id, crtc_id,
CurrentTime, crtc.timestamp,
(*crtc).x, x11rb::CURRENT_TIME,
(*crtc).y, crtc.x,
crtc.y,
mode_id, mode_id,
(*crtc).rotation, crtc.rotation,
(*crtc).outputs.offset(0), &crtc.outputs,
1, )?
); .reply()
.map(|_| ())
(self.xrandr.XRRFreeCrtcInfo)(crtc); .map_err(Into::into)
(self.xrandr.XRRFreeScreenResources)(resources);
if status == Success as i32 {
Some(())
} else {
None
}
}
} }
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result<randr::Mode, X11Error> {
unsafe { Ok(self
let mut major = 0; .xcb_connection()
let mut minor = 0; .randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); .reply()?
.mode)
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
}
} }
} }

View file

@ -10,12 +10,11 @@ use std::{
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, XlibWindowHandle};
use x11rb::{ use x11rb::{
connection::Connection, connection::Connection,
properties::WmHintsState, properties::{WmHints, WmHintsState, WmSizeHints, WmSizeHintsSpecification},
protocol::xproto::{self, ConnectionExt as _}, protocol::{
}; randr, xinput,
use x11rb::{ xproto::{self, ConnectionExt as _},
properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}, },
protocol::xinput,
}; };
use crate::{ use crate::{
@ -55,7 +54,7 @@ pub struct SharedState {
// Used to restore position after exiting fullscreen // Used to restore position after exiting fullscreen
pub restore_position: Option<(i32, i32)>, pub restore_position: Option<(i32, i32)>,
// Used to restore video mode after exiting fullscreen // 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 frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<Size>, pub min_inner_size: Option<Size>,
pub max_inner_size: Option<Size>, pub max_inner_size: Option<Size>,
@ -151,7 +150,7 @@ impl UnownedWindow {
None => event_loop.root, None => event_loop.root,
}; };
let mut monitors = xconn.available_monitors(); let mut monitors = leap!(xconn.available_monitors());
let guessed_monitor = if monitors.is_empty() { let guessed_monitor = if monitors.is_empty() {
X11MonitorHandle::dummy() X11MonitorHandle::dummy()
} else { } else {
@ -524,7 +523,7 @@ impl UnownedWindow {
| xinput::XIEventMask::TOUCH_BEGIN | xinput::XIEventMask::TOUCH_BEGIN
| xinput::XIEventMask::TOUCH_UPDATE | xinput::XIEventMask::TOUCH_UPDATE
| xinput::XIEventMask::TOUCH_END; | 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(); .ignore_error();
{ {
@ -740,8 +739,12 @@ impl UnownedWindow {
&Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode))), &Some(Fullscreen::Exclusive(PlatformVideoMode::X(ref video_mode))),
) => { ) => {
let monitor = video_mode.monitor.as_ref().unwrap(); let monitor = video_mode.monitor.as_ref().unwrap();
shared_state_lock.desktop_video_mode = shared_state_lock.desktop_video_mode = Some((
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id))); monitor.id,
self.xconn
.get_crtc_mode(monitor.id)
.expect("Failed to get desktop video mode"),
));
} }
// Restore desktop video mode upon exiting exclusive fullscreen // Restore desktop video mode upon exiting exclusive fullscreen
(&Some(Fullscreen::Exclusive(_)), &None) (&Some(Fullscreen::Exclusive(_)), &None)
@ -877,11 +880,15 @@ impl UnownedWindow {
} }
pub fn available_monitors(&self) -> Vec<X11MonitorHandle> { 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 { pub fn primary_monitor(&self) -> X11MonitorHandle {
self.xconn.primary_monitor() self.xconn
.primary_monitor()
.expect("Failed to get primary monitor")
} }
#[inline] #[inline]

View file

@ -10,17 +10,20 @@ use std::{
use crate::window::CursorIcon; use crate::window::CursorIcon;
use super::{atoms::Atoms, ffi}; use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
use x11rb::{connection::Connection, protocol::xproto, xcb_ffi::XCBConnection}; use x11rb::{connection::Connection, protocol::xproto, resource_manager, xcb_ffi::XCBConnection};
/// A connection to an X server. /// A connection to an X server.
pub(crate) struct XConnection { pub(crate) struct XConnection {
pub xlib: ffi::Xlib, pub xlib: ffi::Xlib,
/// Exposes XRandR functions from version < 1.5
pub xrandr: ffi::Xrandr_2_2_0,
pub xcursor: ffi::Xcursor, 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 xinput2: ffi::XInput2,
pub display: *mut ffi::Display, pub display: *mut ffi::Display,
/// The manager for the XCB connection. /// The manager for the XCB connection.
/// ///
/// The `Option` ensures that we can drop it before we close the `Display`. /// 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. /// The last timestamp received by this connection.
timestamp: AtomicU32, 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 latest_error: Mutex<Option<XError>>,
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>, pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
} }
@ -53,9 +62,8 @@ impl XConnection {
// opening the libraries // opening the libraries
let xlib = ffi::Xlib::open()?; let xlib = ffi::Xlib::open()?;
let xcursor = ffi::Xcursor::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 xlib_xcb = ffi::Xlib_xcb::open()?;
let xinput2 = ffi::XInput2::open()?;
unsafe { (xlib.XInitThreads)() }; unsafe { (xlib.XInitThreads)() };
unsafe { (xlib.XSetErrorHandler)(error_handler) }; unsafe { (xlib.XSetErrorHandler)(error_handler) };
@ -92,9 +100,12 @@ impl XConnection {
.reply() .reply()
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; .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 { Ok(XConnection {
xlib, xlib,
xrandr,
xcursor, xcursor,
xinput2, xinput2,
display, display,
@ -103,6 +114,8 @@ impl XConnection {
default_screen, default_screen,
timestamp: AtomicU32::new(0), timestamp: AtomicU32::new(0),
latest_error: Mutex::new(None), latest_error: Mutex::new(None),
monitor_handles: Mutex::new(None),
database,
cursor_cache: Default::default(), cursor_cache: Default::default(),
}) })
} }
@ -144,6 +157,12 @@ impl XConnection {
&self.xcb_connection().setup().roots[self.default_screen] &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. /// Get the latest timestamp.
#[inline] #[inline]
pub fn timestamp(&self) -> u32 { pub fn timestamp(&self) -> u32 {