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 | |