| 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 OR Zlib |
| 2 | |
| 3 | // Copyright 2023 John Nunley |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0, the MIT License, and |
| 6 | // the Zlib license. You may not use this software except in compliance |
| 7 | // with at least one of these licenses. You should have received a copy |
| 8 | // of these licenses with this software. You may also find them at: |
| 9 | // |
| 10 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | // https://opensource.org/licenses/MIT |
| 12 | // https://opensource.org/licenses/Zlib |
| 13 | // |
| 14 | // Unless required by applicable law or agreed to in writing, software |
| 15 | // distributed under these licenses is distributed on an "AS IS" BASIS, |
| 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 17 | // See the licenses for the specific language governing permissions and |
| 18 | // limitations under the licenses. |
| 19 | |
| 20 | //! A tiny set of bindings to the [Xlib] library. |
| 21 | //! |
| 22 | //! The primary contemporary library for handling [Xlib] is the [`x11-dl`] crate. However, there are three |
| 23 | //! primary issues. |
| 24 | //! |
| 25 | //! 1. **You should not be using Xlib in 2023.** [Xlib] is legacy code, and even that doesn't get across |
| 26 | //! how poor the API decisions that it's locked itself into are. It has a global error hook for |
| 27 | //! some reason, thread-safety is a mess, and it has so many soundness holes it might as well be made |
| 28 | //! out of swiss cheese. You should not be using [Xlib]. If you *have* to use [Xlib], you should just |
| 29 | //! run all of your logic using the much more sound [XCB] library, or, even more ideally, something |
| 30 | //! like [`x11rb`]. Then, you take the `Display` pointer and use it for whatever legacy API you've |
| 31 | //! locked yourself into, and use [XCB] or [`x11rb`] for everything else. Yes, I just called [GLX] |
| 32 | //! a legacy API. It's the 2020's now. [Vulkan] and [`wgpu`] run everywhere aside from legacy machines. |
| 33 | //! Not to mention, they support [XCB]. |
| 34 | //! |
| 35 | //! 2. Even if you manage to use [`x11-dl`] without tripping over the legacy API, it is a massive crate. |
| 36 | //! [Xlib] comes with quite a few functions, most of which are unnecessary in the 21st century. |
| 37 | //! Even if you don't use any of these and just stick to [XCB], you still pay the price for it. |
| 38 | //! Binaries that use [`x11-dl`] need to dedicate a significant amount of their binary and memory |
| 39 | //! space to the library. Even on Release builds, I have recorded [`x11-dl`] taking up to seven |
| 40 | //! percent of the binary. |
| 41 | //! |
| 42 | //! 3. Global error handling. [Xlib] has a single global error hook. This is reminiscent of the Unix |
| 43 | //! signal handling API, in that it makes it difficult to create well-modularized programs |
| 44 | //! since they will fight with each-other over the error handlers. However, unlike the signal |
| 45 | //! handling API, there is no way to tell if you're replacing an existing error hook. |
| 46 | //! |
| 47 | //! `tiny-xlib` aims to solve all of these problems. It provides a safe API around [Xlib] that is |
| 48 | //! conducive to being handed off to both [XCB] APIs and legacy [Xlib] APIs. The library only |
| 49 | //! imports absolutely necessary functions. In addition, it also provides a common API for |
| 50 | //! handling errors in a safe, modular way. |
| 51 | //! |
| 52 | //! # Features |
| 53 | //! |
| 54 | //! - Safe API around [Xlib]. See the [`Display`] structure. |
| 55 | //! - Minimal set of dependencies. |
| 56 | //! - Implements [`AsRawXcbConnection`], which allows it to be used with [XCB] APIs. |
| 57 | //! - Modular error handling. |
| 58 | //! |
| 59 | //! # Non-Features |
| 60 | //! |
| 61 | //! - Any API outside of opening [`Display`]s and handling errors. If this library doesn't support some |
| 62 | //! feature, it's probably intentional. You should use [XCB] or [`x11rb`] instead. This includes: |
| 63 | //! - Window management. |
| 64 | //! - Any extensions outside of `Xlib-xcb`. |
| 65 | //! - IME handling. |
| 66 | //! - Hardware rendering. |
| 67 | //! |
| 68 | //! # Examples |
| 69 | //! |
| 70 | //! ```no_run |
| 71 | //! use as_raw_xcb_connection::AsRawXcbConnection; |
| 72 | //! use tiny_xlib::Display; |
| 73 | //! |
| 74 | //! use x11rb::connection::Connection; |
| 75 | //! use x11rb::xcb_ffi::XCBConnection; |
| 76 | //! |
| 77 | //! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 78 | //! // Open a display. |
| 79 | //! let display = Display::new(None)?; |
| 80 | //! |
| 81 | //! // Get the XCB connection. |
| 82 | //! let xcb_conn = display.as_raw_xcb_connection(); |
| 83 | //! |
| 84 | //! // Use that pointer to create a new XCB connection. |
| 85 | //! let xcb_conn = unsafe { |
| 86 | //! XCBConnection::from_raw_xcb_connection(xcb_conn.cast(), false)? |
| 87 | //! }; |
| 88 | //! |
| 89 | //! // Register a handler for X11 errors. |
| 90 | //! tiny_xlib::register_error_handler(Box::new(|_, error| { |
| 91 | //! println!("X11 error: {:?}" , error); |
| 92 | //! false |
| 93 | //! })); |
| 94 | //! |
| 95 | //! // Do whatever you want with the XCB connection. |
| 96 | //! loop { |
| 97 | //! println!("Event: {:?}" , xcb_conn.wait_for_event()?); |
| 98 | //! } |
| 99 | //! # Ok(()) } |
| 100 | //! ``` |
| 101 | //! |
| 102 | //! # Optional Features |
| 103 | //! |
| 104 | //! - `tracing`, enabled by default, enables telemetry using the [`tracing`] crate. |
| 105 | //! - `dlopen` uses the [`libloading`] library to load the X11 libraries instead of linking to them |
| 106 | //! directly. |
| 107 | //! |
| 108 | //! [Xlib]: https://en.wikipedia.org/wiki/Xlib |
| 109 | //! [XCB]: https://xcb.freedesktop.org/ |
| 110 | //! [`x11-dl`]: https://crates.io/crates/x11-dl |
| 111 | //! [`x11rb`]: https://crates.io/crates/x11rb |
| 112 | //! [GLX]: https://en.wikipedia.org/wiki/GLX |
| 113 | //! [Vulkan]: https://www.khronos.org/vulkan/ |
| 114 | //! [`wgpu`]: https://crates.io/crates/wgpu |
| 115 | //! [`Display`]: struct.Display.html |
| 116 | //! [`AsRawXcbConnection`]: https://docs.rs/as_raw_xcb_connection/latest/as_raw_xcb_connection/trait.AsRawXcbConnection.html |
| 117 | //! [`tracing`]: https://crates.io/crates/tracing |
| 118 | //! [`libloading`]: https://crates.io/crates/libloading |
| 119 | |
| 120 | #![allow (unused_unsafe)] |
| 121 | #![cfg_attr (coverage, feature(no_coverage))] |
| 122 | |
| 123 | mod ffi; |
| 124 | |
| 125 | use std::cell::Cell; |
| 126 | use std::ffi::CStr; |
| 127 | use std::fmt; |
| 128 | use std::io; |
| 129 | use std::marker::PhantomData; |
| 130 | use std::mem::{self, ManuallyDrop}; |
| 131 | use std::os::raw::{c_int, c_void}; |
| 132 | use std::ptr::{self, NonNull}; |
| 133 | use std::sync::{Mutex, MutexGuard, Once, PoisonError}; |
| 134 | |
| 135 | macro_rules! lock { |
| 136 | ($e:expr) => {{ |
| 137 | // Make sure this isn't flagged with coverage. |
| 138 | #[cfg_attr(coverage, no_coverage)] |
| 139 | fn unwrapper<T>(guard: PoisonError<MutexGuard<'_, T>>) -> MutexGuard<'_, T> { |
| 140 | guard.into_inner() |
| 141 | } |
| 142 | |
| 143 | ($e).lock().unwrap_or_else(unwrapper) |
| 144 | }}; |
| 145 | } |
| 146 | |
| 147 | ctor_lite::ctor! { |
| 148 | unsafe static XLIB: io::Result<ffi::Xlib> = { |
| 149 | #[cfg_attr (coverage, no_coverage)] |
| 150 | unsafe fn load_xlib_with_error_hook() -> io::Result<ffi::Xlib> { |
| 151 | // Here's a puzzle: how do you *safely* add an error hook to Xlib? Like signal handling, there |
| 152 | // is a single global error hook. Therefore, we need to make sure that we economize on the |
| 153 | // single slot that we have by offering a way to set it. However, unlike signal handling, there |
| 154 | // is no way to tell if we're replacing an existing error hook. If we replace another library's |
| 155 | // error hook, we could cause unsound behavior if it assumes that it is the only error hook. |
| 156 | // |
| 157 | // However, we don't want to call the default error hook, because it exits the program. So, in |
| 158 | // order to tell if the error hook is the default one, we need to compare it to the default |
| 159 | // error hook. However, we can't just compare the function pointers, because the default error |
| 160 | // hook is a private function that we can't access. |
| 161 | // |
| 162 | // In order to access it, before anything else runs, this function is called. It loads Xlib, |
| 163 | // sets the error hook to a dummy function, reads the resulting error hook into a static |
| 164 | // variable, and then resets the error hook to the default function. This allows us to read |
| 165 | // the default error hook and compare it to the one that we're setting. |
| 166 | #[cfg_attr (coverage, no_coverage)] |
| 167 | fn error(e: impl std::error::Error) -> io::Error { |
| 168 | io::Error::new(io::ErrorKind::Other, format!("failed to load Xlib: {}" , e)) |
| 169 | } |
| 170 | let xlib = ffi::Xlib::load().map_err(error)?; |
| 171 | |
| 172 | // Dummy function we use to set the error hook. |
| 173 | #[cfg_attr (coverage, no_coverage)] |
| 174 | unsafe extern "C" fn dummy( |
| 175 | _display: *mut ffi::Display, |
| 176 | _error: *mut ffi::XErrorEvent, |
| 177 | ) -> std::os::raw::c_int { |
| 178 | 0 |
| 179 | } |
| 180 | |
| 181 | // Set the error hook to the dummy function. |
| 182 | let default_hook = xlib.set_error_handler(Some(dummy)); |
| 183 | |
| 184 | // Read the error hook into a static variable. |
| 185 | // SAFETY: This should only run once at the start of the program, no need to worry about |
| 186 | // multithreading. |
| 187 | DEFAULT_ERROR_HOOK.set(default_hook); |
| 188 | |
| 189 | // Set the error hook back to the default function. |
| 190 | xlib.set_error_handler(default_hook); |
| 191 | |
| 192 | Ok(xlib) |
| 193 | } |
| 194 | |
| 195 | unsafe { load_xlib_with_error_hook() } |
| 196 | }; |
| 197 | } |
| 198 | |
| 199 | #[inline ] |
| 200 | fn get_xlib(sym: &io::Result<ffi::Xlib>) -> io::Result<&ffi::Xlib> { |
| 201 | // Eat coverage on the error branch. |
| 202 | #[cfg_attr (coverage, no_coverage)] |
| 203 | fn error(e: &io::Error) -> io::Error { |
| 204 | io::Error::new(e.kind(), error:e.to_string()) |
| 205 | } |
| 206 | |
| 207 | sym.as_ref().map_err(op:error) |
| 208 | } |
| 209 | |
| 210 | /// The default error hook to compare against. |
| 211 | static DEFAULT_ERROR_HOOK: ErrorHookSlot = ErrorHookSlot::new(); |
| 212 | |
| 213 | /// An error handling hook. |
| 214 | type ErrorHook = Box<dyn FnMut(&Display, &ErrorEvent) -> bool + Send + Sync + 'static>; |
| 215 | |
| 216 | /// List of error hooks to invoke. |
| 217 | static ERROR_HANDLERS: Mutex<HandlerList> = Mutex::new(HandlerList::new()); |
| 218 | |
| 219 | /// Global error handler for X11. |
| 220 | unsafe extern "C" fn error_handler( |
| 221 | display: *mut ffi::Display, |
| 222 | error: *mut ffi::XErrorEvent, |
| 223 | ) -> c_int { |
| 224 | // Abort the program if the error hook panics. |
| 225 | struct AbortOnPanic; |
| 226 | impl Drop for AbortOnPanic { |
| 227 | #[cfg_attr (coverage, no_coverage)] |
| 228 | #[cold ] |
| 229 | #[inline (never)] |
| 230 | fn drop(&mut self) { |
| 231 | std::process::abort(); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | let bomb = AbortOnPanic; |
| 236 | |
| 237 | let mut handlers = lock!(ERROR_HANDLERS); |
| 238 | |
| 239 | let prev = handlers.prev; |
| 240 | if let Some(prev) = prev { |
| 241 | // Drop the mutex lock to make sure no deadlocks occur. Otherwise, if the prev handlers |
| 242 | // tries to add its own handler, we'll deadlock. |
| 243 | drop(handlers); |
| 244 | |
| 245 | unsafe { |
| 246 | // Run the previous error hook, if any. |
| 247 | prev(display, error); |
| 248 | } |
| 249 | |
| 250 | // Restore the mutex lock. |
| 251 | handlers = lock!(ERROR_HANDLERS); |
| 252 | } |
| 253 | |
| 254 | // Read out the variables. |
| 255 | // SAFETY: Guaranteed to be a valid display setup. |
| 256 | let display_ptr = unsafe { Display::from_ptr(display.cast()) }; |
| 257 | let event = ErrorEvent(ptr::read(error)); |
| 258 | |
| 259 | #[cfg (feature = "tracing" )] |
| 260 | tracing::error!( |
| 261 | display = ?&*display_ptr, |
| 262 | error = ?event, |
| 263 | "got Xlib error" , |
| 264 | ); |
| 265 | |
| 266 | // Invoke the error hooks. |
| 267 | handlers.iter_mut().any(|(_i, handler)| { |
| 268 | #[cfg (feature = "tracing" )] |
| 269 | tracing::trace!(key = _i, "invoking error handler" ); |
| 270 | |
| 271 | let stop_going = (handler)(&display_ptr, &event); |
| 272 | |
| 273 | #[cfg (feature = "tracing" )] |
| 274 | { |
| 275 | if stop_going { |
| 276 | tracing::trace!("error handler returned true, stopping" ); |
| 277 | } else { |
| 278 | tracing::trace!("error handler returned false, continuing" ); |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | stop_going |
| 283 | }); |
| 284 | |
| 285 | // Defuse the bomb. |
| 286 | mem::forget(bomb); |
| 287 | |
| 288 | // Apparently the return value here has no effect. |
| 289 | 0 |
| 290 | } |
| 291 | |
| 292 | /// Register the error handler. |
| 293 | fn setup_error_handler(xlib: &ffi::Xlib) { |
| 294 | static REGISTERED: Once = Once::new(); |
| 295 | REGISTERED.call_once(move || { |
| 296 | // Make sure threads are initialized here. |
| 297 | unsafe { |
| 298 | xlib.init_threads(); |
| 299 | } |
| 300 | |
| 301 | // Get the previous error handler. |
| 302 | let prev: Option …> = unsafe { xlib.set_error_handler(Some(error_handler)) }; |
| 303 | |
| 304 | // If it isn't the default error handler, then we need to store it. |
| 305 | // SAFETY: DEFAULT_ERROR_HOOK is not set after the program starts, so this is safe. |
| 306 | let default_hook: Option = unsafe { DEFAULT_ERROR_HOOK.get() }; |
| 307 | if prev != default_hook.flatten() && prev != Some(error_handler) { |
| 308 | lock!(ERROR_HANDLERS).prev = prev; |
| 309 | } |
| 310 | }); |
| 311 | } |
| 312 | |
| 313 | /// A key to the error handler list that can be used to remove handlers. |
| 314 | #[derive (Debug, Copy, Clone)] |
| 315 | pub struct HandlerKey(usize); |
| 316 | |
| 317 | /// The error event type. |
| 318 | #[derive (Clone)] |
| 319 | pub struct ErrorEvent(ffi::XErrorEvent); |
| 320 | |
| 321 | // SAFETY: With XInitThreads, ErrorEvent is both Send and Sync. |
| 322 | unsafe impl Send for ErrorEvent {} |
| 323 | unsafe impl Sync for ErrorEvent {} |
| 324 | |
| 325 | impl ErrorEvent { |
| 326 | /// Get the serial number of the failed request. |
| 327 | #[allow (clippy::unnecessary_cast)] |
| 328 | pub fn serial(&self) -> u64 { |
| 329 | self.0.serial as u64 |
| 330 | } |
| 331 | |
| 332 | /// Get the error code. |
| 333 | pub fn error_code(&self) -> u8 { |
| 334 | self.0.error_code |
| 335 | } |
| 336 | |
| 337 | /// Get the request code. |
| 338 | pub fn request_code(&self) -> u8 { |
| 339 | self.0.request_code |
| 340 | } |
| 341 | |
| 342 | /// Get the minor opcode of the failed request. |
| 343 | pub fn minor_code(&self) -> u8 { |
| 344 | self.0.minor_code |
| 345 | } |
| 346 | |
| 347 | /// Get the resource ID of the failed request. |
| 348 | pub fn resource_id(&self) -> usize { |
| 349 | self.0.resourceid as usize |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | impl fmt::Debug for ErrorEvent { |
| 354 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 355 | f&mut DebugStruct<'_, '_>.debug_struct("ErrorEvent" ) |
| 356 | .field("serial" , &self.serial()) |
| 357 | .field("error_code" , &self.error_code()) |
| 358 | .field("request_code" , &self.request_code()) |
| 359 | .field("minor_code" , &self.minor_code()) |
| 360 | .field(name:"resource_id" , &self.resource_id()) |
| 361 | .finish_non_exhaustive() |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | /// The display pointer. |
| 366 | pub struct Display { |
| 367 | /// The display pointer. |
| 368 | ptr: NonNull<ffi::Display>, |
| 369 | |
| 370 | /// This owns the memory that the display pointer points to. |
| 371 | _marker: PhantomData<Box<ffi::Display>>, |
| 372 | } |
| 373 | |
| 374 | // SAFETY: With XInitThreads, Display is both Send and Sync. |
| 375 | unsafe impl Send for Display {} |
| 376 | unsafe impl Sync for Display {} |
| 377 | |
| 378 | impl fmt::Debug for Display { |
| 379 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 380 | f.debug_tuple(name:"Display" ).field(&self.ptr.as_ptr()).finish() |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | impl Display { |
| 385 | /// Open a new display. |
| 386 | pub fn new(name: Option<&CStr>) -> io::Result<Self> { |
| 387 | let xlib = get_xlib(&XLIB)?; |
| 388 | |
| 389 | // Make sure the error handler is registered. |
| 390 | setup_error_handler(xlib); |
| 391 | |
| 392 | let name = name.map_or(std::ptr::null(), |n| n.as_ptr()); |
| 393 | let pointer = unsafe { xlib.open_display(name) }; |
| 394 | |
| 395 | NonNull::new(pointer) |
| 396 | .map(|ptr| Self { |
| 397 | ptr, |
| 398 | _marker: PhantomData, |
| 399 | }) |
| 400 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to open display" )) |
| 401 | } |
| 402 | |
| 403 | /// Create a new `Display` from a pointer. |
| 404 | /// |
| 405 | /// # Safety |
| 406 | /// |
| 407 | /// The pointer must be a valid pointer to an Xlib display. In addition, it should only be dropped if the |
| 408 | /// user logically owns the display. |
| 409 | pub unsafe fn from_ptr(ptr: *mut c_void) -> ManuallyDrop<Self> { |
| 410 | ManuallyDrop::new(Self { |
| 411 | // SAFETY: "valid" implies non-null |
| 412 | ptr: NonNull::new_unchecked(ptr.cast()), |
| 413 | _marker: PhantomData, |
| 414 | }) |
| 415 | } |
| 416 | |
| 417 | /// Get the pointer to the display. |
| 418 | pub fn as_ptr(&self) -> *mut c_void { |
| 419 | self.ptr.as_ptr().cast() |
| 420 | } |
| 421 | |
| 422 | /// Get the default screen index for this display. |
| 423 | pub fn screen_index(&self) -> usize { |
| 424 | let xlib = get_xlib(&XLIB).expect("failed to load Xlib" ); |
| 425 | |
| 426 | // SAFETY: Valid display pointer. |
| 427 | let index = unsafe { xlib.default_screen(self.ptr.as_ptr()) }; |
| 428 | |
| 429 | // Cast down to usize. |
| 430 | index.try_into().unwrap_or_else(|_| { |
| 431 | #[cfg (feature = "tracing" )] |
| 432 | tracing::error!( |
| 433 | "XDefaultScreen returned a value out of usize range (how?!), returning zero" |
| 434 | ); |
| 435 | 0 |
| 436 | }) |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | unsafe impl as_raw_xcb_connection::AsRawXcbConnection for Display { |
| 441 | fn as_raw_xcb_connection(&self) -> *mut as_raw_xcb_connection::xcb_connection_t { |
| 442 | let xlib: &Xlib = get_xlib(&XLIB).expect(msg:"failed to load Xlib" ); |
| 443 | unsafe { xlib.get_xcb_connection(self.ptr.as_ptr()) } |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | impl Drop for Display { |
| 448 | fn drop(&mut self) { |
| 449 | // SAFETY: We own the display pointer, so we can drop it. |
| 450 | if let Ok(xlib: &Xlib) = get_xlib(&XLIB) { |
| 451 | unsafe { |
| 452 | xlib.close_display(self.ptr.as_ptr()); |
| 453 | } |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | /// Insert an error handler into the list. |
| 459 | pub fn register_error_handler(handler: ErrorHook) -> io::Result<HandlerKey> { |
| 460 | // Make sure the error handler is registered. |
| 461 | setup_error_handler(get_xlib(&XLIB)?); |
| 462 | |
| 463 | // Insert the handler into the list. |
| 464 | let mut handlers: MutexGuard<'_, HandlerList> = lock!(ERROR_HANDLERS); |
| 465 | let key: usize = handlers.insert(handler); |
| 466 | Ok(HandlerKey(key)) |
| 467 | } |
| 468 | |
| 469 | /// Remove an error handler from the list. |
| 470 | pub fn unregister_error_handler(key: HandlerKey) { |
| 471 | // Remove the handler from the list. |
| 472 | let mut handlers: MutexGuard<'_, HandlerList> = lock!(ERROR_HANDLERS); |
| 473 | handlers.remove(index:key.0); |
| 474 | } |
| 475 | |
| 476 | /// The list of error handlers. |
| 477 | struct HandlerList { |
| 478 | /// The inner list of slots. |
| 479 | slots: Vec<Slot>, |
| 480 | |
| 481 | /// The number of filled slots. |
| 482 | filled: usize, |
| 483 | |
| 484 | /// The first unfilled slot. |
| 485 | unfilled: usize, |
| 486 | |
| 487 | /// The last error handler hook. |
| 488 | prev: ffi::XErrorHook, |
| 489 | } |
| 490 | |
| 491 | /// A slot in the error handler list. |
| 492 | enum Slot { |
| 493 | /// A slot that is filled. |
| 494 | Filled(ErrorHook), |
| 495 | |
| 496 | /// A slot that is unfilled. |
| 497 | /// |
| 498 | /// This value points to the next unfilled slot. |
| 499 | Unfilled(usize), |
| 500 | } |
| 501 | |
| 502 | impl HandlerList { |
| 503 | /// Create a new handler list. |
| 504 | #[cfg_attr (coverage, no_coverage)] |
| 505 | const fn new() -> Self { |
| 506 | Self { |
| 507 | slots: vec![], |
| 508 | filled: 0, |
| 509 | unfilled: 0, |
| 510 | prev: None, |
| 511 | } |
| 512 | } |
| 513 | |
| 514 | /// Push a new error handler. |
| 515 | /// |
| 516 | /// Returns the index of the handler. |
| 517 | fn insert(&mut self, handler: ErrorHook) -> usize { |
| 518 | // Eat the coverage for the unreachable branch. |
| 519 | #[cfg_attr (coverage, no_coverage)] |
| 520 | #[inline (always)] |
| 521 | fn unwrapper(slot: &Slot) -> usize { |
| 522 | match slot { |
| 523 | Slot::Filled(_) => unreachable!(), |
| 524 | Slot::Unfilled(next) => *next, |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | let index = self.filled; |
| 529 | |
| 530 | if self.unfilled == self.slots.len() { |
| 531 | self.slots.push(Slot::Filled(handler)); |
| 532 | self.unfilled += 1; |
| 533 | } else { |
| 534 | let unfilled = self.unfilled; |
| 535 | self.unfilled = unwrapper(&self.slots[unfilled]); |
| 536 | self.slots[unfilled] = Slot::Filled(handler); |
| 537 | } |
| 538 | |
| 539 | self.filled += 1; |
| 540 | |
| 541 | index |
| 542 | } |
| 543 | |
| 544 | /// Remove an error handler. |
| 545 | fn remove(&mut self, index: usize) { |
| 546 | let slot = &mut self.slots[index]; |
| 547 | |
| 548 | if let Slot::Filled(_) = slot { |
| 549 | *slot = Slot::Unfilled(self.unfilled); |
| 550 | self.unfilled = index; |
| 551 | self.filled -= 1; |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | /// Iterate over the error handlers. |
| 556 | fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut ErrorHook)> { |
| 557 | self.slots |
| 558 | .iter_mut() |
| 559 | .enumerate() |
| 560 | .filter_map(|(i, slot)| match slot { |
| 561 | Slot::Filled(handler) => Some((i, handler)), |
| 562 | _ => None, |
| 563 | }) |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | /// Static unsafe error hook slot. |
| 568 | struct ErrorHookSlot(Cell<Option<ffi::XErrorHook>>); |
| 569 | |
| 570 | unsafe impl Sync for ErrorHookSlot {} |
| 571 | |
| 572 | impl ErrorHookSlot { |
| 573 | #[cfg_attr (coverage, no_coverage)] |
| 574 | const fn new() -> Self { |
| 575 | Self(Cell::new(None)) |
| 576 | } |
| 577 | |
| 578 | unsafe fn get(&self) -> Option<ffi::XErrorHook> { |
| 579 | self.0.get() |
| 580 | } |
| 581 | |
| 582 | #[cfg_attr (coverage, no_coverage)] |
| 583 | unsafe fn set(&self, hook: ffi::XErrorHook) { |
| 584 | self.0.set(val:Some(hook)); |
| 585 | } |
| 586 | } |
| 587 | |