1use std::{
2 collections::HashMap,
3 error::Error,
4 fmt, ptr,
5 sync::{
6 atomic::{AtomicU32, Ordering},
7 Arc, Mutex, RwLock, RwLockReadGuard,
8 },
9};
10
11use crate::window::CursorIcon;
12
13use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
14use x11rb::{
15 connection::Connection,
16 protocol::{
17 randr::ConnectionExt as _,
18 xproto::{self, ConnectionExt},
19 },
20 resource_manager,
21 xcb_ffi::XCBConnection,
22};
23
24/// A connection to an X server.
25pub struct XConnection {
26 pub xlib: ffi::Xlib,
27 pub xcursor: ffi::Xcursor,
28
29 // TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together
30 // for some reason.
31 pub xinput2: ffi::XInput2,
32
33 pub display: *mut ffi::Display,
34
35 /// The manager for the XCB connection.
36 ///
37 /// The `Option` ensures that we can drop it before we close the `Display`.
38 xcb: Option<XCBConnection>,
39
40 /// The atoms used by `winit`.
41 ///
42 /// This is a large structure, so I've elected to Box it to make accessing the fields of
43 /// this struct easier. Feel free to unbox it if you like kicking puppies.
44 atoms: Box<Atoms>,
45
46 /// The index of the default screen.
47 default_screen: usize,
48
49 /// The last timestamp received by this connection.
50 timestamp: AtomicU32,
51
52 /// List of monitor handles.
53 pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
54
55 /// The resource database.
56 database: RwLock<resource_manager::Database>,
57
58 /// RandR version.
59 randr_version: (u32, u32),
60
61 /// Atom for the XSettings screen.
62 xsettings_screen: Option<xproto::Atom>,
63
64 pub latest_error: Mutex<Option<XError>>,
65 pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
66}
67
68unsafe impl Send for XConnection {}
69unsafe impl Sync for XConnection {}
70
71pub type XErrorHandler =
72 Option<unsafe extern "C" fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> std::os::raw::c_int>;
73
74impl XConnection {
75 pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
76 // opening the libraries
77 let xlib = ffi::Xlib::open()?;
78 let xcursor = ffi::Xcursor::open()?;
79 let xlib_xcb = ffi::Xlib_xcb::open()?;
80 let xinput2 = ffi::XInput2::open()?;
81
82 unsafe { (xlib.XInitThreads)() };
83 unsafe { (xlib.XSetErrorHandler)(error_handler) };
84
85 // calling XOpenDisplay
86 let display = unsafe {
87 let display = (xlib.XOpenDisplay)(ptr::null());
88 if display.is_null() {
89 return Err(XNotSupported::XOpenDisplayFailed);
90 }
91 display
92 };
93
94 // Open the x11rb XCB connection.
95 let xcb = {
96 // Get a pointer to the underlying XCB connection
97 let xcb_connection =
98 unsafe { (xlib_xcb.XGetXCBConnection)(display as *mut ffi::Display) };
99 assert!(!xcb_connection.is_null());
100
101 // Wrap the XCB connection in an x11rb XCB connection
102 let conn =
103 unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection.cast(), false) };
104
105 conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
106 };
107
108 // Get the default screen.
109 let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
110
111 // Load the database.
112 let database = resource_manager::new_from_default(&xcb)
113 .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
114
115 // Load the RandR version.
116 let randr_version = xcb
117 .randr_query_version(1, 3)
118 .expect("failed to request XRandR version")
119 .reply()
120 .expect("failed to query XRandR version");
121
122 let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
123 if xsettings_screen.is_none() {
124 log::warn!("error setting XSETTINGS; Xft options won't reload automatically")
125 }
126
127 // Fetch atoms.
128 let atoms = Atoms::new(&xcb)
129 .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
130 .reply()
131 .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
132
133 Ok(XConnection {
134 xlib,
135 xcursor,
136 xinput2,
137 display,
138 xcb: Some(xcb),
139 atoms: Box::new(atoms),
140 default_screen,
141 timestamp: AtomicU32::new(0),
142 latest_error: Mutex::new(None),
143 monitor_handles: Mutex::new(None),
144 database: RwLock::new(database),
145 cursor_cache: Default::default(),
146 randr_version: (randr_version.major_version, randr_version.minor_version),
147 xsettings_screen,
148 })
149 }
150
151 fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
152 // Fetch the _XSETTINGS_S[screen number] atom.
153 let xsettings_screen = xcb
154 .intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
155 .ok()?
156 .reply()
157 .ok()?
158 .atom;
159
160 // Get PropertyNotify events from the XSETTINGS window.
161 // TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
162 // in order to accomodate for a changed window here.
163 let selector_window = xcb
164 .get_selection_owner(xsettings_screen)
165 .ok()?
166 .reply()
167 .ok()?
168 .owner;
169
170 xcb.change_window_attributes(
171 selector_window,
172 &xproto::ChangeWindowAttributesAux::new()
173 .event_mask(xproto::EventMask::PROPERTY_CHANGE),
174 )
175 .ok()?
176 .check()
177 .ok()?;
178
179 Some(xsettings_screen)
180 }
181
182 /// Checks whether an error has been triggered by the previous function calls.
183 #[inline]
184 pub fn check_errors(&self) -> Result<(), XError> {
185 let error = self.latest_error.lock().unwrap().take();
186 if let Some(error) = error {
187 Err(error)
188 } else {
189 Ok(())
190 }
191 }
192
193 #[inline]
194 pub fn randr_version(&self) -> (u32, u32) {
195 self.randr_version
196 }
197
198 /// Get the underlying XCB connection.
199 #[inline]
200 pub fn xcb_connection(&self) -> &XCBConnection {
201 self.xcb
202 .as_ref()
203 .expect("xcb_connection somehow called after drop?")
204 }
205
206 /// Get the list of atoms.
207 #[inline]
208 pub fn atoms(&self) -> &Atoms {
209 &self.atoms
210 }
211
212 /// Get the index of the default screen.
213 #[inline]
214 pub fn default_screen_index(&self) -> usize {
215 self.default_screen
216 }
217
218 /// Get the default screen.
219 #[inline]
220 pub fn default_root(&self) -> &xproto::Screen {
221 &self.xcb_connection().setup().roots[self.default_screen]
222 }
223
224 /// Get the resource database.
225 #[inline]
226 pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> {
227 self.database.read().unwrap_or_else(|e| e.into_inner())
228 }
229
230 /// Reload the resource database.
231 #[inline]
232 pub fn reload_database(&self) -> Result<(), super::X11Error> {
233 let database = resource_manager::new_from_default(self.xcb_connection())?;
234 *self.database.write().unwrap_or_else(|e| e.into_inner()) = database;
235 Ok(())
236 }
237
238 /// Get the latest timestamp.
239 #[inline]
240 pub fn timestamp(&self) -> u32 {
241 self.timestamp.load(Ordering::Relaxed)
242 }
243
244 /// Set the last witnessed timestamp.
245 #[inline]
246 pub fn set_timestamp(&self, timestamp: u32) {
247 // Store the timestamp in the slot if it's greater than the last one.
248 let mut last_timestamp = self.timestamp.load(Ordering::Relaxed);
249 loop {
250 let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32);
251
252 if wrapping_sub(timestamp, last_timestamp) <= 0 {
253 break;
254 }
255
256 match self.timestamp.compare_exchange(
257 last_timestamp,
258 timestamp,
259 Ordering::Relaxed,
260 Ordering::Relaxed,
261 ) {
262 Ok(_) => break,
263 Err(x) => last_timestamp = x,
264 }
265 }
266 }
267
268 /// Get the atom for Xsettings.
269 #[inline]
270 pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
271 self.xsettings_screen
272 }
273}
274
275impl fmt::Debug for XConnection {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 self.display.fmt(f)
278 }
279}
280
281impl Drop for XConnection {
282 #[inline]
283 fn drop(&mut self) {
284 self.xcb = None;
285 unsafe { (self.xlib.XCloseDisplay)(self.display) };
286 }
287}
288
289/// Error triggered by xlib.
290#[derive(Debug, Clone)]
291pub struct XError {
292 pub description: String,
293 pub error_code: u8,
294 pub request_code: u8,
295 pub minor_code: u8,
296}
297
298impl Error for XError {}
299
300impl fmt::Display for XError {
301 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
302 write!(
303 formatter,
304 "X error: {} (code: {}, request code: {}, minor code: {})",
305 self.description, self.error_code, self.request_code, self.minor_code
306 )
307 }
308}
309
310/// Error returned if this system doesn't have XLib or can't create an X connection.
311#[derive(Clone, Debug)]
312pub enum XNotSupported {
313 /// Failed to load one or several shared libraries.
314 LibraryOpenError(ffi::OpenError),
315
316 /// Connecting to the X server with `XOpenDisplay` failed.
317 XOpenDisplayFailed, // TODO: add better message.
318
319 /// We encountered an error while converting the connection to XCB.
320 XcbConversionError(Arc<dyn Error + Send + Sync + 'static>),
321}
322
323impl From<ffi::OpenError> for XNotSupported {
324 #[inline]
325 fn from(err: ffi::OpenError) -> XNotSupported {
326 XNotSupported::LibraryOpenError(err)
327 }
328}
329
330impl XNotSupported {
331 fn description(&self) -> &'static str {
332 match self {
333 XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
334 XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
335 XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB",
336 }
337 }
338}
339
340impl Error for XNotSupported {
341 #[inline]
342 fn source(&self) -> Option<&(dyn Error + 'static)> {
343 match *self {
344 XNotSupported::LibraryOpenError(ref err: &OpenError) => Some(err),
345 XNotSupported::XcbConversionError(ref err: &Arc) => Some(&**err),
346 _ => None,
347 }
348 }
349}
350
351impl fmt::Display for XNotSupported {
352 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
353 formatter.write_str(self.description())
354 }
355}
356
357/// A newtype wrapper around a `ConnectError` that can't be accessed by downstream libraries.
358///
359/// Without this, `x11rb` would become a public dependency.
360#[derive(Debug)]
361struct WrapConnectError(x11rb::rust_connection::ConnectError);
362
363impl fmt::Display for WrapConnectError {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 fmt::Display::fmt(&self.0, f)
366 }
367}
368
369impl Error for WrapConnectError {
370 // We can't implement `source()` here or otherwise risk exposing `x11rb`.
371}
372