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