Skip to main content

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