1 | use std::{ |
2 | collections::HashMap, |
3 | error::Error, |
4 | fmt, ptr, |
5 | sync::{ |
6 | atomic::{AtomicU32, Ordering}, |
7 | Arc, Mutex, RwLock, RwLockReadGuard, |
8 | }, |
9 | }; |
10 | |
11 | use crate::window::CursorIcon; |
12 | |
13 | use super::{atoms::Atoms, ffi, monitor::MonitorHandle}; |
14 | use 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. |
25 | pub 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 | |
68 | unsafe impl Send for XConnection {} |
69 | unsafe impl Sync for XConnection {} |
70 | |
71 | pub type XErrorHandler = |
72 | Option<unsafe extern "C" fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> std::os::raw::c_int>; |
73 | |
74 | impl 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 | |
275 | impl fmt::Debug for XConnection { |
276 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
277 | self.display.fmt(f) |
278 | } |
279 | } |
280 | |
281 | impl 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)] |
291 | pub struct XError { |
292 | pub description: String, |
293 | pub error_code: u8, |
294 | pub request_code: u8, |
295 | pub minor_code: u8, |
296 | } |
297 | |
298 | impl Error for XError {} |
299 | |
300 | impl 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)] |
312 | pub 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 | |
323 | impl From<ffi::OpenError> for XNotSupported { |
324 | #[inline ] |
325 | fn from(err: ffi::OpenError) -> XNotSupported { |
326 | XNotSupported::LibraryOpenError(err) |
327 | } |
328 | } |
329 | |
330 | impl 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 | |
340 | impl 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 | |
351 | impl 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)] |
361 | struct WrapConnectError(x11rb::rust_connection::ConnectError); |
362 | |
363 | impl fmt::Display for WrapConnectError { |
364 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
365 | fmt::Display::fmt(&self.0, f) |
366 | } |
367 | } |
368 | |
369 | impl Error for WrapConnectError { |
370 | // We can't implement `source()` here or otherwise risk exposing `x11rb`. |
371 | } |
372 | |