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