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