winit/platform_impl/linux/
mod.rs

1#![cfg(free_unix)]
2
3#[cfg(all(not(x11_platform), not(wayland_platform)))]
4compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
5
6use std::env;
7use std::num::{NonZeroU16, NonZeroU32};
8use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
9use std::time::Duration;
10#[cfg(x11_platform)]
11use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc, sync::Mutex};
12
13use smol_str::SmolStr;
14
15pub(crate) use self::common::xkb::{physicalkey_to_scancode, scancode_to_physicalkey};
16#[cfg(x11_platform)]
17use self::x11::{XConnection, XError, XNotSupported};
18use crate::application::ApplicationHandler;
19pub(crate) use crate::cursor::OnlyCursorImageSource as PlatformCustomCursorSource;
20#[cfg(x11_platform)]
21use crate::dpi::Size;
22use crate::dpi::{PhysicalPosition, PhysicalSize};
23use crate::error::{EventLoopError, NotSupportedError};
24use crate::event_loop::ActiveEventLoop;
25pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
26use crate::keyboard::Key;
27use crate::platform::pump_events::PumpStatus;
28#[cfg(x11_platform)]
29use crate::platform::x11::{WindowType as XWindowType, XlibErrorHook};
30#[cfg(x11_platform)]
31use crate::utils::Lazy;
32use crate::window::ActivationToken;
33
34pub(crate) mod common;
35#[cfg(wayland_platform)]
36pub(crate) mod wayland;
37#[cfg(x11_platform)]
38pub(crate) mod x11;
39
40#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
41pub(crate) enum Backend {
42    #[cfg(x11_platform)]
43    X,
44    #[cfg(wayland_platform)]
45    Wayland,
46}
47
48#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
49pub(crate) struct PlatformSpecificEventLoopAttributes {
50    pub(crate) forced_backend: Option<Backend>,
51    pub(crate) any_thread: bool,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ApplicationName {
56    pub general: String,
57    pub instance: String,
58}
59
60impl ApplicationName {
61    pub fn new(general: String, instance: String) -> Self {
62        Self { general, instance }
63    }
64}
65
66#[derive(Clone, Debug, PartialEq)]
67pub struct PlatformSpecificWindowAttributes {
68    pub name: Option<ApplicationName>,
69    pub activation_token: Option<ActivationToken>,
70    #[cfg(x11_platform)]
71    pub x11: X11WindowAttributes,
72}
73
74#[derive(Clone, Debug, PartialEq)]
75#[cfg(x11_platform)]
76pub struct X11WindowAttributes {
77    pub visual_id: Option<x11rb::protocol::xproto::Visualid>,
78    pub screen_id: Option<i32>,
79    pub base_size: Option<Size>,
80    pub override_redirect: bool,
81    pub x11_window_types: Vec<XWindowType>,
82
83    /// The parent window to embed this window into.
84    pub embed_window: Option<x11rb::protocol::xproto::Window>,
85}
86
87#[cfg_attr(not(x11_platform), allow(clippy::derivable_impls))]
88impl Default for PlatformSpecificWindowAttributes {
89    fn default() -> Self {
90        Self {
91            name: None,
92            activation_token: None,
93            #[cfg(x11_platform)]
94            x11: X11WindowAttributes {
95                visual_id: None,
96                screen_id: None,
97                base_size: None,
98                override_redirect: false,
99                x11_window_types: vec![XWindowType::Normal],
100                embed_window: None,
101            },
102        }
103    }
104}
105
106#[cfg(x11_platform)]
107pub(crate) static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
108    Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
109
110#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct WindowId(u64);
112
113impl From<WindowId> for u64 {
114    fn from(window_id: WindowId) -> Self {
115        window_id.0
116    }
117}
118
119impl From<u64> for WindowId {
120    fn from(raw_id: u64) -> Self {
121        Self(raw_id)
122    }
123}
124
125impl WindowId {
126    pub const fn dummy() -> Self {
127        Self(0)
128    }
129}
130
131#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132pub enum DeviceId {
133    #[cfg(x11_platform)]
134    X(x11::DeviceId),
135    #[cfg(wayland_platform)]
136    Wayland(wayland::DeviceId),
137}
138
139impl DeviceId {
140    pub const fn dummy() -> Self {
141        #[cfg(wayland_platform)]
142        return DeviceId::Wayland(wayland::DeviceId::dummy());
143        #[cfg(all(not(wayland_platform), x11_platform))]
144        return DeviceId::X(x11::DeviceId::dummy());
145    }
146}
147
148#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149pub enum FingerId {
150    #[cfg(x11_platform)]
151    X(x11::FingerId),
152    #[cfg(wayland_platform)]
153    Wayland(wayland::FingerId),
154}
155
156impl FingerId {
157    pub const fn dummy() -> Self {
158        #[cfg(wayland_platform)]
159        return FingerId::Wayland(wayland::FingerId::dummy());
160        #[cfg(all(not(wayland_platform), x11_platform))]
161        return FingerId::X(x11::FingerId::dummy());
162    }
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
166pub enum MonitorHandle {
167    #[cfg(x11_platform)]
168    X(x11::MonitorHandle),
169    #[cfg(wayland_platform)]
170    Wayland(wayland::MonitorHandle),
171}
172
173/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
174/// expands to the equivalent of
175/// ```ignore
176/// match self {
177///    Enum::X(foo) => foo.something(),
178///    Enum::Wayland(foo) => foo.something(),
179/// }
180/// ```
181/// The result can be converted to another enum by adding `; as AnotherEnum`
182macro_rules! x11_or_wayland {
183    (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
184        match $what {
185            #[cfg(x11_platform)]
186            $enum::X($($c1)*) => $enum2::X($x),
187            #[cfg(wayland_platform)]
188            $enum::Wayland($($c1)*) => $enum2::Wayland($x),
189        }
190    };
191    (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
192        match $what {
193            #[cfg(x11_platform)]
194            $enum::X($($c1)*) => $x,
195            #[cfg(wayland_platform)]
196            $enum::Wayland($($c1)*) => $x,
197        }
198    };
199}
200
201impl MonitorHandle {
202    #[inline]
203    pub fn name(&self) -> Option<String> {
204        x11_or_wayland!(match self; MonitorHandle(m) => m.name())
205    }
206
207    #[inline]
208    pub fn native_identifier(&self) -> u32 {
209        x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
210    }
211
212    #[inline]
213    pub fn position(&self) -> Option<PhysicalPosition<i32>> {
214        x11_or_wayland!(match self; MonitorHandle(m) => m.position())
215    }
216
217    #[inline]
218    pub fn scale_factor(&self) -> f64 {
219        x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as _)
220    }
221
222    #[inline]
223    pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
224        x11_or_wayland!(match self; MonitorHandle(m) => m.current_video_mode())
225    }
226
227    #[inline]
228    pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
229        x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
230    }
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub enum VideoModeHandle {
235    #[cfg(x11_platform)]
236    X(x11::VideoModeHandle),
237    #[cfg(wayland_platform)]
238    Wayland(wayland::VideoModeHandle),
239}
240
241impl VideoModeHandle {
242    #[inline]
243    pub fn size(&self) -> PhysicalSize<u32> {
244        x11_or_wayland!(match self; VideoModeHandle(m) => m.size())
245    }
246
247    #[inline]
248    pub fn bit_depth(&self) -> Option<NonZeroU16> {
249        x11_or_wayland!(match self; VideoModeHandle(m) => m.bit_depth())
250    }
251
252    #[inline]
253    pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
254        x11_or_wayland!(match self; VideoModeHandle(m) => m.refresh_rate_millihertz())
255    }
256
257    #[inline]
258    pub fn monitor(&self) -> MonitorHandle {
259        x11_or_wayland!(match self; VideoModeHandle(m) => m.monitor(); as MonitorHandle)
260    }
261}
262
263#[derive(Debug, Clone, Eq, PartialEq, Hash)]
264pub struct KeyEventExtra {
265    pub text_with_all_modifiers: Option<SmolStr>,
266    pub key_without_modifiers: Key,
267}
268
269#[derive(Clone, Debug, Eq, Hash, PartialEq)]
270pub(crate) enum PlatformCustomCursor {
271    #[cfg(wayland_platform)]
272    Wayland(wayland::CustomCursor),
273    #[cfg(x11_platform)]
274    X(x11::CustomCursor),
275}
276
277/// Hooks for X11 errors.
278#[cfg(x11_platform)]
279pub(crate) static XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
280
281#[cfg(x11_platform)]
282unsafe extern "C" fn x_error_callback(
283    display: *mut x11::ffi::Display,
284    event: *mut x11::ffi::XErrorEvent,
285) -> c_int {
286    let xconn_lock = X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner());
287    if let Ok(ref xconn) = *xconn_lock {
288        // Call all the hooks.
289        let mut error_handled = false;
290        for hook in XLIB_ERROR_HOOKS.lock().unwrap().iter() {
291            error_handled |= hook(display as *mut _, event as *mut _);
292        }
293
294        // `assume_init` is safe here because the array consists of `MaybeUninit` values,
295        // which do not require initialization.
296        let mut buf: [MaybeUninit<c_char>; 1024] = unsafe { MaybeUninit::uninit().assume_init() };
297        unsafe {
298            (xconn.xlib.XGetErrorText)(
299                display,
300                (*event).error_code as c_int,
301                buf.as_mut_ptr() as *mut c_char,
302                buf.len() as c_int,
303            )
304        };
305        let description =
306            unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) }.to_string_lossy();
307
308        let error = unsafe {
309            XError {
310                description: description.into_owned(),
311                error_code: (*event).error_code,
312                request_code: (*event).request_code,
313                minor_code: (*event).minor_code,
314            }
315        };
316
317        // Don't log error.
318        if !error_handled {
319            tracing::error!("X11 error: {:#?}", error);
320            // XXX only update the error, if it wasn't handled by any of the hooks.
321            *xconn.latest_error.lock().unwrap() = Some(error);
322        }
323    }
324    // Fun fact: this return value is completely ignored.
325    0
326}
327
328pub enum EventLoop {
329    #[cfg(wayland_platform)]
330    Wayland(Box<wayland::EventLoop>),
331    #[cfg(x11_platform)]
332    X(x11::EventLoop),
333}
334
335#[derive(Clone)]
336pub enum EventLoopProxy {
337    #[cfg(x11_platform)]
338    X(x11::EventLoopProxy),
339    #[cfg(wayland_platform)]
340    Wayland(wayland::EventLoopProxy),
341}
342
343impl EventLoop {
344    pub(crate) fn new(
345        attributes: &PlatformSpecificEventLoopAttributes,
346    ) -> Result<Self, EventLoopError> {
347        if !attributes.any_thread && !is_main_thread() {
348            panic!(
349                "Initializing the event loop outside of the main thread is a significant \
350                 cross-platform compatibility hazard. If you absolutely need to create an \
351                 EventLoop on a different thread, you can use the \
352                 `EventLoopBuilderExtX11::any_thread` or `EventLoopBuilderExtWayland::any_thread` \
353                 functions."
354            );
355        }
356
357        // NOTE: Wayland first because of X11 could be present under Wayland as well. Empty
358        // variables are also treated as not set.
359        let backend = match (
360            attributes.forced_backend,
361            env::var("WAYLAND_DISPLAY")
362                .ok()
363                .filter(|var| !var.is_empty())
364                .or_else(|| env::var("WAYLAND_SOCKET").ok())
365                .filter(|var| !var.is_empty())
366                .is_some(),
367            env::var("DISPLAY").map(|var| !var.is_empty()).unwrap_or(false),
368        ) {
369            // User is forcing a backend.
370            (Some(backend), ..) => backend,
371            // Wayland is present.
372            #[cfg(wayland_platform)]
373            (None, true, _) => Backend::Wayland,
374            // X11 is present.
375            #[cfg(x11_platform)]
376            (None, _, true) => Backend::X,
377            // No backend is present.
378            (_, wayland_display, x11_display) => {
379                let msg = if wayland_display && !cfg!(wayland_platform) {
380                    "DISPLAY is not set; note: enable the `winit/wayland` feature to support \
381                     Wayland"
382                } else if x11_display && !cfg!(x11_platform) {
383                    "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET is set; note: enable the \
384                     `winit/x11` feature to support X11"
385                } else {
386                    "neither WAYLAND_DISPLAY nor WAYLAND_SOCKET nor DISPLAY is set."
387                };
388                return Err(NotSupportedError::new(msg).into());
389            },
390        };
391
392        // Create the display based on the backend.
393        match backend {
394            #[cfg(wayland_platform)]
395            Backend::Wayland => EventLoop::new_wayland_any_thread().map_err(Into::into),
396            #[cfg(x11_platform)]
397            Backend::X => EventLoop::new_x11_any_thread().map_err(Into::into),
398        }
399    }
400
401    #[cfg(wayland_platform)]
402    fn new_wayland_any_thread() -> Result<EventLoop, EventLoopError> {
403        wayland::EventLoop::new().map(|evlp| EventLoop::Wayland(Box::new(evlp)))
404    }
405
406    #[cfg(x11_platform)]
407    fn new_x11_any_thread() -> Result<EventLoop, EventLoopError> {
408        let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() {
409            Ok(xconn) => xconn.clone(),
410            Err(err) => return Err(os_error!(err.clone()).into()),
411        };
412
413        Ok(EventLoop::X(x11::EventLoop::new(xconn)))
414    }
415
416    #[inline]
417    pub fn is_wayland(&self) -> bool {
418        match *self {
419            #[cfg(wayland_platform)]
420            EventLoop::Wayland(_) => true,
421            #[cfg(x11_platform)]
422            _ => false,
423        }
424    }
425
426    pub fn run_app<A: ApplicationHandler>(self, app: A) -> Result<(), EventLoopError> {
427        x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app(app))
428    }
429
430    pub fn run_app_on_demand<A: ApplicationHandler>(
431        &mut self,
432        app: A,
433    ) -> Result<(), EventLoopError> {
434        x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_app_on_demand(app))
435    }
436
437    pub fn pump_app_events<A: ApplicationHandler>(
438        &mut self,
439        timeout: Option<Duration>,
440        app: A,
441    ) -> PumpStatus {
442        x11_or_wayland!(match self; EventLoop(evlp) => evlp.pump_app_events(timeout, app))
443    }
444
445    pub fn window_target(&self) -> &dyn ActiveEventLoop {
446        x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
447    }
448}
449
450impl AsFd for EventLoop {
451    fn as_fd(&self) -> BorrowedFd<'_> {
452        x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_fd())
453    }
454}
455
456impl AsRawFd for EventLoop {
457    fn as_raw_fd(&self) -> RawFd {
458        x11_or_wayland!(match self; EventLoop(evlp) => evlp.as_raw_fd())
459    }
460}
461
462impl EventLoopProxy {
463    pub fn wake_up(&self) {
464        x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.wake_up())
465    }
466}
467
468#[derive(Clone)]
469#[allow(dead_code)]
470pub(crate) enum OwnedDisplayHandle {
471    #[cfg(x11_platform)]
472    X(Arc<XConnection>),
473    #[cfg(wayland_platform)]
474    Wayland(wayland_client::Connection),
475}
476
477impl OwnedDisplayHandle {
478    #[cfg(feature = "rwh_06")]
479    #[inline]
480    pub fn raw_display_handle_rwh_06(
481        &self,
482    ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
483        use std::ptr::NonNull;
484
485        match self {
486            #[cfg(x11_platform)]
487            Self::X(xconn) => Ok(rwh_06::XlibDisplayHandle::new(
488                NonNull::new(xconn.display.cast()),
489                xconn.default_screen_index() as _,
490            )
491            .into()),
492
493            #[cfg(wayland_platform)]
494            Self::Wayland(conn) => {
495                use sctk::reexports::client::Proxy;
496
497                Ok(rwh_06::WaylandDisplayHandle::new(
498                    NonNull::new(conn.display().id().as_ptr().cast()).unwrap(),
499                )
500                .into())
501            },
502        }
503    }
504}
505
506impl PartialEq for OwnedDisplayHandle {
507    fn eq(&self, other: &Self) -> bool {
508        match (self, other) {
509            #[cfg(x11_platform)]
510            (Self::X(this), Self::X(other)) => Arc::as_ptr(this).eq(&Arc::as_ptr(other)),
511            #[cfg(wayland_platform)]
512            (Self::Wayland(this), Self::Wayland(other)) => this.eq(other),
513            #[cfg(all(x11_platform, wayland_platform))]
514            _ => false,
515        }
516    }
517}
518
519impl Eq for OwnedDisplayHandle {}
520
521/// Returns the minimum `Option<Duration>`, taking into account that `None`
522/// equates to an infinite timeout, not a zero timeout (so can't just use
523/// `Option::min`)
524fn min_timeout(a: Option<Duration>, b: Option<Duration>) -> Option<Duration> {
525    a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
526}
527
528#[cfg(target_os = "linux")]
529fn is_main_thread() -> bool {
530    rustix::thread::gettid() == rustix::process::getpid()
531}
532
533#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
534fn is_main_thread() -> bool {
535    use libc::pthread_main_np;
536
537    unsafe { pthread_main_np() == 1 }
538}
539
540#[cfg(target_os = "netbsd")]
541fn is_main_thread() -> bool {
542    std::thread::current().name() == Some("main")
543}