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