winit/platform_impl/linux/x11/
window.rs

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