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