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