Skip to main content

winit_x11/
window.rs

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