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