1use anyhow::{Context as _, anyhow};
2
3use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
4use crate::{
5 AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
6 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
7 Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
8 Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
9 WindowKind, WindowParams, X11ClientStatePtr, px, size,
10};
11
12use blade_graphics as gpu;
13use raw_window_handle as rwh;
14use util::{ResultExt, maybe};
15use x11rb::{
16 connection::Connection,
17 cookie::{Cookie, VoidCookie},
18 errors::ConnectionError,
19 properties::WmSizeHints,
20 protocol::{
21 sync,
22 xinput::{self, ConnectionExt as _},
23 xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
24 },
25 wrapper::ConnectionExt as _,
26 xcb_ffi::XCBConnection,
27};
28
29use std::{
30 cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
31 sync::Arc,
32};
33
34use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES};
35x11rb::atom_manager! {
36 pub XcbAtoms: AtomsCookie {
37 XA_ATOM,
38 XdndAware,
39 XdndStatus,
40 XdndEnter,
41 XdndLeave,
42 XdndPosition,
43 XdndSelection,
44 XdndDrop,
45 XdndFinished,
46 XdndTypeList,
47 XdndActionCopy,
48 TextUriList: b"text/uri-list",
49 UTF8_STRING,
50 TEXT,
51 STRING,
52 TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
53 TEXT_PLAIN: b"text/plain",
54 XDND_DATA,
55 WM_PROTOCOLS,
56 WM_DELETE_WINDOW,
57 WM_CHANGE_STATE,
58 _NET_WM_PID,
59 _NET_WM_NAME,
60 _NET_WM_STATE,
61 _NET_WM_STATE_MAXIMIZED_VERT,
62 _NET_WM_STATE_MAXIMIZED_HORZ,
63 _NET_WM_STATE_FULLSCREEN,
64 _NET_WM_STATE_HIDDEN,
65 _NET_WM_STATE_FOCUSED,
66 _NET_ACTIVE_WINDOW,
67 _NET_WM_SYNC_REQUEST,
68 _NET_WM_SYNC_REQUEST_COUNTER,
69 _NET_WM_BYPASS_COMPOSITOR,
70 _NET_WM_MOVERESIZE,
71 _NET_WM_WINDOW_TYPE,
72 _NET_WM_WINDOW_TYPE_NOTIFICATION,
73 _NET_WM_SYNC,
74 _NET_SUPPORTED,
75 _MOTIF_WM_HINTS,
76 _GTK_SHOW_WINDOW_MENU,
77 _GTK_FRAME_EXTENTS,
78 _GTK_EDGE_CONSTRAINTS,
79 _NET_CLIENT_LIST_STACKING,
80 }
81}
82
83fn query_render_extent(
84 xcb: &Rc<XCBConnection>,
85 x_window: xproto::Window,
86) -> anyhow::Result<gpu::Extent> {
87 let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
88 Ok(gpu::Extent {
89 width: reply.width as u32,
90 height: reply.height as u32,
91 depth: 1,
92 })
93}
94
95impl ResizeEdge {
96 fn to_moveresize(&self) -> u32 {
97 match self {
98 ResizeEdge::TopLeft => 0,
99 ResizeEdge::Top => 1,
100 ResizeEdge::TopRight => 2,
101 ResizeEdge::Right => 3,
102 ResizeEdge::BottomRight => 4,
103 ResizeEdge::Bottom => 5,
104 ResizeEdge::BottomLeft => 6,
105 ResizeEdge::Left => 7,
106 }
107 }
108}
109
110#[derive(Debug)]
111struct EdgeConstraints {
112 top_tiled: bool,
113 #[allow(dead_code)]
114 top_resizable: bool,
115
116 right_tiled: bool,
117 #[allow(dead_code)]
118 right_resizable: bool,
119
120 bottom_tiled: bool,
121 #[allow(dead_code)]
122 bottom_resizable: bool,
123
124 left_tiled: bool,
125 #[allow(dead_code)]
126 left_resizable: bool,
127}
128
129impl EdgeConstraints {
130 fn from_atom(atom: u32) -> Self {
131 EdgeConstraints {
132 top_tiled: (atom & (1 << 0)) != 0,
133 top_resizable: (atom & (1 << 1)) != 0,
134 right_tiled: (atom & (1 << 2)) != 0,
135 right_resizable: (atom & (1 << 3)) != 0,
136 bottom_tiled: (atom & (1 << 4)) != 0,
137 bottom_resizable: (atom & (1 << 5)) != 0,
138 left_tiled: (atom & (1 << 6)) != 0,
139 left_resizable: (atom & (1 << 7)) != 0,
140 }
141 }
142
143 fn to_tiling(&self) -> Tiling {
144 Tiling {
145 top: self.top_tiled,
146 right: self.right_tiled,
147 bottom: self.bottom_tiled,
148 left: self.left_tiled,
149 }
150 }
151}
152
153#[derive(Copy, Clone, Debug)]
154struct Visual {
155 id: xproto::Visualid,
156 colormap: u32,
157 depth: u8,
158}
159
160struct VisualSet {
161 inherit: Visual,
162 opaque: Option<Visual>,
163 transparent: Option<Visual>,
164 root: u32,
165 black_pixel: u32,
166}
167
168fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
169 let screen = &xcb.setup().roots[screen_index];
170 let mut set = VisualSet {
171 inherit: Visual {
172 id: screen.root_visual,
173 colormap: screen.default_colormap,
174 depth: screen.root_depth,
175 },
176 opaque: None,
177 transparent: None,
178 root: screen.root,
179 black_pixel: screen.black_pixel,
180 };
181
182 for depth_info in screen.allowed_depths.iter() {
183 for visual_type in depth_info.visuals.iter() {
184 let visual = Visual {
185 id: visual_type.visual_id,
186 colormap: 0,
187 depth: depth_info.depth,
188 };
189 log::debug!(
190 "Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
191 visual_type.visual_id,
192 visual_type.class,
193 depth_info.depth,
194 visual_type.bits_per_rgb_value,
195 visual_type.red_mask,
196 visual_type.green_mask,
197 visual_type.blue_mask,
198 );
199
200 if (
201 visual_type.red_mask,
202 visual_type.green_mask,
203 visual_type.blue_mask,
204 ) != (0xFF0000, 0xFF00, 0xFF)
205 {
206 continue;
207 }
208 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
209 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
210
211 if alpha_mask == 0 {
212 if set.opaque.is_none() {
213 set.opaque = Some(visual);
214 }
215 } else {
216 if set.transparent.is_none() {
217 set.transparent = Some(visual);
218 }
219 }
220 }
221 }
222
223 set
224}
225
226struct RawWindow {
227 connection: *mut c_void,
228 screen_id: usize,
229 window_id: u32,
230 visual_id: u32,
231}
232
233#[derive(Default)]
234pub struct Callbacks {
235 request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
236 input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
237 active_status_change: Option<Box<dyn FnMut(bool)>>,
238 hovered_status_change: Option<Box<dyn FnMut(bool)>>,
239 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
240 moved: Option<Box<dyn FnMut()>>,
241 should_close: Option<Box<dyn FnMut() -> bool>>,
242 close: Option<Box<dyn FnOnce()>>,
243 appearance_changed: Option<Box<dyn FnMut()>>,
244}
245
246pub struct X11WindowState {
247 pub destroyed: bool,
248 client: X11ClientStatePtr,
249 executor: ForegroundExecutor,
250 atoms: XcbAtoms,
251 x_root_window: xproto::Window,
252 pub(crate) counter_id: sync::Counter,
253 pub(crate) last_sync_counter: Option<sync::Int64>,
254 bounds: Bounds<Pixels>,
255 scale_factor: f32,
256 renderer: BladeRenderer,
257 display: Rc<dyn PlatformDisplay>,
258 input_handler: Option<PlatformInputHandler>,
259 appearance: WindowAppearance,
260 background_appearance: WindowBackgroundAppearance,
261 maximized_vertical: bool,
262 maximized_horizontal: bool,
263 hidden: bool,
264 active: bool,
265 hovered: bool,
266 fullscreen: bool,
267 client_side_decorations_supported: bool,
268 decorations: WindowDecorations,
269 edge_constraints: Option<EdgeConstraints>,
270 pub handle: AnyWindowHandle,
271 last_insets: [u32; 4],
272}
273
274impl X11WindowState {
275 fn is_transparent(&self) -> bool {
276 self.background_appearance != WindowBackgroundAppearance::Opaque
277 }
278}
279
280#[derive(Clone)]
281pub(crate) struct X11WindowStatePtr {
282 pub state: Rc<RefCell<X11WindowState>>,
283 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
284 xcb: Rc<XCBConnection>,
285 x_window: xproto::Window,
286}
287
288impl rwh::HasWindowHandle for RawWindow {
289 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
290 let Some(non_zero) = NonZeroU32::new(self.window_id) else {
291 log::error!("RawWindow.window_id zero when getting window handle.");
292 return Err(rwh::HandleError::Unavailable);
293 };
294 let mut handle = rwh::XcbWindowHandle::new(non_zero);
295 handle.visual_id = NonZeroU32::new(self.visual_id);
296 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
297 }
298}
299impl rwh::HasDisplayHandle for RawWindow {
300 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
301 let Some(non_zero) = NonNull::new(self.connection) else {
302 log::error!("Null RawWindow.connection when getting display handle.");
303 return Err(rwh::HandleError::Unavailable);
304 };
305 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
306 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
307 }
308}
309
310impl rwh::HasWindowHandle for X11Window {
311 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
312 unimplemented!()
313 }
314}
315impl rwh::HasDisplayHandle for X11Window {
316 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
317 unimplemented!()
318 }
319}
320
321fn check_reply<C, F>(
322 failure_context: F,
323 result: Result<VoidCookie<'_, Rc<XCBConnection>>, ConnectionError>,
324) -> anyhow::Result<()>
325where
326 C: Display + Send + Sync + 'static,
327 F: FnOnce() -> C,
328{
329 result
330 .map_err(|connection_error| anyhow!(connection_error))
331 .and_then(|response| {
332 response
333 .check()
334 .map_err(|error_response| anyhow!(error_response))
335 })
336 .with_context(failure_context)
337}
338
339fn get_reply<C, F, O>(
340 failure_context: F,
341 result: Result<Cookie<'_, Rc<XCBConnection>, O>, ConnectionError>,
342) -> anyhow::Result<O>
343where
344 C: Display + Send + Sync + 'static,
345 F: FnOnce() -> C,
346 O: x11rb::x11_utils::TryParse,
347{
348 result
349 .map_err(|connection_error| anyhow!(connection_error))
350 .and_then(|response| {
351 response
352 .reply()
353 .map_err(|error_response| anyhow!(error_response))
354 })
355 .with_context(failure_context)
356}
357
358impl X11WindowState {
359 pub fn new(
360 handle: AnyWindowHandle,
361 client: X11ClientStatePtr,
362 executor: ForegroundExecutor,
363 gpu_context: &BladeContext,
364 params: WindowParams,
365 xcb: &Rc<XCBConnection>,
366 client_side_decorations_supported: bool,
367 x_main_screen_index: usize,
368 x_window: xproto::Window,
369 atoms: &XcbAtoms,
370 scale_factor: f32,
371 appearance: WindowAppearance,
372 ) -> anyhow::Result<Self> {
373 let x_screen_index = params
374 .display_id
375 .map_or(x_main_screen_index, |did| did.0 as usize);
376
377 let visual_set = find_visuals(&xcb, x_screen_index);
378
379 let visual = match visual_set.transparent {
380 Some(visual) => visual,
381 None => {
382 log::warn!("Unable to find a transparent visual",);
383 visual_set.inherit
384 }
385 };
386 log::info!("Using {:?}", visual);
387
388 let colormap = if visual.colormap != 0 {
389 visual.colormap
390 } else {
391 let id = xcb.generate_id()?;
392 log::info!("Creating colormap {}", id);
393 check_reply(
394 || format!("X11 CreateColormap failed. id: {}", id),
395 xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),
396 )?;
397 id
398 };
399
400 let win_aux = xproto::CreateWindowAux::new()
401 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
402 .border_pixel(visual_set.black_pixel)
403 .colormap(colormap)
404 .event_mask(
405 xproto::EventMask::EXPOSURE
406 | xproto::EventMask::STRUCTURE_NOTIFY
407 | xproto::EventMask::FOCUS_CHANGE
408 | xproto::EventMask::KEY_PRESS
409 | xproto::EventMask::KEY_RELEASE
410 | EventMask::PROPERTY_CHANGE,
411 );
412
413 let mut bounds = params.bounds.to_device_pixels(scale_factor);
414 if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
415 log::warn!(
416 "Window bounds contain a zero value. height={}, width={}. Falling back to defaults.",
417 bounds.size.height.0,
418 bounds.size.width.0
419 );
420 bounds.size.width = 800.into();
421 bounds.size.height = 600.into();
422 }
423
424 check_reply(
425 || {
426 format!(
427 "X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
428 visual.depth,
429 x_window,
430 visual_set.root,
431 bounds.origin.x.0 + 2,
432 bounds.origin.y.0,
433 bounds.size.width.0,
434 bounds.size.height.0
435 )
436 },
437 xcb.create_window(
438 visual.depth,
439 x_window,
440 visual_set.root,
441 (bounds.origin.x.0 + 2) as i16,
442 bounds.origin.y.0 as i16,
443 bounds.size.width.0 as u16,
444 bounds.size.height.0 as u16,
445 0,
446 xproto::WindowClass::INPUT_OUTPUT,
447 visual.id,
448 &win_aux,
449 ),
450 )?;
451
452 // Collect errors during setup, so that window can be destroyed on failure.
453 let setup_result = maybe!({
454 let pid = std::process::id();
455 check_reply(
456 || "X11 ChangeProperty for _NET_WM_PID failed.",
457 xcb.change_property32(
458 xproto::PropMode::REPLACE,
459 x_window,
460 atoms._NET_WM_PID,
461 xproto::AtomEnum::CARDINAL,
462 &[pid],
463 ),
464 )?;
465
466 if let Some(size) = params.window_min_size {
467 let mut size_hints = WmSizeHints::new();
468 let min_size = (size.width.0 as i32, size.height.0 as i32);
469 size_hints.min_size = Some(min_size);
470 check_reply(
471 || {
472 format!(
473 "X11 change of WM_SIZE_HINTS failed. min_size: {:?}",
474 min_size
475 )
476 },
477 size_hints.set_normal_hints(xcb, x_window),
478 )?;
479 }
480
481 let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
482 if reply.x == 0 && reply.y == 0 {
483 bounds.origin.x.0 += 2;
484 // Work around a bug where our rendered content appears
485 // outside the window bounds when opened at the default position
486 // (14px, 49px on X + Gnome + Ubuntu 22).
487 let x = bounds.origin.x.0;
488 let y = bounds.origin.y.0;
489 check_reply(
490 || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
491 xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
492 )?;
493 }
494 if let Some(titlebar) = params.titlebar {
495 if let Some(title) = titlebar.title {
496 check_reply(
497 || "X11 ChangeProperty8 on window title failed.",
498 xcb.change_property8(
499 xproto::PropMode::REPLACE,
500 x_window,
501 xproto::AtomEnum::WM_NAME,
502 xproto::AtomEnum::STRING,
503 title.as_bytes(),
504 ),
505 )?;
506 }
507 }
508 if params.kind == WindowKind::PopUp {
509 check_reply(
510 || "X11 ChangeProperty32 setting window type for pop-up failed.",
511 xcb.change_property32(
512 xproto::PropMode::REPLACE,
513 x_window,
514 atoms._NET_WM_WINDOW_TYPE,
515 xproto::AtomEnum::ATOM,
516 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
517 ),
518 )?;
519 }
520
521 check_reply(
522 || "X11 ChangeProperty32 setting protocols failed.",
523 xcb.change_property32(
524 xproto::PropMode::REPLACE,
525 x_window,
526 atoms.WM_PROTOCOLS,
527 xproto::AtomEnum::ATOM,
528 &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
529 ),
530 )?;
531
532 get_reply(
533 || "X11 sync protocol initialize failed.",
534 sync::initialize(xcb, 3, 1),
535 )?;
536 let sync_request_counter = xcb.generate_id()?;
537 check_reply(
538 || "X11 sync CreateCounter failed.",
539 sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
540 )?;
541
542 check_reply(
543 || "X11 ChangeProperty32 setting sync request counter failed.",
544 xcb.change_property32(
545 xproto::PropMode::REPLACE,
546 x_window,
547 atoms._NET_WM_SYNC_REQUEST_COUNTER,
548 xproto::AtomEnum::CARDINAL,
549 &[sync_request_counter],
550 ),
551 )?;
552
553 check_reply(
554 || "X11 XiSelectEvents failed.",
555 xcb.xinput_xi_select_events(
556 x_window,
557 &[xinput::EventMask {
558 deviceid: XINPUT_ALL_DEVICE_GROUPS,
559 mask: vec![
560 xinput::XIEventMask::MOTION
561 | xinput::XIEventMask::BUTTON_PRESS
562 | xinput::XIEventMask::BUTTON_RELEASE
563 | xinput::XIEventMask::ENTER
564 | xinput::XIEventMask::LEAVE,
565 ],
566 }],
567 ),
568 )?;
569
570 check_reply(
571 || "X11 XiSelectEvents for device changes failed.",
572 xcb.xinput_xi_select_events(
573 x_window,
574 &[xinput::EventMask {
575 deviceid: XINPUT_ALL_DEVICES,
576 mask: vec![
577 xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
578 ],
579 }],
580 ),
581 )?;
582
583 xcb.flush().with_context(|| "X11 Flush failed.")?;
584
585 let renderer = {
586 let raw_window = RawWindow {
587 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
588 xcb,
589 ) as *mut _,
590 screen_id: x_screen_index,
591 window_id: x_window,
592 visual_id: visual.id,
593 };
594 let config = BladeSurfaceConfig {
595 // Note: this has to be done after the GPU init, or otherwise
596 // the sizes are immediately invalidated.
597 size: query_render_extent(xcb, x_window)?,
598 // We set it to transparent by default, even if we have client-side
599 // decorations, since those seem to work on X11 even without `true` here.
600 // If the window appearance changes, then the renderer will get updated
601 // too
602 transparent: false,
603 };
604 BladeRenderer::new(gpu_context, &raw_window, config)?
605 };
606
607 let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
608
609 Ok(Self {
610 client,
611 executor,
612 display,
613 x_root_window: visual_set.root,
614 bounds: bounds.to_pixels(scale_factor),
615 scale_factor,
616 renderer,
617 atoms: *atoms,
618 input_handler: None,
619 active: false,
620 hovered: false,
621 fullscreen: false,
622 maximized_vertical: false,
623 maximized_horizontal: false,
624 hidden: false,
625 appearance,
626 handle,
627 background_appearance: WindowBackgroundAppearance::Opaque,
628 destroyed: false,
629 client_side_decorations_supported,
630 decorations: WindowDecorations::Server,
631 last_insets: [0, 0, 0, 0],
632 edge_constraints: None,
633 counter_id: sync_request_counter,
634 last_sync_counter: None,
635 })
636 });
637
638 if setup_result.is_err() {
639 check_reply(
640 || "X11 DestroyWindow failed while cleaning it up after setup failure.",
641 xcb.destroy_window(x_window),
642 )?;
643 xcb.flush()
644 .with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?;
645 }
646
647 setup_result
648 }
649
650 fn content_size(&self) -> Size<Pixels> {
651 let size = self.renderer.viewport_size();
652 Size {
653 width: size.width.into(),
654 height: size.height.into(),
655 }
656 }
657}
658
659/// A handle to an X11 window which destroys it on Drop.
660pub struct X11WindowHandle {
661 id: xproto::Window,
662 xcb: Rc<XCBConnection>,
663}
664
665impl Drop for X11WindowHandle {
666 fn drop(&mut self) {
667 maybe!({
668 check_reply(
669 || "X11 DestroyWindow failed while dropping X11WindowHandle.",
670 self.xcb.destroy_window(self.id),
671 )?;
672 self.xcb
673 .flush()
674 .with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?;
675 anyhow::Ok(())
676 })
677 .log_err();
678 }
679}
680
681pub(crate) struct X11Window(pub X11WindowStatePtr);
682
683impl Drop for X11Window {
684 fn drop(&mut self) {
685 let mut state = self.0.state.borrow_mut();
686 state.renderer.destroy();
687
688 let destroy_x_window = maybe!({
689 check_reply(
690 || "X11 DestroyWindow failure.",
691 self.0.xcb.destroy_window(self.0.x_window),
692 )?;
693 self.0
694 .xcb
695 .flush()
696 .with_context(|| "X11 Flush failed after calling DestroyWindow.")?;
697
698 anyhow::Ok(())
699 })
700 .log_err();
701
702 if destroy_x_window.is_some() {
703 // Mark window as destroyed so that we can filter out when X11 events
704 // for it still come in.
705 state.destroyed = true;
706
707 let this_ptr = self.0.clone();
708 let client_ptr = state.client.clone();
709 state
710 .executor
711 .spawn(async move {
712 this_ptr.close();
713 client_ptr.drop_window(this_ptr.x_window);
714 })
715 .detach();
716 }
717
718 drop(state);
719 }
720}
721
722enum WmHintPropertyState {
723 // Remove = 0,
724 // Add = 1,
725 Toggle = 2,
726}
727
728impl X11Window {
729 pub fn new(
730 handle: AnyWindowHandle,
731 client: X11ClientStatePtr,
732 executor: ForegroundExecutor,
733 gpu_context: &BladeContext,
734 params: WindowParams,
735 xcb: &Rc<XCBConnection>,
736 client_side_decorations_supported: bool,
737 x_main_screen_index: usize,
738 x_window: xproto::Window,
739 atoms: &XcbAtoms,
740 scale_factor: f32,
741 appearance: WindowAppearance,
742 ) -> anyhow::Result<Self> {
743 let ptr = X11WindowStatePtr {
744 state: Rc::new(RefCell::new(X11WindowState::new(
745 handle,
746 client,
747 executor,
748 gpu_context,
749 params,
750 xcb,
751 client_side_decorations_supported,
752 x_main_screen_index,
753 x_window,
754 atoms,
755 scale_factor,
756 appearance,
757 )?)),
758 callbacks: Rc::new(RefCell::new(Callbacks::default())),
759 xcb: xcb.clone(),
760 x_window,
761 };
762
763 let state = ptr.state.borrow_mut();
764 ptr.set_wm_properties(state)?;
765
766 Ok(Self(ptr))
767 }
768
769 fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
770 &self,
771 failure_context: F,
772 wm_hint_property_state: WmHintPropertyState,
773 prop1: u32,
774 prop2: u32,
775 ) -> anyhow::Result<()> {
776 let state = self.0.state.borrow();
777 let message = ClientMessageEvent::new(
778 32,
779 self.0.x_window,
780 state.atoms._NET_WM_STATE,
781 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
782 );
783 check_reply(
784 failure_context,
785 self.0.xcb.send_event(
786 false,
787 state.x_root_window,
788 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
789 message,
790 ),
791 )
792 }
793
794 fn get_root_position(
795 &self,
796 position: Point<Pixels>,
797 ) -> anyhow::Result<TranslateCoordinatesReply> {
798 let state = self.0.state.borrow();
799 get_reply(
800 || "X11 TranslateCoordinates failed.",
801 self.0.xcb.translate_coordinates(
802 self.0.x_window,
803 state.x_root_window,
804 (position.x.0 * state.scale_factor) as i16,
805 (position.y.0 * state.scale_factor) as i16,
806 ),
807 )
808 }
809
810 fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
811 let state = self.0.state.borrow();
812
813 check_reply(
814 || "X11 UngrabPointer before move/resize of window ailed.",
815 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
816 )?;
817
818 let pointer = get_reply(
819 || "X11 QueryPointer before move/resize of window failed.",
820 self.0.xcb.query_pointer(self.0.x_window),
821 )?;
822 let message = ClientMessageEvent::new(
823 32,
824 self.0.x_window,
825 state.atoms._NET_WM_MOVERESIZE,
826 [
827 pointer.root_x as u32,
828 pointer.root_y as u32,
829 flag,
830 0, // Left mouse button
831 0,
832 ],
833 );
834 check_reply(
835 || "X11 SendEvent to move/resize window failed.",
836 self.0.xcb.send_event(
837 false,
838 state.x_root_window,
839 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
840 message,
841 ),
842 )?;
843
844 self.flush()
845 }
846
847 fn flush(&self) -> anyhow::Result<()> {
848 self.0.xcb.flush().with_context(|| "X11 Flush failed.")
849 }
850}
851
852impl X11WindowStatePtr {
853 pub fn should_close(&self) -> bool {
854 let mut cb = self.callbacks.borrow_mut();
855 if let Some(mut should_close) = cb.should_close.take() {
856 let result = (should_close)();
857 cb.should_close = Some(should_close);
858 result
859 } else {
860 true
861 }
862 }
863
864 pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
865 let mut state = self.state.borrow_mut();
866 if event.atom == state.atoms._NET_WM_STATE {
867 self.set_wm_properties(state)?;
868 } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
869 self.set_edge_constraints(state)?;
870 }
871 Ok(())
872 }
873
874 fn set_edge_constraints(
875 &self,
876 mut state: std::cell::RefMut<X11WindowState>,
877 ) -> anyhow::Result<()> {
878 let reply = get_reply(
879 || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
880 self.xcb.get_property(
881 false,
882 self.x_window,
883 state.atoms._GTK_EDGE_CONSTRAINTS,
884 xproto::AtomEnum::CARDINAL,
885 0,
886 4,
887 ),
888 )?;
889
890 if reply.value_len != 0 {
891 let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
892 let edge_constraints = EdgeConstraints::from_atom(atom);
893 state.edge_constraints.replace(edge_constraints);
894 }
895
896 Ok(())
897 }
898
899 fn set_wm_properties(
900 &self,
901 mut state: std::cell::RefMut<X11WindowState>,
902 ) -> anyhow::Result<()> {
903 let reply = get_reply(
904 || "X11 GetProperty for _NET_WM_STATE failed.",
905 self.xcb.get_property(
906 false,
907 self.x_window,
908 state.atoms._NET_WM_STATE,
909 xproto::AtomEnum::ATOM,
910 0,
911 u32::MAX,
912 ),
913 )?;
914
915 let atoms = reply
916 .value
917 .chunks_exact(4)
918 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
919
920 state.active = false;
921 state.fullscreen = false;
922 state.maximized_vertical = false;
923 state.maximized_horizontal = false;
924 state.hidden = true;
925
926 for atom in atoms {
927 if atom == state.atoms._NET_WM_STATE_FOCUSED {
928 state.active = true;
929 } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
930 state.fullscreen = true;
931 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
932 state.maximized_vertical = true;
933 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
934 state.maximized_horizontal = true;
935 } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
936 state.hidden = true;
937 }
938 }
939
940 Ok(())
941 }
942
943 pub fn close(&self) {
944 let mut callbacks = self.callbacks.borrow_mut();
945 if let Some(fun) = callbacks.close.take() {
946 fun()
947 }
948 }
949
950 pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
951 let mut cb = self.callbacks.borrow_mut();
952 if let Some(ref mut fun) = cb.request_frame {
953 fun(request_frame_options);
954 }
955 }
956
957 pub fn handle_input(&self, input: PlatformInput) {
958 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
959 if !fun(input.clone()).propagate {
960 return;
961 }
962 }
963 if let PlatformInput::KeyDown(event) = input {
964 let mut state = self.state.borrow_mut();
965 if let Some(mut input_handler) = state.input_handler.take() {
966 if let Some(key_char) = &event.keystroke.key_char {
967 drop(state);
968 input_handler.replace_text_in_range(None, key_char);
969 state = self.state.borrow_mut();
970 }
971 state.input_handler = Some(input_handler);
972 }
973 }
974 }
975
976 pub fn handle_ime_commit(&self, text: String) {
977 let mut state = self.state.borrow_mut();
978 if let Some(mut input_handler) = state.input_handler.take() {
979 drop(state);
980 input_handler.replace_text_in_range(None, &text);
981 let mut state = self.state.borrow_mut();
982 state.input_handler = Some(input_handler);
983 }
984 }
985
986 pub fn handle_ime_preedit(&self, text: String) {
987 let mut state = self.state.borrow_mut();
988 if let Some(mut input_handler) = state.input_handler.take() {
989 drop(state);
990 input_handler.replace_and_mark_text_in_range(None, &text, None);
991 let mut state = self.state.borrow_mut();
992 state.input_handler = Some(input_handler);
993 }
994 }
995
996 pub fn handle_ime_unmark(&self) {
997 let mut state = self.state.borrow_mut();
998 if let Some(mut input_handler) = state.input_handler.take() {
999 drop(state);
1000 input_handler.unmark_text();
1001 let mut state = self.state.borrow_mut();
1002 state.input_handler = Some(input_handler);
1003 }
1004 }
1005
1006 pub fn handle_ime_delete(&self) {
1007 let mut state = self.state.borrow_mut();
1008 if let Some(mut input_handler) = state.input_handler.take() {
1009 drop(state);
1010 if let Some(marked) = input_handler.marked_text_range() {
1011 input_handler.replace_text_in_range(Some(marked), "");
1012 }
1013 let mut state = self.state.borrow_mut();
1014 state.input_handler = Some(input_handler);
1015 }
1016 }
1017
1018 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
1019 let mut state = self.state.borrow_mut();
1020 let mut bounds: Option<Bounds<Pixels>> = None;
1021 if let Some(mut input_handler) = state.input_handler.take() {
1022 drop(state);
1023 if let Some(selection) = input_handler.selected_text_range(true) {
1024 bounds = input_handler.bounds_for_range(selection.range);
1025 }
1026 let mut state = self.state.borrow_mut();
1027 state.input_handler = Some(input_handler);
1028 };
1029 bounds
1030 }
1031
1032 pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1033 let mut resize_args = None;
1034 let is_resize;
1035 {
1036 let mut state = self.state.borrow_mut();
1037 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1038
1039 is_resize = bounds.size.width != state.bounds.size.width
1040 || bounds.size.height != state.bounds.size.height;
1041
1042 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1043 // because it contains wrong values.
1044 if is_resize {
1045 state.bounds.size = bounds.size;
1046 } else {
1047 state.bounds = bounds;
1048 }
1049
1050 let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1051 if true {
1052 state.renderer.update_drawable_size(size(
1053 DevicePixels(gpu_size.width as i32),
1054 DevicePixels(gpu_size.height as i32),
1055 ));
1056 resize_args = Some((state.content_size(), state.scale_factor));
1057 }
1058 if let Some(value) = state.last_sync_counter.take() {
1059 check_reply(
1060 || "X11 sync SetCounter failed.",
1061 sync::set_counter(&self.xcb, state.counter_id, value),
1062 )?;
1063 }
1064 }
1065
1066 let mut callbacks = self.callbacks.borrow_mut();
1067 if let Some((content_size, scale_factor)) = resize_args {
1068 if let Some(ref mut fun) = callbacks.resize {
1069 fun(content_size, scale_factor)
1070 }
1071 }
1072 if !is_resize {
1073 if let Some(ref mut fun) = callbacks.moved {
1074 fun();
1075 }
1076 }
1077
1078 Ok(())
1079 }
1080
1081 pub fn set_active(&self, focus: bool) {
1082 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
1083 fun(focus);
1084 }
1085 }
1086
1087 pub fn set_hovered(&self, focus: bool) {
1088 if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
1089 fun(focus);
1090 }
1091 }
1092
1093 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1094 let mut state = self.state.borrow_mut();
1095 state.appearance = appearance;
1096 let is_transparent = state.is_transparent();
1097 state.renderer.update_transparency(is_transparent);
1098 state.appearance = appearance;
1099 drop(state);
1100 let mut callbacks = self.callbacks.borrow_mut();
1101 if let Some(ref mut fun) = callbacks.appearance_changed {
1102 (fun)()
1103 }
1104 }
1105}
1106
1107impl PlatformWindow for X11Window {
1108 fn bounds(&self) -> Bounds<Pixels> {
1109 self.0.state.borrow().bounds
1110 }
1111
1112 fn is_maximized(&self) -> bool {
1113 let state = self.0.state.borrow();
1114
1115 // A maximized window that gets minimized will still retain its maximized state.
1116 !state.hidden && state.maximized_vertical && state.maximized_horizontal
1117 }
1118
1119 fn window_bounds(&self) -> WindowBounds {
1120 let state = self.0.state.borrow();
1121 if self.is_maximized() {
1122 WindowBounds::Maximized(state.bounds)
1123 } else {
1124 WindowBounds::Windowed(state.bounds)
1125 }
1126 }
1127
1128 fn inner_window_bounds(&self) -> WindowBounds {
1129 let state = self.0.state.borrow();
1130 if self.is_maximized() {
1131 WindowBounds::Maximized(state.bounds)
1132 } else {
1133 let mut bounds = state.bounds;
1134 let [left, right, top, bottom] = state.last_insets;
1135
1136 let [left, right, top, bottom] = [
1137 Pixels((left as f32) / state.scale_factor),
1138 Pixels((right as f32) / state.scale_factor),
1139 Pixels((top as f32) / state.scale_factor),
1140 Pixels((bottom as f32) / state.scale_factor),
1141 ];
1142
1143 bounds.origin.x += left;
1144 bounds.origin.y += top;
1145 bounds.size.width -= left + right;
1146 bounds.size.height -= top + bottom;
1147
1148 WindowBounds::Windowed(bounds)
1149 }
1150 }
1151
1152 fn content_size(&self) -> Size<Pixels> {
1153 // We divide by the scale factor here because this value is queried to determine how much to draw,
1154 // but it will be multiplied later by the scale to adjust for scaling.
1155 let state = self.0.state.borrow();
1156 state
1157 .content_size()
1158 .map(|size| size.div(state.scale_factor))
1159 }
1160
1161 fn resize(&mut self, size: Size<Pixels>) {
1162 let state = self.0.state.borrow();
1163 let size = size.to_device_pixels(state.scale_factor);
1164 let width = size.width.0 as u32;
1165 let height = size.height.0 as u32;
1166
1167 check_reply(
1168 || {
1169 format!(
1170 "X11 ConfigureWindow failed. width: {}, height: {}",
1171 width, height
1172 )
1173 },
1174 self.0.xcb.configure_window(
1175 self.0.x_window,
1176 &xproto::ConfigureWindowAux::new()
1177 .width(width)
1178 .height(height),
1179 ),
1180 )
1181 .log_err();
1182 self.flush().log_err();
1183 }
1184
1185 fn scale_factor(&self) -> f32 {
1186 self.0.state.borrow().scale_factor
1187 }
1188
1189 fn appearance(&self) -> WindowAppearance {
1190 self.0.state.borrow().appearance
1191 }
1192
1193 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1194 Some(self.0.state.borrow().display.clone())
1195 }
1196
1197 fn mouse_position(&self) -> Point<Pixels> {
1198 let reply = get_reply(
1199 || "X11 QueryPointer failed.",
1200 self.0.xcb.query_pointer(self.0.x_window),
1201 )
1202 .unwrap();
1203 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1204 }
1205
1206 fn modifiers(&self) -> Modifiers {
1207 self.0
1208 .state
1209 .borrow()
1210 .client
1211 .0
1212 .upgrade()
1213 .map(|ref_cell| ref_cell.borrow().modifiers)
1214 .unwrap_or_default()
1215 }
1216
1217 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1218 self.0.state.borrow_mut().input_handler = Some(input_handler);
1219 }
1220
1221 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1222 self.0.state.borrow_mut().input_handler.take()
1223 }
1224
1225 fn prompt(
1226 &self,
1227 _level: PromptLevel,
1228 _msg: &str,
1229 _detail: Option<&str>,
1230 _answers: &[PromptButton],
1231 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1232 None
1233 }
1234
1235 fn activate(&self) {
1236 let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1237 let message = xproto::ClientMessageEvent::new(
1238 32,
1239 self.0.x_window,
1240 self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1241 data,
1242 );
1243 self.0
1244 .xcb
1245 .send_event(
1246 false,
1247 self.0.state.borrow().x_root_window,
1248 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1249 message,
1250 )
1251 .log_err();
1252 self.0
1253 .xcb
1254 .set_input_focus(
1255 xproto::InputFocus::POINTER_ROOT,
1256 self.0.x_window,
1257 xproto::Time::CURRENT_TIME,
1258 )
1259 .log_err();
1260 self.flush().unwrap();
1261 }
1262
1263 fn is_active(&self) -> bool {
1264 self.0.state.borrow().active
1265 }
1266
1267 fn is_hovered(&self) -> bool {
1268 self.0.state.borrow().hovered
1269 }
1270
1271 fn set_title(&mut self, title: &str) {
1272 check_reply(
1273 || "X11 ChangeProperty8 on WM_NAME failed.",
1274 self.0.xcb.change_property8(
1275 xproto::PropMode::REPLACE,
1276 self.0.x_window,
1277 xproto::AtomEnum::WM_NAME,
1278 xproto::AtomEnum::STRING,
1279 title.as_bytes(),
1280 ),
1281 )
1282 .log_err();
1283
1284 check_reply(
1285 || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1286 self.0.xcb.change_property8(
1287 xproto::PropMode::REPLACE,
1288 self.0.x_window,
1289 self.0.state.borrow().atoms._NET_WM_NAME,
1290 self.0.state.borrow().atoms.UTF8_STRING,
1291 title.as_bytes(),
1292 ),
1293 )
1294 .log_err();
1295 self.flush().log_err();
1296 }
1297
1298 fn set_app_id(&mut self, app_id: &str) {
1299 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1300 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1301 data.push(b'\0');
1302 data.extend(app_id.bytes()); // class
1303
1304 check_reply(
1305 || "X11 ChangeProperty8 for WM_CLASS failed.",
1306 self.0.xcb.change_property8(
1307 xproto::PropMode::REPLACE,
1308 self.0.x_window,
1309 xproto::AtomEnum::WM_CLASS,
1310 xproto::AtomEnum::STRING,
1311 &data,
1312 ),
1313 )
1314 .unwrap();
1315 }
1316
1317 fn map_window(&mut self) -> anyhow::Result<()> {
1318 check_reply(
1319 || "X11 MapWindow failed.",
1320 self.0.xcb.map_window(self.0.x_window),
1321 )?;
1322 Ok(())
1323 }
1324
1325 fn set_edited(&mut self, _edited: bool) {
1326 log::info!("ignoring macOS specific set_edited");
1327 }
1328
1329 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1330 let mut state = self.0.state.borrow_mut();
1331 state.background_appearance = background_appearance;
1332 let transparent = state.is_transparent();
1333 state.renderer.update_transparency(transparent);
1334 }
1335
1336 fn show_character_palette(&self) {
1337 log::info!("ignoring macOS specific show_character_palette");
1338 }
1339
1340 fn minimize(&self) {
1341 let state = self.0.state.borrow();
1342 const WINDOW_ICONIC_STATE: u32 = 3;
1343 let message = ClientMessageEvent::new(
1344 32,
1345 self.0.x_window,
1346 state.atoms.WM_CHANGE_STATE,
1347 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1348 );
1349 check_reply(
1350 || "X11 SendEvent to minimize window failed.",
1351 self.0.xcb.send_event(
1352 false,
1353 state.x_root_window,
1354 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1355 message,
1356 ),
1357 )
1358 .unwrap();
1359 }
1360
1361 fn zoom(&self) {
1362 let state = self.0.state.borrow();
1363 self.set_wm_hints(
1364 || "X11 SendEvent to maximize a window failed.",
1365 WmHintPropertyState::Toggle,
1366 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1367 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1368 )
1369 .unwrap();
1370 }
1371
1372 fn toggle_fullscreen(&self) {
1373 let state = self.0.state.borrow();
1374 self.set_wm_hints(
1375 || "X11 SendEvent to fullscreen a window failed.",
1376 WmHintPropertyState::Toggle,
1377 state.atoms._NET_WM_STATE_FULLSCREEN,
1378 xproto::AtomEnum::NONE.into(),
1379 )
1380 .unwrap();
1381 }
1382
1383 fn is_fullscreen(&self) -> bool {
1384 self.0.state.borrow().fullscreen
1385 }
1386
1387 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1388 self.0.callbacks.borrow_mut().request_frame = Some(callback);
1389 }
1390
1391 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1392 self.0.callbacks.borrow_mut().input = Some(callback);
1393 }
1394
1395 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1396 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1397 }
1398
1399 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1400 self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1401 }
1402
1403 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1404 self.0.callbacks.borrow_mut().resize = Some(callback);
1405 }
1406
1407 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1408 self.0.callbacks.borrow_mut().moved = Some(callback);
1409 }
1410
1411 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1412 self.0.callbacks.borrow_mut().should_close = Some(callback);
1413 }
1414
1415 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1416 self.0.callbacks.borrow_mut().close = Some(callback);
1417 }
1418
1419 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1420 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1421 }
1422
1423 fn draw(&self, scene: &Scene) {
1424 let mut inner = self.0.state.borrow_mut();
1425 inner.renderer.draw(scene);
1426 }
1427
1428 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1429 let inner = self.0.state.borrow();
1430 inner.renderer.sprite_atlas().clone()
1431 }
1432
1433 fn show_window_menu(&self, position: Point<Pixels>) {
1434 let state = self.0.state.borrow();
1435
1436 check_reply(
1437 || "X11 UngrabPointer failed.",
1438 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1439 )
1440 .unwrap();
1441
1442 let coords = self.get_root_position(position).unwrap();
1443 let message = ClientMessageEvent::new(
1444 32,
1445 self.0.x_window,
1446 state.atoms._GTK_SHOW_WINDOW_MENU,
1447 [
1448 XINPUT_ALL_DEVICE_GROUPS as u32,
1449 coords.dst_x as u32,
1450 coords.dst_y as u32,
1451 0,
1452 0,
1453 ],
1454 );
1455 check_reply(
1456 || "X11 SendEvent to show window menu failed.",
1457 self.0.xcb.send_event(
1458 false,
1459 state.x_root_window,
1460 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1461 message,
1462 ),
1463 )
1464 .unwrap();
1465 }
1466
1467 fn start_window_move(&self) {
1468 const MOVERESIZE_MOVE: u32 = 8;
1469 self.send_moveresize(MOVERESIZE_MOVE).unwrap();
1470 }
1471
1472 fn start_window_resize(&self, edge: ResizeEdge) {
1473 self.send_moveresize(edge.to_moveresize()).unwrap();
1474 }
1475
1476 fn window_decorations(&self) -> crate::Decorations {
1477 let state = self.0.state.borrow();
1478
1479 // Client window decorations require compositor support
1480 if !state.client_side_decorations_supported {
1481 return Decorations::Server;
1482 }
1483
1484 match state.decorations {
1485 WindowDecorations::Server => Decorations::Server,
1486 WindowDecorations::Client => {
1487 let tiling = if state.fullscreen {
1488 Tiling::tiled()
1489 } else if let Some(edge_constraints) = &state.edge_constraints {
1490 edge_constraints.to_tiling()
1491 } else {
1492 // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1493 Tiling {
1494 top: state.maximized_vertical,
1495 bottom: state.maximized_vertical,
1496 left: state.maximized_horizontal,
1497 right: state.maximized_horizontal,
1498 }
1499 };
1500 Decorations::Client { tiling }
1501 }
1502 }
1503 }
1504
1505 fn set_client_inset(&self, inset: Pixels) {
1506 let mut state = self.0.state.borrow_mut();
1507
1508 let dp = (inset.0 * state.scale_factor) as u32;
1509
1510 let insets = if state.fullscreen {
1511 [0, 0, 0, 0]
1512 } else if let Some(edge_constraints) = &state.edge_constraints {
1513 let left = if edge_constraints.left_tiled { 0 } else { dp };
1514 let top = if edge_constraints.top_tiled { 0 } else { dp };
1515 let right = if edge_constraints.right_tiled { 0 } else { dp };
1516 let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1517
1518 [left, right, top, bottom]
1519 } else {
1520 let (left, right) = if state.maximized_horizontal {
1521 (0, 0)
1522 } else {
1523 (dp, dp)
1524 };
1525 let (top, bottom) = if state.maximized_vertical {
1526 (0, 0)
1527 } else {
1528 (dp, dp)
1529 };
1530 [left, right, top, bottom]
1531 };
1532
1533 if state.last_insets != insets {
1534 state.last_insets = insets;
1535
1536 check_reply(
1537 || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1538 self.0.xcb.change_property(
1539 xproto::PropMode::REPLACE,
1540 self.0.x_window,
1541 state.atoms._GTK_FRAME_EXTENTS,
1542 xproto::AtomEnum::CARDINAL,
1543 size_of::<u32>() as u8 * 8,
1544 4,
1545 bytemuck::cast_slice::<u32, u8>(&insets),
1546 ),
1547 )
1548 .unwrap();
1549 }
1550 }
1551
1552 fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1553 let mut state = self.0.state.borrow_mut();
1554
1555 if matches!(decorations, crate::WindowDecorations::Client)
1556 && !state.client_side_decorations_supported
1557 {
1558 log::info!(
1559 "x11: no compositor present, falling back to server-side window decorations"
1560 );
1561 decorations = crate::WindowDecorations::Server;
1562 }
1563
1564 // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1565 let hints_data: [u32; 5] = match decorations {
1566 WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1567 WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1568 };
1569
1570 check_reply(
1571 || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1572 self.0.xcb.change_property(
1573 xproto::PropMode::REPLACE,
1574 self.0.x_window,
1575 state.atoms._MOTIF_WM_HINTS,
1576 state.atoms._MOTIF_WM_HINTS,
1577 size_of::<u32>() as u8 * 8,
1578 5,
1579 bytemuck::cast_slice::<u32, u8>(&hints_data),
1580 ),
1581 )
1582 .unwrap();
1583
1584 match decorations {
1585 WindowDecorations::Server => {
1586 state.decorations = WindowDecorations::Server;
1587 let is_transparent = state.is_transparent();
1588 state.renderer.update_transparency(is_transparent);
1589 }
1590 WindowDecorations::Client => {
1591 state.decorations = WindowDecorations::Client;
1592 let is_transparent = state.is_transparent();
1593 state.renderer.update_transparency(is_transparent);
1594 }
1595 }
1596
1597 drop(state);
1598 let mut callbacks = self.0.callbacks.borrow_mut();
1599 if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1600 appearance_changed();
1601 }
1602 }
1603
1604 fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
1605 let mut state = self.0.state.borrow_mut();
1606 let client = state.client.clone();
1607 drop(state);
1608 client.update_ime_position(bounds);
1609 }
1610
1611 fn gpu_specs(&self) -> Option<GpuSpecs> {
1612 self.0.state.borrow().renderer.gpu_specs().into()
1613 }
1614}