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