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