winit/platform_impl/linux/x11/
window.rs

1use std::ffi::CString;
2use std::mem::replace;
3use std::os::raw::*;
4use std::path::Path;
5use std::sync::{Arc, Mutex, MutexGuard};
6use std::{cmp, env};
7
8use tracing::{debug, info, warn};
9use x11rb::connection::Connection;
10use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
11use x11rb::protocol::shape::{ConnectionExt as ShapeExt, SK, SO};
12use x11rb::protocol::xproto::{self, ClipOrdering, ConnectionExt as _, Rectangle};
13use x11rb::protocol::{randr, xinput};
14
15use crate::cursor::{Cursor, CustomCursor as RootCustomCursor};
16use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size};
17use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
18use crate::event::{Event, InnerSizeWriter, WindowEvent};
19use crate::event_loop::AsyncRequestSerial;
20use crate::platform::x11::WindowType;
21use crate::platform_impl::x11::atoms::*;
22use crate::platform_impl::x11::{
23    xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error,
24};
25use crate::platform_impl::{
26    Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor,
27    PlatformIcon, VideoModeHandle as PlatformVideoModeHandle,
28};
29use crate::window::{
30    CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes,
31    WindowButtons, WindowLevel,
32};
33
34use super::util::{self, SelectedCursor};
35use super::{
36    ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection,
37};
38
39#[derive(Debug)]
40pub struct SharedState {
41    pub cursor_pos: Option<(f64, f64)>,
42    pub size: Option<(u32, u32)>,
43    pub position: Option<(i32, i32)>,
44    pub inner_position: Option<(i32, i32)>,
45    pub inner_position_rel_parent: Option<(i32, i32)>,
46    pub is_resizable: bool,
47    pub is_decorated: bool,
48    pub last_monitor: X11MonitorHandle,
49    pub dpi_adjusted: Option<(u32, u32)>,
50    pub(crate) fullscreen: Option<Fullscreen>,
51    // Set when application calls `set_fullscreen` when window is not visible
52    pub(crate) desired_fullscreen: Option<Option<Fullscreen>>,
53    // Used to restore position after exiting fullscreen
54    pub restore_position: Option<(i32, i32)>,
55    // Used to restore video mode after exiting fullscreen
56    pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>,
57    pub frame_extents: Option<util::FrameExtentsHeuristic>,
58    pub min_inner_size: Option<Size>,
59    pub max_inner_size: Option<Size>,
60    pub resize_increments: Option<Size>,
61    pub base_size: Option<Size>,
62    pub visibility: Visibility,
63    pub has_focus: bool,
64    // Use `Option` to not apply hittest logic when it was never requested.
65    pub cursor_hittest: Option<bool>,
66}
67
68#[derive(Copy, Clone, Debug, Eq, PartialEq)]
69pub enum Visibility {
70    No,
71    Yes,
72    // Waiting for VisibilityNotify
73    YesWait,
74}
75
76impl SharedState {
77    fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex<Self> {
78        let visibility =
79            if window_attributes.visible { Visibility::YesWait } else { Visibility::No };
80
81        Mutex::new(SharedState {
82            last_monitor,
83            visibility,
84
85            is_resizable: window_attributes.resizable,
86            is_decorated: window_attributes.decorations,
87            cursor_pos: None,
88            size: None,
89            position: None,
90            inner_position: None,
91            inner_position_rel_parent: None,
92            dpi_adjusted: None,
93            fullscreen: None,
94            desired_fullscreen: None,
95            restore_position: None,
96            desktop_video_mode: None,
97            frame_extents: None,
98            min_inner_size: None,
99            max_inner_size: None,
100            resize_increments: None,
101            base_size: None,
102            has_focus: false,
103            cursor_hittest: None,
104        })
105    }
106}
107
108unsafe impl Send for UnownedWindow {}
109unsafe impl Sync for UnownedWindow {}
110
111pub struct UnownedWindow {
112    pub(crate) xconn: Arc<XConnection>, // never changes
113    xwindow: xproto::Window,            // never changes
114    #[allow(dead_code)]
115    visual: u32, // never changes
116    root: xproto::Window,               // never changes
117    #[allow(dead_code)]
118    screen_id: i32, // never changes
119    selected_cursor: Mutex<SelectedCursor>,
120    cursor_grabbed_mode: Mutex<CursorGrabMode>,
121    #[allow(clippy::mutex_atomic)]
122    cursor_visible: Mutex<bool>,
123    ime_sender: Mutex<ImeSender>,
124    pub shared_state: Mutex<SharedState>,
125    redraw_sender: WakeSender<WindowId>,
126    activation_sender: WakeSender<super::ActivationToken>,
127}
128
129macro_rules! leap {
130    ($e:expr) => {
131        match $e {
132            Ok(x) => x,
133            Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))),
134        }
135    };
136}
137
138impl UnownedWindow {
139    #[allow(clippy::unnecessary_cast)]
140    pub(crate) fn new(
141        event_loop: &ActiveEventLoop,
142        window_attrs: WindowAttributes,
143    ) -> Result<UnownedWindow, RootOsError> {
144        let xconn = &event_loop.xconn;
145        let atoms = xconn.atoms();
146
147        let screen_id = match window_attrs.platform_specific.x11.screen_id {
148            Some(id) => id,
149            None => xconn.default_screen_index() as c_int,
150        };
151
152        let screen = {
153            let screen_id_usize = usize::try_from(screen_id)
154                .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative")))?;
155            xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!(
156                OsError::Misc("requested screen id not present in server's response")
157            ))?
158        };
159
160        #[cfg(feature = "rwh_06")]
161        let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) {
162            Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window,
163            Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(),
164            Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11"),
165            None => screen.root,
166        };
167        #[cfg(not(feature = "rwh_06"))]
168        let root = event_loop.root;
169
170        let mut monitors = leap!(xconn.available_monitors());
171        let guessed_monitor = if monitors.is_empty() {
172            X11MonitorHandle::dummy()
173        } else {
174            xconn
175                .query_pointer(root, util::VIRTUAL_CORE_POINTER)
176                .ok()
177                .and_then(|pointer_state| {
178                    let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64);
179
180                    for i in 0..monitors.len() {
181                        if monitors[i].rect.contains_point(x, y) {
182                            return Some(monitors.swap_remove(i));
183                        }
184                    }
185
186                    None
187                })
188                .unwrap_or_else(|| monitors.swap_remove(0))
189        };
190        let scale_factor = guessed_monitor.scale_factor();
191
192        info!("Guessed window scale factor: {}", scale_factor);
193
194        let max_inner_size: Option<(u32, u32)> =
195            window_attrs.max_inner_size.map(|size| size.to_physical::<u32>(scale_factor).into());
196        let min_inner_size: Option<(u32, u32)> =
197            window_attrs.min_inner_size.map(|size| size.to_physical::<u32>(scale_factor).into());
198
199        let position =
200            window_attrs.position.map(|position| position.to_physical::<i32>(scale_factor));
201
202        let dimensions = {
203            // x11 only applies constraints when the window is actively resized
204            // by the user, so we have to manually apply the initial constraints
205            let mut dimensions: (u32, u32) = window_attrs
206                .inner_size
207                .map(|size| size.to_physical::<u32>(scale_factor))
208                .or_else(|| Some((800, 600).into()))
209                .map(Into::into)
210                .unwrap();
211            if let Some(max) = max_inner_size {
212                dimensions.0 = cmp::min(dimensions.0, max.0);
213                dimensions.1 = cmp::min(dimensions.1, max.1);
214            }
215            if let Some(min) = min_inner_size {
216                dimensions.0 = cmp::max(dimensions.0, min.0);
217                dimensions.1 = cmp::max(dimensions.1, min.1);
218            }
219            debug!("Calculated physical dimensions: {}x{}", dimensions.0, dimensions.1);
220            dimensions
221        };
222
223        // An iterator over the visuals matching screen id combined with their depths.
224        let mut all_visuals = screen
225            .allowed_depths
226            .iter()
227            .flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth)));
228
229        // creating
230        let (visualtype, depth, require_colormap) =
231            match window_attrs.platform_specific.x11.visual_id {
232                Some(vi) => {
233                    // Find this specific visual.
234                    let (visualtype, depth) =
235                        all_visuals.find(|(visual, _)| visual.visual_id == vi).ok_or_else(
236                            || os_error!(OsError::XError(X11Error::NoSuchVisual(vi).into())),
237                        )?;
238
239                    (Some(visualtype), depth, true)
240                },
241                None if window_attrs.transparent => {
242                    // Find a suitable visual, true color with 32 bits of depth.
243                    all_visuals
244                        .find_map(|(visual, depth)| {
245                            (depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR)
246                                .then_some((Some(visual), depth, true))
247                        })
248                        .unwrap_or_else(|| {
249                            debug!(
250                                "Could not set transparency, because XMatchVisualInfo returned \
251                                 zero for the required parameters"
252                            );
253                            (None as _, x11rb::COPY_FROM_PARENT as _, false)
254                        })
255                },
256                _ => (None, x11rb::COPY_FROM_PARENT as _, false),
257            };
258        let mut visual = visualtype.map_or(x11rb::COPY_FROM_PARENT, |v| v.visual_id);
259
260        let window_attributes = {
261            use xproto::EventMask;
262
263            let mut aux = xproto::CreateWindowAux::new();
264            let event_mask = EventMask::EXPOSURE
265                | EventMask::STRUCTURE_NOTIFY
266                | EventMask::VISIBILITY_CHANGE
267                | EventMask::KEY_PRESS
268                | EventMask::KEY_RELEASE
269                | EventMask::KEYMAP_STATE
270                | EventMask::BUTTON_PRESS
271                | EventMask::BUTTON_RELEASE
272                | EventMask::POINTER_MOTION
273                | EventMask::PROPERTY_CHANGE;
274
275            aux = aux.event_mask(event_mask).border_pixel(0);
276
277            if window_attrs.platform_specific.x11.override_redirect {
278                aux = aux.override_redirect(true as u32);
279            }
280
281            // Add a colormap if needed.
282            let colormap_visual = match window_attrs.platform_specific.x11.visual_id {
283                Some(vi) => Some(vi),
284                None if require_colormap => Some(visual),
285                _ => None,
286            };
287
288            if let Some(visual) = colormap_visual {
289                let colormap = leap!(xconn.xcb_connection().generate_id());
290                leap!(xconn.xcb_connection().create_colormap(
291                    xproto::ColormapAlloc::NONE,
292                    colormap,
293                    root,
294                    visual,
295                ));
296                aux = aux.colormap(colormap);
297            } else {
298                aux = aux.colormap(0);
299            }
300
301            aux
302        };
303
304        // Figure out the window's parent.
305        let parent = window_attrs.platform_specific.x11.embed_window.unwrap_or(root);
306
307        // finally creating the window
308        let xwindow = {
309            let (x, y) = position.map_or((0, 0), Into::into);
310            let wid = leap!(xconn.xcb_connection().generate_id());
311            let result = xconn.xcb_connection().create_window(
312                depth,
313                wid,
314                parent,
315                x,
316                y,
317                dimensions.0.try_into().unwrap(),
318                dimensions.1.try_into().unwrap(),
319                0,
320                xproto::WindowClass::INPUT_OUTPUT,
321                visual,
322                &window_attributes,
323            );
324            leap!(leap!(result).check());
325
326            wid
327        };
328
329        // The COPY_FROM_PARENT is a special value for the visual used to copy
330        // the visual from the parent window, thus we have to query the visual
331        // we've got when we built the window above.
332        if visual == x11rb::COPY_FROM_PARENT {
333            visual = leap!(leap!(xconn
334                .xcb_connection()
335                .get_window_attributes(xwindow as xproto::Window))
336            .reply())
337            .visual;
338        }
339
340        #[allow(clippy::mutex_atomic)]
341        let mut window = UnownedWindow {
342            xconn: Arc::clone(xconn),
343            xwindow: xwindow as xproto::Window,
344            visual,
345            root,
346            screen_id,
347            selected_cursor: Default::default(),
348            cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
349            cursor_visible: Mutex::new(true),
350            ime_sender: Mutex::new(event_loop.ime_sender.clone()),
351            shared_state: SharedState::new(guessed_monitor, &window_attrs),
352            redraw_sender: event_loop.redraw_sender.clone(),
353            activation_sender: event_loop.activation_sender.clone(),
354        };
355
356        // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window
357        // title to determine placement/etc., so doing this after mapping would cause the WM to
358        // act on the wrong title state.
359        leap!(window.set_title_inner(&window_attrs.title)).ignore_error();
360        leap!(window.set_decorations_inner(window_attrs.decorations)).ignore_error();
361
362        if let Some(theme) = window_attrs.preferred_theme {
363            leap!(window.set_theme_inner(Some(theme))).ignore_error();
364        }
365
366        // Embed the window if needed.
367        if window_attrs.platform_specific.x11.embed_window.is_some() {
368            window.embed_window()?;
369        }
370
371        {
372            // Enable drag and drop (TODO: extend API to make this toggleable)
373            {
374                let dnd_aware_atom = atoms[XdndAware];
375                let version = &[5u32]; // Latest version; hasn't changed since 2002
376                leap!(xconn.change_property(
377                    window.xwindow,
378                    dnd_aware_atom,
379                    u32::from(xproto::AtomEnum::ATOM),
380                    xproto::PropMode::REPLACE,
381                    version,
382                ))
383                .ignore_error();
384            }
385
386            // WM_CLASS must be set *before* mapping the window, as per ICCCM!
387            {
388                let (instance, class) = if let Some(name) = window_attrs.platform_specific.name {
389                    (name.instance, name.general)
390                } else {
391                    let class = env::args_os()
392                        .next()
393                        .as_ref()
394                        // Default to the name of the binary (via argv[0])
395                        .and_then(|path| Path::new(path).file_name())
396                        .and_then(|bin_name| bin_name.to_str())
397                        .map(|bin_name| bin_name.to_owned())
398                        .unwrap_or_else(|| window_attrs.title.clone());
399                    // This environment variable is extraordinarily unlikely to actually be used...
400                    let instance = env::var("RESOURCE_NAME").ok().unwrap_or_else(|| class.clone());
401                    (instance, class)
402                };
403
404                let class = format!("{instance}\0{class}\0");
405                leap!(xconn.change_property(
406                    window.xwindow,
407                    xproto::Atom::from(xproto::AtomEnum::WM_CLASS),
408                    xproto::Atom::from(xproto::AtomEnum::STRING),
409                    xproto::PropMode::REPLACE,
410                    class.as_bytes(),
411                ))
412                .ignore_error();
413            }
414
415            if let Some(flusher) = leap!(window.set_pid()) {
416                flusher.ignore_error()
417            }
418
419            leap!(window.set_window_types(window_attrs.platform_specific.x11.x11_window_types))
420                .ignore_error();
421
422            // Set size hints.
423            let mut min_inner_size =
424                window_attrs.min_inner_size.map(|size| size.to_physical::<u32>(scale_factor));
425            let mut max_inner_size =
426                window_attrs.max_inner_size.map(|size| size.to_physical::<u32>(scale_factor));
427
428            if !window_attrs.resizable {
429                if util::wm_name_is_one_of(&["Xfwm4"]) {
430                    warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
431                } else {
432                    max_inner_size = Some(dimensions.into());
433                    min_inner_size = Some(dimensions.into());
434                }
435            }
436
437            let shared_state = window.shared_state.get_mut().unwrap();
438            shared_state.min_inner_size = min_inner_size.map(Into::into);
439            shared_state.max_inner_size = max_inner_size.map(Into::into);
440            shared_state.resize_increments = window_attrs.resize_increments;
441            shared_state.base_size = window_attrs.platform_specific.x11.base_size;
442
443            let normal_hints = WmSizeHints {
444                position: position.map(|PhysicalPosition { x, y }| {
445                    (WmSizeHintsSpecification::UserSpecified, x, y)
446                }),
447                size: Some((
448                    WmSizeHintsSpecification::UserSpecified,
449                    cast_dimension_to_hint(dimensions.0),
450                    cast_dimension_to_hint(dimensions.1),
451                )),
452                max_size: max_inner_size.map(cast_physical_size_to_hint),
453                min_size: min_inner_size.map(cast_physical_size_to_hint),
454                size_increment: window_attrs
455                    .resize_increments
456                    .map(|size| cast_size_to_hint(size, scale_factor)),
457                base_size: window_attrs
458                    .platform_specific
459                    .x11
460                    .base_size
461                    .map(|size| cast_size_to_hint(size, scale_factor)),
462                aspect: None,
463                win_gravity: None,
464            };
465            leap!(leap!(normal_hints.set(
466                xconn.xcb_connection(),
467                window.xwindow as xproto::Window,
468                xproto::AtomEnum::WM_NORMAL_HINTS,
469            ))
470            .check());
471
472            // Set window icons
473            if let Some(icon) = window_attrs.window_icon {
474                leap!(window.set_icon_inner(icon.inner)).ignore_error();
475            }
476
477            // Opt into handling window close
478            let result = xconn.xcb_connection().change_property(
479                xproto::PropMode::REPLACE,
480                window.xwindow,
481                atoms[WM_PROTOCOLS],
482                xproto::AtomEnum::ATOM,
483                32,
484                2,
485                bytemuck::cast_slice::<xproto::Atom, u8>(&[
486                    atoms[WM_DELETE_WINDOW],
487                    atoms[_NET_WM_PING],
488                ]),
489            );
490            leap!(result).ignore_error();
491
492            // Select XInput2 events
493            let mask = xinput::XIEventMask::MOTION
494                | xinput::XIEventMask::BUTTON_PRESS
495                | xinput::XIEventMask::BUTTON_RELEASE
496                | xinput::XIEventMask::ENTER
497                | xinput::XIEventMask::LEAVE
498                | xinput::XIEventMask::FOCUS_IN
499                | xinput::XIEventMask::FOCUS_OUT
500                | xinput::XIEventMask::TOUCH_BEGIN
501                | xinput::XIEventMask::TOUCH_UPDATE
502                | xinput::XIEventMask::TOUCH_END;
503            leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask))
504                .ignore_error();
505
506            // Set visibility (map window)
507            if window_attrs.visible {
508                leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();
509                leap!(xconn.xcb_connection().configure_window(
510                    xwindow,
511                    &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE)
512                ))
513                .ignore_error();
514            }
515
516            // Attempt to make keyboard input repeat detectable
517            unsafe {
518                let mut supported_ptr = ffi::False;
519                (xconn.xlib.XkbSetDetectableAutoRepeat)(
520                    xconn.display,
521                    ffi::True,
522                    &mut supported_ptr,
523                );
524                if supported_ptr == ffi::False {
525                    return Err(os_error!(OsError::Misc("`XkbSetDetectableAutoRepeat` failed")));
526                }
527            }
528
529            // Try to create input context for the window.
530            if let Some(ime) = event_loop.ime.as_ref() {
531                let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false);
532                leap!(result);
533            }
534
535            // These properties must be set after mapping
536            if window_attrs.maximized {
537                leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error();
538            }
539            if window_attrs.fullscreen.is_some() {
540                if let Some(flusher) =
541                    leap!(window
542                        .set_fullscreen_inner(window_attrs.fullscreen.clone().map(Into::into)))
543                {
544                    flusher.ignore_error()
545                }
546
547                if let Some(PhysicalPosition { x, y }) = position {
548                    let shared_state = window.shared_state.get_mut().unwrap();
549
550                    shared_state.restore_position = Some((x, y));
551                }
552            }
553
554            leap!(window.set_window_level_inner(window_attrs.window_level)).ignore_error();
555        }
556
557        window.set_cursor(window_attrs.cursor);
558
559        // Remove the startup notification if we have one.
560        if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() {
561            leap!(xconn.remove_activation_token(xwindow, &startup.token));
562        }
563
564        // We never want to give the user a broken window, since by then, it's too late to handle.
565        let window = leap!(xconn.sync_with_server().map(|_| window));
566
567        Ok(window)
568    }
569
570    /// Embed this window into a parent window.
571    pub(super) fn embed_window(&self) -> Result<(), RootOsError> {
572        let atoms = self.xconn.atoms();
573        leap!(leap!(self.xconn.change_property(
574            self.xwindow,
575            atoms[_XEMBED],
576            atoms[_XEMBED],
577            xproto::PropMode::REPLACE,
578            &[0u32, 1u32],
579        ))
580        .check());
581
582        Ok(())
583    }
584
585    pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> {
586        self.shared_state.lock().unwrap()
587    }
588
589    fn set_pid(&self) -> Result<Option<VoidCookie<'_>>, X11Error> {
590        let atoms = self.xconn.atoms();
591        let pid_atom = atoms[_NET_WM_PID];
592        let client_machine_atom = atoms[WM_CLIENT_MACHINE];
593
594        // Get the hostname and the PID.
595        let uname = rustix::system::uname();
596        let pid = rustix::process::getpid();
597
598        self.xconn
599            .change_property(
600                self.xwindow,
601                pid_atom,
602                xproto::Atom::from(xproto::AtomEnum::CARDINAL),
603                xproto::PropMode::REPLACE,
604                &[pid.as_raw_nonzero().get() as util::Cardinal],
605            )?
606            .ignore_error();
607        let flusher = self.xconn.change_property(
608            self.xwindow,
609            client_machine_atom,
610            xproto::Atom::from(xproto::AtomEnum::STRING),
611            xproto::PropMode::REPLACE,
612            uname.nodename().to_bytes(),
613        );
614        flusher.map(Some)
615    }
616
617    fn set_window_types(&self, window_types: Vec<WindowType>) -> Result<VoidCookie<'_>, X11Error> {
618        let atoms = self.xconn.atoms();
619        let hint_atom = atoms[_NET_WM_WINDOW_TYPE];
620        let atoms: Vec<_> = window_types.iter().map(|t| t.as_atom(&self.xconn)).collect();
621
622        self.xconn.change_property(
623            self.xwindow,
624            hint_atom,
625            xproto::Atom::from(xproto::AtomEnum::ATOM),
626            xproto::PropMode::REPLACE,
627            &atoms,
628        )
629    }
630
631    pub fn set_theme_inner(&self, theme: Option<Theme>) -> Result<VoidCookie<'_>, X11Error> {
632        let atoms = self.xconn.atoms();
633        let hint_atom = atoms[_GTK_THEME_VARIANT];
634        let utf8_atom = atoms[UTF8_STRING];
635        let variant = match theme {
636            Some(Theme::Dark) => "dark",
637            Some(Theme::Light) => "light",
638            None => "dark",
639        };
640        let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte");
641        self.xconn.change_property(
642            self.xwindow,
643            hint_atom,
644            utf8_atom,
645            xproto::PropMode::REPLACE,
646            variant.as_bytes(),
647        )
648    }
649
650    #[inline]
651    pub fn set_theme(&self, theme: Option<Theme>) {
652        self.set_theme_inner(theme).expect("Failed to change window theme").ignore_error();
653
654        self.xconn.flush_requests().expect("Failed to change window theme");
655    }
656
657    fn set_netwm(
658        &self,
659        operation: util::StateOperation,
660        properties: (u32, u32, u32, u32),
661    ) -> Result<VoidCookie<'_>, X11Error> {
662        let atoms = self.xconn.atoms();
663        let state_atom = atoms[_NET_WM_STATE];
664        self.xconn.send_client_msg(
665            self.xwindow,
666            self.root,
667            state_atom,
668            Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY),
669            [operation as u32, properties.0, properties.1, properties.2, properties.3],
670        )
671    }
672
673    fn set_fullscreen_hint(&self, fullscreen: bool) -> Result<VoidCookie<'_>, X11Error> {
674        let atoms = self.xconn.atoms();
675        let fullscreen_atom = atoms[_NET_WM_STATE_FULLSCREEN];
676        let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0));
677
678        if fullscreen {
679            // Ensure that the fullscreen window receives input focus to prevent
680            // locking up the user's display.
681            self.xconn
682                .xcb_connection()
683                .set_input_focus(xproto::InputFocus::PARENT, self.xwindow, x11rb::CURRENT_TIME)?
684                .ignore_error();
685        }
686
687        flusher
688    }
689
690    fn set_fullscreen_inner(
691        &self,
692        fullscreen: Option<Fullscreen>,
693    ) -> Result<Option<VoidCookie<'_>>, X11Error> {
694        let mut shared_state_lock = self.shared_state_lock();
695
696        match shared_state_lock.visibility {
697            // Setting fullscreen on a window that is not visible will generate an error.
698            Visibility::No | Visibility::YesWait => {
699                shared_state_lock.desired_fullscreen = Some(fullscreen);
700                return Ok(None);
701            },
702            Visibility::Yes => (),
703        }
704
705        let old_fullscreen = shared_state_lock.fullscreen.clone();
706        if old_fullscreen == fullscreen {
707            return Ok(None);
708        }
709        shared_state_lock.fullscreen.clone_from(&fullscreen);
710
711        match (&old_fullscreen, &fullscreen) {
712            // Store the desktop video mode before entering exclusive
713            // fullscreen, so we can restore it upon exit, as XRandR does not
714            // provide a mechanism to set this per app-session or restore this
715            // to the desktop video mode as macOS and Windows do
716            (&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))))
717            | (
718                &Some(Fullscreen::Borderless(_)),
719                &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))),
720            ) => {
721                let monitor = video_mode.monitor.as_ref().unwrap();
722                shared_state_lock.desktop_video_mode = Some((
723                    monitor.id,
724                    self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode"),
725                ));
726            },
727            // Restore desktop video mode upon exiting exclusive fullscreen
728            (&Some(Fullscreen::Exclusive(_)), &None)
729            | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
730                let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
731                self.xconn
732                    .set_crtc_config(monitor_id, mode_id)
733                    .expect("failed to restore desktop video mode");
734            },
735            _ => (),
736        }
737
738        drop(shared_state_lock);
739
740        match fullscreen {
741            None => {
742                let flusher = self.set_fullscreen_hint(false);
743                let mut shared_state_lock = self.shared_state_lock();
744                if let Some(position) = shared_state_lock.restore_position.take() {
745                    drop(shared_state_lock);
746                    self.set_position_inner(position.0, position.1)
747                        .expect_then_ignore_error("Failed to restore window position");
748                }
749                flusher.map(Some)
750            },
751            Some(fullscreen) => {
752                let (video_mode, monitor) = match fullscreen {
753                    Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => {
754                        (Some(video_mode), video_mode.monitor.clone().unwrap())
755                    },
756                    Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => {
757                        (None, monitor)
758                    },
759                    Fullscreen::Borderless(None) => {
760                        (None, self.shared_state_lock().last_monitor.clone())
761                    },
762                    #[cfg(wayland_platform)]
763                    _ => unreachable!(),
764                };
765
766                // Don't set fullscreen on an invalid dummy monitor handle
767                if monitor.is_dummy() {
768                    return Ok(None);
769                }
770
771                if let Some(video_mode) = video_mode {
772                    // FIXME: this is actually not correct if we're setting the
773                    // video mode to a resolution higher than the current
774                    // desktop resolution, because XRandR does not automatically
775                    // reposition the monitors to the right and below this
776                    // monitor.
777                    //
778                    // What ends up happening is we will get the fullscreen
779                    // window showing up on those monitors as well, because
780                    // their virtual position now overlaps with the monitor that
781                    // we just made larger..
782                    //
783                    // It'd be quite a bit of work to handle this correctly (and
784                    // nobody else seems to bother doing this correctly either),
785                    // so we're just leaving this broken. Fixing this would
786                    // involve storing all CRTCs upon entering fullscreen,
787                    // restoring them upon exit, and after entering fullscreen,
788                    // repositioning displays to the right and below this
789                    // display. I think there would still be edge cases that are
790                    // difficult or impossible to handle correctly, e.g. what if
791                    // a new monitor was plugged in while in fullscreen?
792                    //
793                    // I think we might just want to disallow setting the video
794                    // mode higher than the current desktop video mode (I'm sure
795                    // this will make someone unhappy, but it's very unusual for
796                    // games to want to do this anyway).
797                    self.xconn
798                        .set_crtc_config(monitor.id, video_mode.native_mode)
799                        .expect("failed to set video mode");
800                }
801
802                let window_position = self.outer_position_physical();
803                self.shared_state_lock().restore_position = Some(window_position);
804                let monitor_origin: (i32, i32) = monitor.position().into();
805                self.set_position_inner(monitor_origin.0, monitor_origin.1)
806                    .expect_then_ignore_error("Failed to set window position");
807                self.set_fullscreen_hint(true).map(Some)
808            },
809        }
810    }
811
812    #[inline]
813    pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
814        let shared_state = self.shared_state_lock();
815
816        shared_state.desired_fullscreen.clone().unwrap_or_else(|| shared_state.fullscreen.clone())
817    }
818
819    #[inline]
820    pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
821        if let Some(flusher) =
822            self.set_fullscreen_inner(fullscreen).expect("Failed to change window fullscreen state")
823        {
824            flusher.check().expect("Failed to change window fullscreen state");
825            self.invalidate_cached_frame_extents();
826        }
827    }
828
829    // Called by EventProcessor when a VisibilityNotify event is received
830    pub(crate) fn visibility_notify(&self) {
831        let mut shared_state = self.shared_state_lock();
832
833        match shared_state.visibility {
834            Visibility::No => self
835                .xconn
836                .xcb_connection()
837                .unmap_window(self.xwindow)
838                .expect_then_ignore_error("Failed to unmap window"),
839            Visibility::Yes => (),
840            Visibility::YesWait => {
841                shared_state.visibility = Visibility::Yes;
842
843                if let Some(fullscreen) = shared_state.desired_fullscreen.take() {
844                    drop(shared_state);
845                    self.set_fullscreen(fullscreen);
846                }
847            },
848        }
849    }
850
851    pub fn current_monitor(&self) -> Option<X11MonitorHandle> {
852        Some(self.shared_state_lock().last_monitor.clone())
853    }
854
855    pub fn available_monitors(&self) -> Vec<X11MonitorHandle> {
856        self.xconn.available_monitors().expect("Failed to get available monitors")
857    }
858
859    pub fn primary_monitor(&self) -> Option<X11MonitorHandle> {
860        Some(self.xconn.primary_monitor().expect("Failed to get primary monitor"))
861    }
862
863    #[inline]
864    pub fn is_minimized(&self) -> Option<bool> {
865        let atoms = self.xconn.atoms();
866        let state_atom = atoms[_NET_WM_STATE];
867        let state = self.xconn.get_property(
868            self.xwindow,
869            state_atom,
870            xproto::Atom::from(xproto::AtomEnum::ATOM),
871        );
872        let hidden_atom = atoms[_NET_WM_STATE_HIDDEN];
873
874        Some(match state {
875            Ok(atoms) => {
876                atoms.iter().any(|atom: &xproto::Atom| *atom as xproto::Atom == hidden_atom)
877            },
878            _ => false,
879        })
880    }
881
882    /// Refresh the API for the given monitor.
883    #[inline]
884    pub(super) fn refresh_dpi_for_monitor<T: 'static>(
885        &self,
886        new_monitor: &X11MonitorHandle,
887        maybe_prev_scale_factor: Option<f64>,
888        mut callback: impl FnMut(Event<T>),
889    ) {
890        // Check if the self is on this monitor
891        let monitor = self.shared_state_lock().last_monitor.clone();
892        if monitor.name == new_monitor.name {
893            let (width, height) = self.inner_size_physical();
894            let (new_width, new_height) = self.adjust_for_dpi(
895                // If we couldn't determine the previous scale
896                // factor (e.g., because all monitors were closed
897                // before), just pick whatever the current monitor
898                // has set as a baseline.
899                maybe_prev_scale_factor.unwrap_or(monitor.scale_factor),
900                new_monitor.scale_factor,
901                width,
902                height,
903                &self.shared_state_lock(),
904            );
905
906            let window_id = crate::window::WindowId(self.id());
907            let old_inner_size = PhysicalSize::new(width, height);
908            let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height)));
909            callback(Event::WindowEvent {
910                window_id,
911                event: WindowEvent::ScaleFactorChanged {
912                    scale_factor: new_monitor.scale_factor,
913                    inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)),
914                },
915            });
916
917            let new_inner_size = *inner_size.lock().unwrap();
918            drop(inner_size);
919
920            if new_inner_size != old_inner_size {
921                let (new_width, new_height) = new_inner_size.into();
922                self.request_inner_size_physical(new_width, new_height);
923            }
924        }
925    }
926
927    fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> {
928        let atoms = self.xconn.atoms();
929
930        if minimized {
931            let root_window = self.xconn.default_root().root;
932
933            self.xconn.send_client_msg(
934                self.xwindow,
935                root_window,
936                atoms[WM_CHANGE_STATE],
937                Some(
938                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
939                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
940                ),
941                [3u32, 0, 0, 0, 0],
942            )
943        } else {
944            self.xconn.send_client_msg(
945                self.xwindow,
946                self.root,
947                atoms[_NET_ACTIVE_WINDOW],
948                Some(
949                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
950                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
951                ),
952                [1, x11rb::CURRENT_TIME, 0, 0, 0],
953            )
954        }
955    }
956
957    #[inline]
958    pub fn set_minimized(&self, minimized: bool) {
959        self.set_minimized_inner(minimized)
960            .expect_then_ignore_error("Failed to change window minimization");
961
962        self.xconn.flush_requests().expect("Failed to change window minimization");
963    }
964
965    #[inline]
966    pub fn is_maximized(&self) -> bool {
967        let atoms = self.xconn.atoms();
968        let state_atom = atoms[_NET_WM_STATE];
969        let state = self.xconn.get_property(
970            self.xwindow,
971            state_atom,
972            xproto::Atom::from(xproto::AtomEnum::ATOM),
973        );
974        let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
975        let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
976        match state {
977            Ok(atoms) => {
978                let horz_maximized = atoms.contains(&horz_atom);
979                let vert_maximized = atoms.contains(&vert_atom);
980                horz_maximized && vert_maximized
981            },
982            _ => false,
983        }
984    }
985
986    fn set_maximized_inner(&self, maximized: bool) -> Result<VoidCookie<'_>, X11Error> {
987        let atoms = self.xconn.atoms();
988        let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
989        let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT];
990
991        self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0))
992    }
993
994    #[inline]
995    pub fn set_maximized(&self, maximized: bool) {
996        self.set_maximized_inner(maximized)
997            .expect_then_ignore_error("Failed to change window maximization");
998        self.xconn.flush_requests().expect("Failed to change window maximization");
999        self.invalidate_cached_frame_extents();
1000    }
1001
1002    fn set_title_inner(&self, title: &str) -> Result<VoidCookie<'_>, X11Error> {
1003        let atoms = self.xconn.atoms();
1004
1005        let title = CString::new(title).expect("Window title contained null byte");
1006        self.xconn
1007            .change_property(
1008                self.xwindow,
1009                xproto::Atom::from(xproto::AtomEnum::WM_NAME),
1010                xproto::Atom::from(xproto::AtomEnum::STRING),
1011                xproto::PropMode::REPLACE,
1012                title.as_bytes(),
1013            )?
1014            .ignore_error();
1015        self.xconn.change_property(
1016            self.xwindow,
1017            atoms[_NET_WM_NAME],
1018            atoms[UTF8_STRING],
1019            xproto::PropMode::REPLACE,
1020            title.as_bytes(),
1021        )
1022    }
1023
1024    #[inline]
1025    pub fn set_title(&self, title: &str) {
1026        self.set_title_inner(title).expect_then_ignore_error("Failed to set window title");
1027
1028        self.xconn.flush_requests().expect("Failed to set window title");
1029    }
1030
1031    #[inline]
1032    pub fn set_transparent(&self, _transparent: bool) {}
1033
1034    #[inline]
1035    pub fn set_blur(&self, _blur: bool) {}
1036
1037    fn set_decorations_inner(&self, decorations: bool) -> Result<VoidCookie<'_>, X11Error> {
1038        self.shared_state_lock().is_decorated = decorations;
1039        let mut hints = self.xconn.get_motif_hints(self.xwindow);
1040
1041        hints.set_decorations(decorations);
1042
1043        self.xconn.set_motif_hints(self.xwindow, &hints)
1044    }
1045
1046    #[inline]
1047    pub fn set_decorations(&self, decorations: bool) {
1048        self.set_decorations_inner(decorations)
1049            .expect_then_ignore_error("Failed to set decoration state");
1050        self.xconn.flush_requests().expect("Failed to set decoration state");
1051        self.invalidate_cached_frame_extents();
1052    }
1053
1054    #[inline]
1055    pub fn is_decorated(&self) -> bool {
1056        self.shared_state_lock().is_decorated
1057    }
1058
1059    fn set_maximizable_inner(&self, maximizable: bool) -> Result<VoidCookie<'_>, X11Error> {
1060        let mut hints = self.xconn.get_motif_hints(self.xwindow);
1061
1062        hints.set_maximizable(maximizable);
1063
1064        self.xconn.set_motif_hints(self.xwindow, &hints)
1065    }
1066
1067    fn toggle_atom(&self, atom_name: AtomName, enable: bool) -> Result<VoidCookie<'_>, X11Error> {
1068        let atoms = self.xconn.atoms();
1069        let atom = atoms[atom_name];
1070        self.set_netwm(enable.into(), (atom, 0, 0, 0))
1071    }
1072
1073    fn set_window_level_inner(&self, level: WindowLevel) -> Result<VoidCookie<'_>, X11Error> {
1074        self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)?.ignore_error();
1075        self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom)
1076    }
1077
1078    #[inline]
1079    pub fn set_window_level(&self, level: WindowLevel) {
1080        self.set_window_level_inner(level)
1081            .expect_then_ignore_error("Failed to set window-level state");
1082        self.xconn.flush_requests().expect("Failed to set window-level state");
1083    }
1084
1085    fn set_icon_inner(&self, icon: PlatformIcon) -> Result<VoidCookie<'_>, X11Error> {
1086        let atoms = self.xconn.atoms();
1087        let icon_atom = atoms[_NET_WM_ICON];
1088        let data = icon.to_cardinals();
1089        self.xconn.change_property(
1090            self.xwindow,
1091            icon_atom,
1092            xproto::Atom::from(xproto::AtomEnum::CARDINAL),
1093            xproto::PropMode::REPLACE,
1094            data.as_slice(),
1095        )
1096    }
1097
1098    fn unset_icon_inner(&self) -> Result<VoidCookie<'_>, X11Error> {
1099        let atoms = self.xconn.atoms();
1100        let icon_atom = atoms[_NET_WM_ICON];
1101        let empty_data: [util::Cardinal; 0] = [];
1102        self.xconn.change_property(
1103            self.xwindow,
1104            icon_atom,
1105            xproto::Atom::from(xproto::AtomEnum::CARDINAL),
1106            xproto::PropMode::REPLACE,
1107            &empty_data,
1108        )
1109    }
1110
1111    #[inline]
1112    pub(crate) fn set_window_icon(&self, icon: Option<PlatformIcon>) {
1113        match icon {
1114            Some(icon) => self.set_icon_inner(icon),
1115            None => self.unset_icon_inner(),
1116        }
1117        .expect_then_ignore_error("Failed to set icons");
1118
1119        self.xconn.flush_requests().expect("Failed to set icons");
1120    }
1121
1122    #[inline]
1123    pub fn set_visible(&self, visible: bool) {
1124        let mut shared_state = self.shared_state_lock();
1125
1126        match (visible, shared_state.visibility) {
1127            (true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => {
1128                return
1129            },
1130            _ => (),
1131        }
1132
1133        if visible {
1134            self.xconn
1135                .xcb_connection()
1136                .map_window(self.xwindow)
1137                .expect_then_ignore_error("Failed to call `xcb_map_window`");
1138            self.xconn
1139                .xcb_connection()
1140                .configure_window(
1141                    self.xwindow,
1142                    &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE),
1143                )
1144                .expect_then_ignore_error("Failed to call `xcb_configure_window`");
1145            self.xconn.flush_requests().expect("Failed to call XMapRaised");
1146            shared_state.visibility = Visibility::YesWait;
1147        } else {
1148            self.xconn
1149                .xcb_connection()
1150                .unmap_window(self.xwindow)
1151                .expect_then_ignore_error("Failed to call `xcb_unmap_window`");
1152            self.xconn.flush_requests().expect("Failed to call XUnmapWindow");
1153            shared_state.visibility = Visibility::No;
1154        }
1155    }
1156
1157    #[inline]
1158    pub fn is_visible(&self) -> Option<bool> {
1159        Some(self.shared_state_lock().visibility == Visibility::Yes)
1160    }
1161
1162    fn update_cached_frame_extents(&self) {
1163        let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root);
1164        self.shared_state_lock().frame_extents = Some(extents);
1165    }
1166
1167    pub(crate) fn invalidate_cached_frame_extents(&self) {
1168        self.shared_state_lock().frame_extents.take();
1169    }
1170
1171    pub(crate) fn outer_position_physical(&self) -> (i32, i32) {
1172        let extents = self.shared_state_lock().frame_extents.clone();
1173        if let Some(extents) = extents {
1174            let (x, y) = self.inner_position_physical();
1175            extents.inner_pos_to_outer(x, y)
1176        } else {
1177            self.update_cached_frame_extents();
1178            self.outer_position_physical()
1179        }
1180    }
1181
1182    #[inline]
1183    pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
1184        let extents = self.shared_state_lock().frame_extents.clone();
1185        if let Some(extents) = extents {
1186            let (x, y) = self.inner_position_physical();
1187            Ok(extents.inner_pos_to_outer(x, y).into())
1188        } else {
1189            self.update_cached_frame_extents();
1190            self.outer_position()
1191        }
1192    }
1193
1194    pub(crate) fn inner_position_physical(&self) -> (i32, i32) {
1195        // This should be okay to unwrap since the only error XTranslateCoordinates can return
1196        // is BadWindow, and if the window handle is bad we have bigger problems.
1197        self.xconn
1198            .translate_coords(self.xwindow, self.root)
1199            .map(|coords| (coords.dst_x.into(), coords.dst_y.into()))
1200            .unwrap()
1201    }
1202
1203    #[inline]
1204    pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
1205        Ok(self.inner_position_physical().into())
1206    }
1207
1208    pub(crate) fn set_position_inner(
1209        &self,
1210        mut x: i32,
1211        mut y: i32,
1212    ) -> Result<VoidCookie<'_>, X11Error> {
1213        // There are a few WMs that set client area position rather than window position, so
1214        // we'll translate for consistency.
1215        if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) {
1216            let extents = self.shared_state_lock().frame_extents.clone();
1217            if let Some(extents) = extents {
1218                x += cast_dimension_to_hint(extents.frame_extents.left);
1219                y += cast_dimension_to_hint(extents.frame_extents.top);
1220            } else {
1221                self.update_cached_frame_extents();
1222                return self.set_position_inner(x, y);
1223            }
1224        }
1225
1226        self.xconn
1227            .xcb_connection()
1228            .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y))
1229            .map_err(Into::into)
1230    }
1231
1232    pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
1233        self.set_position_inner(x, y).expect_then_ignore_error("Failed to call `XMoveWindow`");
1234    }
1235
1236    #[inline]
1237    pub fn set_outer_position(&self, position: Position) {
1238        let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
1239        self.set_position_physical(x, y);
1240    }
1241
1242    pub(crate) fn inner_size_physical(&self) -> (u32, u32) {
1243        // This should be okay to unwrap since the only error XGetGeometry can return
1244        // is BadWindow, and if the window handle is bad we have bigger problems.
1245        self.xconn
1246            .get_geometry(self.xwindow)
1247            .map(|geo| (geo.width.into(), geo.height.into()))
1248            .unwrap()
1249    }
1250
1251    #[inline]
1252    pub fn inner_size(&self) -> PhysicalSize<u32> {
1253        self.inner_size_physical().into()
1254    }
1255
1256    #[inline]
1257    pub fn outer_size(&self) -> PhysicalSize<u32> {
1258        let extents = self.shared_state_lock().frame_extents.clone();
1259        if let Some(extents) = extents {
1260            let (width, height) = self.inner_size_physical();
1261            extents.inner_size_to_outer(width, height).into()
1262        } else {
1263            self.update_cached_frame_extents();
1264            self.outer_size()
1265        }
1266    }
1267
1268    pub(crate) fn request_inner_size_physical(&self, width: u32, height: u32) {
1269        self.xconn
1270            .xcb_connection()
1271            .configure_window(
1272                self.xwindow,
1273                &xproto::ConfigureWindowAux::new().width(width).height(height),
1274            )
1275            .expect_then_ignore_error("Failed to call `xcb_configure_window`");
1276        self.xconn.flush_requests().expect("Failed to call XResizeWindow");
1277        // cursor_hittest needs to be reapplied after each window resize.
1278        if self.shared_state_lock().cursor_hittest.unwrap_or(false) {
1279            let _ = self.set_cursor_hittest(true);
1280        }
1281    }
1282
1283    #[inline]
1284    pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
1285        let scale_factor = self.scale_factor();
1286        let size = size.to_physical::<u32>(scale_factor).into();
1287        if !self.shared_state_lock().is_resizable {
1288            self.update_normal_hints(|normal_hints| {
1289                normal_hints.min_size = Some(size);
1290                normal_hints.max_size = Some(size);
1291            })
1292            .expect("Failed to call `XSetWMNormalHints`");
1293        }
1294        self.request_inner_size_physical(size.0 as u32, size.1 as u32);
1295
1296        None
1297    }
1298
1299    fn update_normal_hints<F>(&self, callback: F) -> Result<(), X11Error>
1300    where
1301        F: FnOnce(&mut WmSizeHints),
1302    {
1303        let mut normal_hints = WmSizeHints::get(
1304            self.xconn.xcb_connection(),
1305            self.xwindow as xproto::Window,
1306            xproto::AtomEnum::WM_NORMAL_HINTS,
1307        )?
1308        .reply()?
1309        .unwrap_or_default();
1310        callback(&mut normal_hints);
1311        normal_hints
1312            .set(
1313                self.xconn.xcb_connection(),
1314                self.xwindow as xproto::Window,
1315                xproto::AtomEnum::WM_NORMAL_HINTS,
1316            )?
1317            .ignore_error();
1318        Ok(())
1319    }
1320
1321    pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
1322        self.update_normal_hints(|normal_hints| {
1323            normal_hints.min_size =
1324                dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h)))
1325        })
1326        .expect("Failed to call `XSetWMNormalHints`");
1327    }
1328
1329    #[inline]
1330    pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
1331        self.shared_state_lock().min_inner_size = dimensions;
1332        let physical_dimensions =
1333            dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
1334        self.set_min_inner_size_physical(physical_dimensions);
1335    }
1336
1337    pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) {
1338        self.update_normal_hints(|normal_hints| {
1339            normal_hints.max_size =
1340                dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h)))
1341        })
1342        .expect("Failed to call `XSetWMNormalHints`");
1343    }
1344
1345    #[inline]
1346    pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
1347        self.shared_state_lock().max_inner_size = dimensions;
1348        let physical_dimensions =
1349            dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into());
1350        self.set_max_inner_size_physical(physical_dimensions);
1351    }
1352
1353    #[inline]
1354    pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
1355        WmSizeHints::get(
1356            self.xconn.xcb_connection(),
1357            self.xwindow as xproto::Window,
1358            xproto::AtomEnum::WM_NORMAL_HINTS,
1359        )
1360        .ok()
1361        .and_then(|cookie| cookie.reply().ok())
1362        .flatten()
1363        .and_then(|hints| hints.size_increment)
1364        .map(|(width, height)| (width as u32, height as u32).into())
1365    }
1366
1367    #[inline]
1368    pub fn set_resize_increments(&self, increments: Option<Size>) {
1369        self.shared_state_lock().resize_increments = increments;
1370        let physical_increments =
1371            increments.map(|increments| cast_size_to_hint(increments, self.scale_factor()));
1372        self.update_normal_hints(|hints| hints.size_increment = physical_increments)
1373            .expect("Failed to call `XSetWMNormalHints`");
1374    }
1375
1376    pub(crate) fn adjust_for_dpi(
1377        &self,
1378        old_scale_factor: f64,
1379        new_scale_factor: f64,
1380        width: u32,
1381        height: u32,
1382        shared_state: &SharedState,
1383    ) -> (u32, u32) {
1384        let scale_factor = new_scale_factor / old_scale_factor;
1385        self.update_normal_hints(|normal_hints| {
1386            let dpi_adjuster = |size: Size| -> (i32, i32) { cast_size_to_hint(size, scale_factor) };
1387            let max_size = shared_state.max_inner_size.map(dpi_adjuster);
1388            let min_size = shared_state.min_inner_size.map(dpi_adjuster);
1389            let resize_increments = shared_state.resize_increments.map(dpi_adjuster);
1390            let base_size = shared_state.base_size.map(dpi_adjuster);
1391
1392            normal_hints.max_size = max_size;
1393            normal_hints.min_size = min_size;
1394            normal_hints.size_increment = resize_increments;
1395            normal_hints.base_size = base_size;
1396        })
1397        .expect("Failed to update normal hints");
1398
1399        let new_width = (width as f64 * scale_factor).round() as u32;
1400        let new_height = (height as f64 * scale_factor).round() as u32;
1401
1402        (new_width, new_height)
1403    }
1404
1405    pub fn set_resizable(&self, resizable: bool) {
1406        if util::wm_name_is_one_of(&["Xfwm4"]) {
1407            // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS`
1408            // from being detected. This makes it impossible for resizing to be
1409            // re-enabled, and also breaks DPI scaling. As such, we choose the lesser of
1410            // two evils and do nothing.
1411            warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4");
1412            return;
1413        }
1414
1415        let (min_size, max_size) = if resizable {
1416            let shared_state_lock = self.shared_state_lock();
1417            (shared_state_lock.min_inner_size, shared_state_lock.max_inner_size)
1418        } else {
1419            let window_size = Some(Size::from(self.inner_size()));
1420            (window_size, window_size)
1421        };
1422        self.shared_state_lock().is_resizable = resizable;
1423
1424        self.set_maximizable_inner(resizable)
1425            .expect_then_ignore_error("Failed to call `XSetWMNormalHints`");
1426
1427        let scale_factor = self.scale_factor();
1428        let min_inner_size = min_size.map(|size| cast_size_to_hint(size, scale_factor));
1429        let max_inner_size = max_size.map(|size| cast_size_to_hint(size, scale_factor));
1430        self.update_normal_hints(|normal_hints| {
1431            normal_hints.min_size = min_inner_size;
1432            normal_hints.max_size = max_inner_size;
1433        })
1434        .expect("Failed to call `XSetWMNormalHints`");
1435    }
1436
1437    #[inline]
1438    pub fn is_resizable(&self) -> bool {
1439        self.shared_state_lock().is_resizable
1440    }
1441
1442    #[inline]
1443    pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {}
1444
1445    #[inline]
1446    pub fn enabled_buttons(&self) -> WindowButtons {
1447        WindowButtons::all()
1448    }
1449
1450    #[allow(dead_code)]
1451    #[inline]
1452    pub fn xlib_display(&self) -> *mut c_void {
1453        self.xconn.display as _
1454    }
1455
1456    #[allow(dead_code)]
1457    #[inline]
1458    pub fn xlib_window(&self) -> c_ulong {
1459        self.xwindow as ffi::Window
1460    }
1461
1462    #[inline]
1463    pub fn set_cursor(&self, cursor: Cursor) {
1464        match cursor {
1465            Cursor::Icon(icon) => {
1466                let old_cursor = replace(
1467                    &mut *self.selected_cursor.lock().unwrap(),
1468                    SelectedCursor::Named(icon),
1469                );
1470
1471                #[allow(clippy::mutex_atomic)]
1472                if SelectedCursor::Named(icon) != old_cursor && *self.cursor_visible.lock().unwrap()
1473                {
1474                    self.xconn.set_cursor_icon(self.xwindow, Some(icon));
1475                }
1476            },
1477            Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::X(cursor) }) => {
1478                #[allow(clippy::mutex_atomic)]
1479                if *self.cursor_visible.lock().unwrap() {
1480                    self.xconn.set_custom_cursor(self.xwindow, &cursor);
1481                }
1482
1483                *self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor);
1484            },
1485            #[cfg(wayland_platform)]
1486            Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::Wayland(_) }) => {
1487                tracing::error!("passed a Wayland cursor to X11 backend")
1488            },
1489        }
1490    }
1491
1492    #[inline]
1493    pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
1494        // We don't support the locked cursor yet, so ignore it early on.
1495        if mode == CursorGrabMode::Locked {
1496            return Err(ExternalError::NotSupported(NotSupportedError::new()));
1497        }
1498
1499        let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
1500        if mode == *grabbed_lock {
1501            return Ok(());
1502        }
1503
1504        // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
1505        // Therefore, this is common to both codepaths.
1506        self.xconn
1507            .xcb_connection()
1508            .ungrab_pointer(x11rb::CURRENT_TIME)
1509            .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`");
1510        *grabbed_lock = CursorGrabMode::None;
1511
1512        let result = match mode {
1513            CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| {
1514                ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into())))
1515            }),
1516            CursorGrabMode::Confined => {
1517                let result = self
1518                    .xconn
1519                    .xcb_connection()
1520                    .grab_pointer(
1521                        true as _,
1522                        self.xwindow,
1523                        xproto::EventMask::BUTTON_PRESS
1524                            | xproto::EventMask::BUTTON_RELEASE
1525                            | xproto::EventMask::ENTER_WINDOW
1526                            | xproto::EventMask::LEAVE_WINDOW
1527                            | xproto::EventMask::POINTER_MOTION
1528                            | xproto::EventMask::POINTER_MOTION_HINT
1529                            | xproto::EventMask::BUTTON1_MOTION
1530                            | xproto::EventMask::BUTTON2_MOTION
1531                            | xproto::EventMask::BUTTON3_MOTION
1532                            | xproto::EventMask::BUTTON4_MOTION
1533                            | xproto::EventMask::BUTTON5_MOTION
1534                            | xproto::EventMask::KEYMAP_STATE,
1535                        xproto::GrabMode::ASYNC,
1536                        xproto::GrabMode::ASYNC,
1537                        self.xwindow,
1538                        0u32,
1539                        x11rb::CURRENT_TIME,
1540                    )
1541                    .expect("Failed to call `grab_pointer`")
1542                    .reply()
1543                    .expect("Failed to receive reply from `grab_pointer`");
1544
1545                match result.status {
1546                    xproto::GrabStatus::SUCCESS => Ok(()),
1547                    xproto::GrabStatus::ALREADY_GRABBED => {
1548                        Err("Cursor could not be confined: already confined by another client")
1549                    },
1550                    xproto::GrabStatus::INVALID_TIME => {
1551                        Err("Cursor could not be confined: invalid time")
1552                    },
1553                    xproto::GrabStatus::NOT_VIEWABLE => {
1554                        Err("Cursor could not be confined: confine location not viewable")
1555                    },
1556                    xproto::GrabStatus::FROZEN => {
1557                        Err("Cursor could not be confined: frozen by another client")
1558                    },
1559                    _ => unreachable!(),
1560                }
1561                .map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err))))
1562            },
1563            CursorGrabMode::Locked => return Ok(()),
1564        };
1565
1566        if result.is_ok() {
1567            *grabbed_lock = mode;
1568        }
1569
1570        result
1571    }
1572
1573    #[inline]
1574    pub fn set_cursor_visible(&self, visible: bool) {
1575        #[allow(clippy::mutex_atomic)]
1576        let mut visible_lock = self.cursor_visible.lock().unwrap();
1577        if visible == *visible_lock {
1578            return;
1579        }
1580        let cursor =
1581            if visible { Some((*self.selected_cursor.lock().unwrap()).clone()) } else { None };
1582        *visible_lock = visible;
1583        drop(visible_lock);
1584        match cursor {
1585            Some(SelectedCursor::Custom(cursor)) => {
1586                self.xconn.set_custom_cursor(self.xwindow, &cursor);
1587            },
1588            Some(SelectedCursor::Named(cursor)) => {
1589                self.xconn.set_cursor_icon(self.xwindow, Some(cursor));
1590            },
1591            None => {
1592                self.xconn.set_cursor_icon(self.xwindow, None);
1593            },
1594        }
1595    }
1596
1597    #[inline]
1598    pub fn scale_factor(&self) -> f64 {
1599        self.shared_state_lock().last_monitor.scale_factor
1600    }
1601
1602    pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> {
1603        {
1604            self.xconn
1605                .xcb_connection()
1606                .warp_pointer(x11rb::NONE, self.xwindow, 0, 0, 0, 0, x as _, y as _)
1607                .map_err(|e| {
1608                    ExternalError::Os(os_error!(OsError::XError(X11Error::from(e).into())))
1609                })?;
1610            self.xconn.flush_requests().map_err(|e| {
1611                ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(e).into())))
1612            })
1613        }
1614    }
1615
1616    #[inline]
1617    pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
1618        let (x, y) = position.to_physical::<i32>(self.scale_factor()).into();
1619        self.set_cursor_position_physical(x, y)
1620    }
1621
1622    #[inline]
1623    pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
1624        // Implement cursor hittest for X11 by either setting an empty or full window input shape.
1625
1626        // In X11, every window has two "shapes":
1627        //   * Bounding shape: defines the visible outline of the window.
1628        //   * Input shape: defines the region of the window that receives pointer/keyboard events.
1629        // If the input shape is the full window rectangle, the window behaves normally.
1630        // If the input shape is empty, the window is completely click‑through.
1631        // Here, we implement hit test by mapping `hittest = true` to "restore a full input shape"
1632        // and `hittest = false` to "clear the input shape" (empty list of rectangles).
1633        let mut rectangles: Vec<Rectangle> = Vec::new();
1634        if hittest {
1635            let size = self.inner_size();
1636            rectangles.push(Rectangle {
1637                x: 0,
1638                y: 0,
1639                width: size.width as u16,
1640                height: size.height as u16,
1641            })
1642        }
1643
1644        self.xconn
1645            .xcb_connection()
1646            .shape_rectangles(
1647                SO::SET,
1648                SK::INPUT,
1649                ClipOrdering::UNSORTED,
1650                self.xwindow,
1651                0,
1652                0,
1653                &rectangles,
1654            )
1655            .map_err(|_e| ExternalError::Ignored)?;
1656        self.shared_state_lock().cursor_hittest = Some(hittest);
1657        Ok(())
1658    }
1659
1660    /// Moves the window while it is being dragged.
1661    pub fn drag_window(&self) -> Result<(), ExternalError> {
1662        self.drag_initiate(util::MOVERESIZE_MOVE)
1663    }
1664
1665    #[inline]
1666    pub fn show_window_menu(&self, _position: Position) {}
1667
1668    /// Resizes the window while it is being dragged.
1669    pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> {
1670        self.drag_initiate(match direction {
1671            ResizeDirection::East => util::MOVERESIZE_RIGHT,
1672            ResizeDirection::North => util::MOVERESIZE_TOP,
1673            ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT,
1674            ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT,
1675            ResizeDirection::South => util::MOVERESIZE_BOTTOM,
1676            ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT,
1677            ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT,
1678            ResizeDirection::West => util::MOVERESIZE_LEFT,
1679        })
1680    }
1681
1682    /// Initiates a drag operation while the left mouse button is pressed.
1683    fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> {
1684        let pointer = self
1685            .xconn
1686            .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER)
1687            .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?;
1688
1689        let window = self.inner_position().map_err(ExternalError::NotSupported)?;
1690
1691        let atoms = self.xconn.atoms();
1692        let message = atoms[_NET_WM_MOVERESIZE];
1693
1694        // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
1695        // if the cursor isn't currently grabbed
1696        let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap();
1697        self.xconn
1698            .xcb_connection()
1699            .ungrab_pointer(x11rb::CURRENT_TIME)
1700            .map_err(|err| {
1701                ExternalError::Os(os_error!(OsError::XError(X11Error::from(err).into())))
1702            })?
1703            .ignore_error();
1704        self.xconn.flush_requests().map_err(|err| {
1705            ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into())))
1706        })?;
1707        *grabbed_lock = CursorGrabMode::None;
1708
1709        // we keep the lock until we are done
1710        self.xconn
1711            .send_client_msg(
1712                self.xwindow,
1713                self.root,
1714                message,
1715                Some(
1716                    xproto::EventMask::SUBSTRUCTURE_REDIRECT
1717                        | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1718                ),
1719                [
1720                    (window.x + xinput_fp1616_to_float(pointer.win_x) as i32) as u32,
1721                    (window.y + xinput_fp1616_to_float(pointer.win_y) as i32) as u32,
1722                    action.try_into().unwrap(),
1723                    1, // Button 1
1724                    1,
1725                ],
1726            )
1727            .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?;
1728
1729        self.xconn.flush_requests().map_err(|err| {
1730            ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into())))
1731        })
1732    }
1733
1734    #[inline]
1735    pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) {
1736        let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into();
1737        let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position(
1738            self.xwindow as ffi::Window,
1739            x,
1740            y,
1741        ));
1742    }
1743
1744    #[inline]
1745    pub fn set_ime_allowed(&self, allowed: bool) {
1746        let _ = self
1747            .ime_sender
1748            .lock()
1749            .unwrap()
1750            .send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed));
1751    }
1752
1753    #[inline]
1754    pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}
1755
1756    #[inline]
1757    pub fn focus_window(&self) {
1758        let atoms = self.xconn.atoms();
1759        let state_atom = atoms[WM_STATE];
1760        let state_type_atom = atoms[CARD32];
1761        let is_minimized = if let Ok(state) =
1762            self.xconn.get_property::<u32>(self.xwindow, state_atom, state_type_atom)
1763        {
1764            state.contains(&super::ICONIC_STATE)
1765        } else {
1766            false
1767        };
1768        let is_visible = match self.shared_state_lock().visibility {
1769            Visibility::Yes => true,
1770            Visibility::YesWait | Visibility::No => false,
1771        };
1772
1773        if is_visible && !is_minimized {
1774            self.xconn
1775                .send_client_msg(
1776                    self.xwindow,
1777                    self.root,
1778                    atoms[_NET_ACTIVE_WINDOW],
1779                    Some(
1780                        xproto::EventMask::SUBSTRUCTURE_REDIRECT
1781                            | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1782                    ),
1783                    [1, x11rb::CURRENT_TIME, 0, 0, 0],
1784                )
1785                .expect_then_ignore_error("Failed to send client message");
1786            if let Err(e) = self.xconn.flush_requests() {
1787                tracing::error!(
1788                    "`flush` returned an error when focusing the window. Error was: {}",
1789                    e
1790                );
1791            }
1792        }
1793    }
1794
1795    #[inline]
1796    pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
1797        let mut wm_hints =
1798            WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
1799                .ok()
1800                .and_then(|cookie| cookie.reply().ok())
1801                .flatten()
1802                .unwrap_or_default();
1803
1804        wm_hints.urgent = request_type.is_some();
1805        wm_hints
1806            .set(self.xconn.xcb_connection(), self.xwindow as xproto::Window)
1807            .expect_then_ignore_error("Failed to set WM hints");
1808    }
1809
1810    #[inline]
1811    pub(crate) fn generate_activation_token(&self) -> Result<String, X11Error> {
1812        // Get the title from the WM_NAME property.
1813        let atoms = self.xconn.atoms();
1814        let title = {
1815            let title_bytes = self
1816                .xconn
1817                .get_property(self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING])
1818                .expect("Failed to get title");
1819
1820            String::from_utf8(title_bytes).expect("Bad title")
1821        };
1822
1823        // Get the activation token and then put it in the event queue.
1824        let token = self.xconn.request_activation_token(&title)?;
1825
1826        Ok(token)
1827    }
1828
1829    #[inline]
1830    pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> {
1831        let serial = AsyncRequestSerial::get();
1832        self.activation_sender
1833            .send((self.id(), serial))
1834            .expect("activation token channel should never be closed");
1835        Ok(serial)
1836    }
1837
1838    #[inline]
1839    pub fn id(&self) -> WindowId {
1840        WindowId(self.xwindow as _)
1841    }
1842
1843    #[inline]
1844    pub fn request_redraw(&self) {
1845        self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap();
1846    }
1847
1848    #[inline]
1849    pub fn pre_present_notify(&self) {
1850        // TODO timer
1851    }
1852
1853    #[cfg(feature = "rwh_04")]
1854    #[inline]
1855    pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle {
1856        let mut window_handle = rwh_04::XlibHandle::empty();
1857        window_handle.display = self.xlib_display();
1858        window_handle.window = self.xlib_window();
1859        window_handle.visual_id = self.visual as c_ulong;
1860        rwh_04::RawWindowHandle::Xlib(window_handle)
1861    }
1862
1863    #[cfg(feature = "rwh_05")]
1864    #[inline]
1865    pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle {
1866        let mut window_handle = rwh_05::XlibWindowHandle::empty();
1867        window_handle.window = self.xlib_window();
1868        window_handle.visual_id = self.visual as c_ulong;
1869        window_handle.into()
1870    }
1871
1872    #[cfg(feature = "rwh_05")]
1873    #[inline]
1874    pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle {
1875        let mut display_handle = rwh_05::XlibDisplayHandle::empty();
1876        display_handle.display = self.xlib_display();
1877        display_handle.screen = self.screen_id;
1878        display_handle.into()
1879    }
1880
1881    #[cfg(feature = "rwh_06")]
1882    #[inline]
1883    pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> {
1884        let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window());
1885        window_handle.visual_id = self.visual as c_ulong;
1886        Ok(window_handle.into())
1887    }
1888
1889    #[cfg(feature = "rwh_06")]
1890    #[inline]
1891    pub fn raw_display_handle_rwh_06(
1892        &self,
1893    ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> {
1894        Ok(rwh_06::XlibDisplayHandle::new(
1895            Some(
1896                std::ptr::NonNull::new(self.xlib_display())
1897                    .expect("display pointer should never be null"),
1898            ),
1899            self.screen_id,
1900        )
1901        .into())
1902    }
1903
1904    #[inline]
1905    pub fn theme(&self) -> Option<Theme> {
1906        None
1907    }
1908
1909    pub fn set_content_protected(&self, _protected: bool) {}
1910
1911    #[inline]
1912    pub fn has_focus(&self) -> bool {
1913        self.shared_state_lock().has_focus
1914    }
1915
1916    pub fn title(&self) -> String {
1917        String::new()
1918    }
1919}
1920
1921/// Cast a dimension value into a hinted dimension for `WmSizeHints`, clamping if too large.
1922fn cast_dimension_to_hint(val: u32) -> i32 {
1923    val.try_into().unwrap_or(i32::MAX)
1924}
1925
1926/// Use the above strategy to cast a physical size into a hinted size.
1927fn cast_physical_size_to_hint(size: PhysicalSize<u32>) -> (i32, i32) {
1928    let PhysicalSize { width, height } = size;
1929    (cast_dimension_to_hint(width), cast_dimension_to_hint(height))
1930}
1931
1932/// Use the above strategy to cast a size into a hinted size.
1933fn cast_size_to_hint(size: Size, scale_factor: f64) -> (i32, i32) {
1934    match size {
1935        Size::Physical(size) => cast_physical_size_to_hint(size),
1936        Size::Logical(size) => size.to_physical::<i32>(scale_factor).into(),
1937    }
1938}