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