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