StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Linux-specific app level functionality for use with `winit`. |
| 2 | //! |
| 3 | //! For more information about X11 extensions and request codes/opcodes, |
| 4 | //! see: https://www.x.org/wiki/Development/Documentation/Protocol/OpCodes. |
| 5 | |
| 6 | use lazy_static::lazy_static; |
| 7 | use std::sync::{Arc, Mutex}; |
| 8 | use wgpu::rwh::{HasDisplayHandle, RawDisplayHandle}; |
| 9 | use winit::event_loop::EventLoop; |
| 10 | use x11rb::protocol::xproto::ConnectionExt as _; |
| 11 | |
| 12 | lazy_static! { |
| 13 | static ref ENCOUNTERED_BAD_MATCH_FROM_DRI3_FENCE_FROM_FD: Arc<Mutex<bool>> = Default::default(); |
| 14 | } |
| 15 | |
| 16 | /// Returns whether a `BadMatch` was returned from a `DRI3FenceFromFd` request. |
| 17 | /// NOTE calling this function resets internal state. Subsequent calls to this function will return |
| 18 | /// false until a new `BadMatch` error is encountered for the aforementioned request. |
| 19 | pub fn take_encountered_bad_match_from_dri3_fence_from_fd() -> bool { |
| 20 | let Ok(mut guard) = ENCOUNTERED_BAD_MATCH_FROM_DRI3_FENCE_FROM_FD.lock() else { |
| 21 | return false; |
| 22 | }; |
| 23 | |
| 24 | std::mem::take(&mut guard) |
| 25 | } |
| 26 | |
| 27 | /// Registers an xlib error hook with winit, if needed. |
| 28 | pub fn maybe_register_xlib_error_hook<T>(event_loop: &EventLoop<T>) { |
| 29 | if !is_x11(event_loop) { |
| 30 | return; |
| 31 | } |
| 32 | |
| 33 | let extension_info_map = get_x11_extension_info_map(); |
| 34 | |
| 35 | // Register a callback function with winit that we can use to |
| 36 | // observe and consume error events from the Xlib event loop. |
| 37 | // This returns a boolean indicating whether or not it was |
| 38 | // "handled" by this error hook. If `true` is returned, |
| 39 | // winit will ignore the error. |
| 40 | winit::platform::x11::register_xlib_error_hook(Box::new(move |_, error| { |
| 41 | static GRAB_KEY_REQUEST_CODE: u8 = 33; |
| 42 | static PRESENT_PIXMAP_MINOR_OPCODE: u8 = 1; |
| 43 | |
| 44 | /// Minor opcode for the `DRI3FenceFromFD` request within the DRI3 extension. |
| 45 | /// See https://cgit.freedesktop.org/xorg/proto/dri3proto/tree/dri3proto.txt. |
| 46 | static DRI3_FENCE_FROM_FD_MINOR_OPCODE: u8 = 4; |
| 47 | |
| 48 | let Some(error) = std::ptr::NonNull::new(error as *mut x11_dl::xlib::XErrorEvent) else { |
| 49 | return false; |
| 50 | }; |
| 51 | let error = unsafe { error.as_ref() }; |
| 52 | |
| 53 | // Ignore errors due to global-hotkey attempting to register a |
| 54 | // hotkey that's already been registered. |
| 55 | if error.error_code == x11_dl::xlib::BadAccess |
| 56 | && error.request_code == GRAB_KEY_REQUEST_CODE |
| 57 | { |
| 58 | return true; |
| 59 | } |
| 60 | |
| 61 | let Some(extension_info) = extension_info_map.get(&error.request_code) else { |
| 62 | // If we can't get information about the extension, let winit |
| 63 | // handle it. It will log the error, so we don't need to. |
| 64 | return false; |
| 65 | }; |
| 66 | |
| 67 | // If there's a BadWindow error from a PresentPixmap |
| 68 | // request, ignore it - this is a known bug in Mesa. |
| 69 | if error.error_code == x11_dl::xlib::BadWindow |
| 70 | && extension_info.name == "Present" |
| 71 | && error.minor_code == PRESENT_PIXMAP_MINOR_OPCODE |
| 72 | { |
| 73 | log::warn!("Ignoring BadWindow error from PresentPixmap request"); |
| 74 | return true; |
| 75 | } |
| 76 | |
| 77 | // Specifically handle a `BadMatch` from a `DRI3_FENCE_FROM_FD` request. From error |
| 78 | // reporting, we only seem to get this error when a user has the Performance PRIME profile |
| 79 | // enabled (indicating to NVIDIA Optimus that the NVIDIA GPU should always be used). |
| 80 | if error.error_code == x11_dl::xlib::BadMatch |
| 81 | && extension_info.name == "DRI3" |
| 82 | && error.minor_code == DRI3_FENCE_FROM_FD_MINOR_OPCODE |
| 83 | { |
| 84 | log::warn!("Ignoring a BadMatch from a DRI3FenceFromFD request. The NVIDIA Performance PRIME profile is likely enabled."); |
| 85 | *ENCOUNTERED_BAD_MATCH_FROM_DRI3_FENCE_FROM_FD |
| 86 | .lock() |
| 87 | .unwrap() = true; |
| 88 | return true; |
| 89 | } |
| 90 | |
| 91 | // For other errors from requests defined in extensions, log some |
| 92 | // relevant extension information, then let winit decide what to do |
| 93 | // with it. winit will log an error if we don't handle it, hence only logging a warning |
| 94 | // here. |
| 95 | log::warn!( |
| 96 | "Detected X11 error in {} extension (major opcode: {}; first error: {})", |
| 97 | extension_info.name, |
| 98 | error.request_code, |
| 99 | extension_info.first_error, |
| 100 | ); |
| 101 | |
| 102 | if *ENCOUNTERED_BAD_MATCH_FROM_DRI3_FENCE_FROM_FD |
| 103 | .lock() |
| 104 | .expect("Mutex should not be poisoned") |
| 105 | && extension_info.name == "Present" |
| 106 | { |
| 107 | log::warn!("Ignoring an error from the PRESENT extension after catching a BadMatch from a DRI3FenceFromFD request. Minor opcode: {}; Error code: {}", |
| 108 | error.minor_code, |
| 109 | error.error_code); |
| 110 | return true; |
| 111 | } |
| 112 | |
| 113 | false |
| 114 | })); |
| 115 | } |
| 116 | |
| 117 | /// Queries the X11 server to get information about which extensions are |
| 118 | /// available and metadata about them. |
| 119 | fn get_x11_extension_info_map() -> std::collections::HashMap<u8, X11ExtensionInfo> { |
| 120 | let mut extension_map = Default::default(); |
| 121 | let Ok((xcb, _)) = x11rb::rust_connection::RustConnection::connect(None) else { |
| 122 | return extension_map; |
| 123 | }; |
| 124 | |
| 125 | let Ok(cookie) = xcb.list_extensions() else { |
| 126 | return extension_map; |
| 127 | }; |
| 128 | |
| 129 | let Ok(extensions) = cookie.reply() else { |
| 130 | return extension_map; |
| 131 | }; |
| 132 | |
| 133 | extensions.names.iter().for_each(|name| { |
| 134 | if let Ok(cookie) = xcb.query_extension(&name.name) { |
| 135 | if let Ok(result) = cookie.reply() { |
| 136 | if let Ok(name) = String::from_utf8(name.name.clone()) { |
| 137 | extension_map.insert( |
| 138 | result.major_opcode, |
| 139 | X11ExtensionInfo { |
| 140 | name, |
| 141 | first_error: result.first_error, |
| 142 | }, |
| 143 | ); |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | }); |
| 148 | |
| 149 | extension_map |
| 150 | } |
| 151 | |
| 152 | /// A collection of information about an X11 extension. |
| 153 | struct X11ExtensionInfo { |
| 154 | /// The name of the extension. |
| 155 | name: String, |
| 156 | |
| 157 | /// The ID offset applied to errors defined in this extension. |
| 158 | first_error: u8, |
| 159 | } |
| 160 | |
| 161 | /// Returns whether or not the provided event loop is using X11 as the |
| 162 | /// underlying platform implementation. |
| 163 | fn is_x11<T>(event_loop: &EventLoop<T>) -> bool { |
| 164 | matches!( |
| 165 | event_loop |
| 166 | .owned_display_handle() |
| 167 | .display_handle() |
| 168 | .map(|dh| dh.as_raw()), |
| 169 | Ok(RawDisplayHandle::Xlib(_)) | Ok(RawDisplayHandle::Xcb(_)) |
| 170 | ) |
| 171 | } |
| 172 |