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 #[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 let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
594
595 Ok(Self {
596 client,
597 executor,
598 display,
599 x_root_window: visual_set.root,
600 bounds: bounds.to_pixels(scale_factor),
601 scale_factor,
602 renderer,
603 atoms: *atoms,
604 input_handler: None,
605 active: false,
606 hovered: false,
607 fullscreen: false,
608 maximized_vertical: false,
609 maximized_horizontal: false,
610 hidden: false,
611 appearance,
612 handle,
613 background_appearance: WindowBackgroundAppearance::Opaque,
614 destroyed: false,
615 client_side_decorations_supported,
616 decorations: WindowDecorations::Server,
617 last_insets: [0, 0, 0, 0],
618 edge_constraints: None,
619 counter_id: sync_request_counter,
620 last_sync_counter: None,
621 })
622 });
623
624 if setup_result.is_err() {
625 check_reply(
626 || "X11 DestroyWindow failed while cleaning it up after setup failure.",
627 xcb.destroy_window(x_window),
628 )?;
629 xcb.flush()
630 .with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?;
631 }
632
633 setup_result
634 }
635
636 fn content_size(&self) -> Size<Pixels> {
637 let size = self.renderer.viewport_size();
638 Size {
639 width: size.width.into(),
640 height: size.height.into(),
641 }
642 }
643}
644
645/// A handle to an X11 window which destroys it on Drop.
646pub struct X11WindowHandle {
647 id: xproto::Window,
648 xcb: Rc<XCBConnection>,
649}
650
651impl Drop for X11WindowHandle {
652 fn drop(&mut self) {
653 maybe!({
654 check_reply(
655 || "X11 DestroyWindow failed while dropping X11WindowHandle.",
656 self.xcb.destroy_window(self.id),
657 )?;
658 self.xcb
659 .flush()
660 .with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?;
661 anyhow::Ok(())
662 })
663 .log_err();
664 }
665}
666
667pub(crate) struct X11Window(pub X11WindowStatePtr);
668
669impl Drop for X11Window {
670 fn drop(&mut self) {
671 let mut state = self.0.state.borrow_mut();
672 state.renderer.destroy();
673
674 let destroy_x_window = maybe!({
675 check_reply(
676 || "X11 DestroyWindow failure.",
677 self.0.xcb.destroy_window(self.0.x_window),
678 )?;
679 self.0
680 .xcb
681 .flush()
682 .with_context(|| "X11 Flush failed after calling DestroyWindow.")?;
683
684 anyhow::Ok(())
685 })
686 .log_err();
687
688 if destroy_x_window.is_some() {
689 // Mark window as destroyed so that we can filter out when X11 events
690 // for it still come in.
691 state.destroyed = true;
692
693 let this_ptr = self.0.clone();
694 let client_ptr = state.client.clone();
695 state
696 .executor
697 .spawn(async move {
698 this_ptr.close();
699 client_ptr.drop_window(this_ptr.x_window);
700 })
701 .detach();
702 }
703
704 drop(state);
705 }
706}
707
708enum WmHintPropertyState {
709 // Remove = 0,
710 // Add = 1,
711 Toggle = 2,
712}
713
714impl X11Window {
715 #[allow(clippy::too_many_arguments)]
716 pub fn new(
717 handle: AnyWindowHandle,
718 client: X11ClientStatePtr,
719 executor: ForegroundExecutor,
720 gpu_context: &BladeContext,
721 params: WindowParams,
722 xcb: &Rc<XCBConnection>,
723 client_side_decorations_supported: bool,
724 x_main_screen_index: usize,
725 x_window: xproto::Window,
726 atoms: &XcbAtoms,
727 scale_factor: f32,
728 appearance: WindowAppearance,
729 ) -> anyhow::Result<Self> {
730 let ptr = X11WindowStatePtr {
731 state: Rc::new(RefCell::new(X11WindowState::new(
732 handle,
733 client,
734 executor,
735 gpu_context,
736 params,
737 xcb,
738 client_side_decorations_supported,
739 x_main_screen_index,
740 x_window,
741 atoms,
742 scale_factor,
743 appearance,
744 )?)),
745 callbacks: Rc::new(RefCell::new(Callbacks::default())),
746 xcb: xcb.clone(),
747 x_window,
748 };
749
750 let state = ptr.state.borrow_mut();
751 ptr.set_wm_properties(state)?;
752
753 Ok(Self(ptr))
754 }
755
756 fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
757 &self,
758 failure_context: F,
759 wm_hint_property_state: WmHintPropertyState,
760 prop1: u32,
761 prop2: u32,
762 ) -> anyhow::Result<()> {
763 let state = self.0.state.borrow();
764 let message = ClientMessageEvent::new(
765 32,
766 self.0.x_window,
767 state.atoms._NET_WM_STATE,
768 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
769 );
770 check_reply(
771 failure_context,
772 self.0.xcb.send_event(
773 false,
774 state.x_root_window,
775 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
776 message,
777 ),
778 )
779 }
780
781 fn get_root_position(
782 &self,
783 position: Point<Pixels>,
784 ) -> anyhow::Result<TranslateCoordinatesReply> {
785 let state = self.0.state.borrow();
786 get_reply(
787 || "X11 TranslateCoordinates failed.",
788 self.0.xcb.translate_coordinates(
789 self.0.x_window,
790 state.x_root_window,
791 (position.x.0 * state.scale_factor) as i16,
792 (position.y.0 * state.scale_factor) as i16,
793 ),
794 )
795 }
796
797 fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
798 let state = self.0.state.borrow();
799
800 check_reply(
801 || "X11 UngrabPointer before move/resize of window ailed.",
802 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
803 )?;
804
805 let pointer = get_reply(
806 || "X11 QueryPointer before move/resize of window failed.",
807 self.0.xcb.query_pointer(self.0.x_window),
808 )?;
809 let message = ClientMessageEvent::new(
810 32,
811 self.0.x_window,
812 state.atoms._NET_WM_MOVERESIZE,
813 [
814 pointer.root_x as u32,
815 pointer.root_y as u32,
816 flag,
817 0, // Left mouse button
818 0,
819 ],
820 );
821 check_reply(
822 || "X11 SendEvent to move/resize window failed.",
823 self.0.xcb.send_event(
824 false,
825 state.x_root_window,
826 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
827 message,
828 ),
829 )?;
830
831 self.flush()
832 }
833
834 fn flush(&self) -> anyhow::Result<()> {
835 self.0.xcb.flush().with_context(|| "X11 Flush failed.")
836 }
837}
838
839impl X11WindowStatePtr {
840 pub fn should_close(&self) -> bool {
841 let mut cb = self.callbacks.borrow_mut();
842 if let Some(mut should_close) = cb.should_close.take() {
843 let result = (should_close)();
844 cb.should_close = Some(should_close);
845 result
846 } else {
847 true
848 }
849 }
850
851 pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
852 let mut state = self.state.borrow_mut();
853 if event.atom == state.atoms._NET_WM_STATE {
854 self.set_wm_properties(state)?;
855 } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
856 self.set_edge_constraints(state)?;
857 }
858 Ok(())
859 }
860
861 fn set_edge_constraints(
862 &self,
863 mut state: std::cell::RefMut<X11WindowState>,
864 ) -> anyhow::Result<()> {
865 let reply = get_reply(
866 || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
867 self.xcb.get_property(
868 false,
869 self.x_window,
870 state.atoms._GTK_EDGE_CONSTRAINTS,
871 xproto::AtomEnum::CARDINAL,
872 0,
873 4,
874 ),
875 )?;
876
877 if reply.value_len != 0 {
878 let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
879 let edge_constraints = EdgeConstraints::from_atom(atom);
880 state.edge_constraints.replace(edge_constraints);
881 }
882
883 Ok(())
884 }
885
886 fn set_wm_properties(
887 &self,
888 mut state: std::cell::RefMut<X11WindowState>,
889 ) -> anyhow::Result<()> {
890 let reply = get_reply(
891 || "X11 GetProperty for _NET_WM_STATE failed.",
892 self.xcb.get_property(
893 false,
894 self.x_window,
895 state.atoms._NET_WM_STATE,
896 xproto::AtomEnum::ATOM,
897 0,
898 u32::MAX,
899 ),
900 )?;
901
902 let atoms = reply
903 .value
904 .chunks_exact(4)
905 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
906
907 state.active = false;
908 state.fullscreen = false;
909 state.maximized_vertical = false;
910 state.maximized_horizontal = false;
911 state.hidden = true;
912
913 for atom in atoms {
914 if atom == state.atoms._NET_WM_STATE_FOCUSED {
915 state.active = true;
916 } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
917 state.fullscreen = true;
918 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
919 state.maximized_vertical = true;
920 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
921 state.maximized_horizontal = true;
922 } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
923 state.hidden = true;
924 }
925 }
926
927 Ok(())
928 }
929
930 pub fn close(&self) {
931 let mut callbacks = self.callbacks.borrow_mut();
932 if let Some(fun) = callbacks.close.take() {
933 fun()
934 }
935 }
936
937 pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
938 let mut cb = self.callbacks.borrow_mut();
939 if let Some(ref mut fun) = cb.request_frame {
940 fun(request_frame_options);
941 }
942 }
943
944 pub fn handle_input(&self, input: PlatformInput) {
945 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
946 if !fun(input.clone()).propagate {
947 return;
948 }
949 }
950 if let PlatformInput::KeyDown(event) = input {
951 let mut state = self.state.borrow_mut();
952 if let Some(mut input_handler) = state.input_handler.take() {
953 if let Some(key_char) = &event.keystroke.key_char {
954 drop(state);
955 input_handler.replace_text_in_range(None, key_char);
956 state = self.state.borrow_mut();
957 }
958 state.input_handler = Some(input_handler);
959 }
960 }
961 }
962
963 pub fn handle_ime_commit(&self, text: String) {
964 let mut state = self.state.borrow_mut();
965 if let Some(mut input_handler) = state.input_handler.take() {
966 drop(state);
967 input_handler.replace_text_in_range(None, &text);
968 let mut state = self.state.borrow_mut();
969 state.input_handler = Some(input_handler);
970 }
971 }
972
973 pub fn handle_ime_preedit(&self, text: String) {
974 let mut state = self.state.borrow_mut();
975 if let Some(mut input_handler) = state.input_handler.take() {
976 drop(state);
977 input_handler.replace_and_mark_text_in_range(None, &text, None);
978 let mut state = self.state.borrow_mut();
979 state.input_handler = Some(input_handler);
980 }
981 }
982
983 pub fn handle_ime_unmark(&self) {
984 let mut state = self.state.borrow_mut();
985 if let Some(mut input_handler) = state.input_handler.take() {
986 drop(state);
987 input_handler.unmark_text();
988 let mut state = self.state.borrow_mut();
989 state.input_handler = Some(input_handler);
990 }
991 }
992
993 pub fn handle_ime_delete(&self) {
994 let mut state = self.state.borrow_mut();
995 if let Some(mut input_handler) = state.input_handler.take() {
996 drop(state);
997 if let Some(marked) = input_handler.marked_text_range() {
998 input_handler.replace_text_in_range(Some(marked), "");
999 }
1000 let mut state = self.state.borrow_mut();
1001 state.input_handler = Some(input_handler);
1002 }
1003 }
1004
1005 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
1006 let mut state = self.state.borrow_mut();
1007 let mut bounds: Option<Bounds<Pixels>> = None;
1008 if let Some(mut input_handler) = state.input_handler.take() {
1009 drop(state);
1010 if let Some(selection) = input_handler.selected_text_range(true) {
1011 bounds = input_handler.bounds_for_range(selection.range);
1012 }
1013 let mut state = self.state.borrow_mut();
1014 state.input_handler = Some(input_handler);
1015 };
1016 bounds
1017 }
1018
1019 pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1020 let mut resize_args = None;
1021 let is_resize;
1022 {
1023 let mut state = self.state.borrow_mut();
1024 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1025
1026 is_resize = bounds.size.width != state.bounds.size.width
1027 || bounds.size.height != state.bounds.size.height;
1028
1029 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1030 // because it contains wrong values.
1031 if is_resize {
1032 state.bounds.size = bounds.size;
1033 } else {
1034 state.bounds = bounds;
1035 }
1036
1037 let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1038 if true {
1039 state.renderer.update_drawable_size(size(
1040 DevicePixels(gpu_size.width as i32),
1041 DevicePixels(gpu_size.height as i32),
1042 ));
1043 resize_args = Some((state.content_size(), state.scale_factor));
1044 }
1045 if let Some(value) = state.last_sync_counter.take() {
1046 check_reply(
1047 || "X11 sync SetCounter failed.",
1048 sync::set_counter(&self.xcb, state.counter_id, value),
1049 )?;
1050 }
1051 }
1052
1053 let mut callbacks = self.callbacks.borrow_mut();
1054 if let Some((content_size, scale_factor)) = resize_args {
1055 if let Some(ref mut fun) = callbacks.resize {
1056 fun(content_size, scale_factor)
1057 }
1058 }
1059 if !is_resize {
1060 if let Some(ref mut fun) = callbacks.moved {
1061 fun();
1062 }
1063 }
1064
1065 Ok(())
1066 }
1067
1068 pub fn set_active(&self, focus: bool) {
1069 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
1070 fun(focus);
1071 }
1072 }
1073
1074 pub fn set_hovered(&self, focus: bool) {
1075 if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
1076 fun(focus);
1077 }
1078 }
1079
1080 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1081 let mut state = self.state.borrow_mut();
1082 state.appearance = appearance;
1083 let is_transparent = state.is_transparent();
1084 state.renderer.update_transparency(is_transparent);
1085 state.appearance = appearance;
1086 drop(state);
1087 let mut callbacks = self.callbacks.borrow_mut();
1088 if let Some(ref mut fun) = callbacks.appearance_changed {
1089 (fun)()
1090 }
1091 }
1092}
1093
1094impl PlatformWindow for X11Window {
1095 fn bounds(&self) -> Bounds<Pixels> {
1096 self.0.state.borrow().bounds
1097 }
1098
1099 fn is_maximized(&self) -> bool {
1100 let state = self.0.state.borrow();
1101
1102 // A maximized window that gets minimized will still retain its maximized state.
1103 !state.hidden && state.maximized_vertical && state.maximized_horizontal
1104 }
1105
1106 fn window_bounds(&self) -> WindowBounds {
1107 let state = self.0.state.borrow();
1108 if self.is_maximized() {
1109 WindowBounds::Maximized(state.bounds)
1110 } else {
1111 WindowBounds::Windowed(state.bounds)
1112 }
1113 }
1114
1115 fn inner_window_bounds(&self) -> WindowBounds {
1116 let state = self.0.state.borrow();
1117 if self.is_maximized() {
1118 WindowBounds::Maximized(state.bounds)
1119 } else {
1120 let mut bounds = state.bounds;
1121 let [left, right, top, bottom] = state.last_insets;
1122
1123 let [left, right, top, bottom] = [
1124 Pixels((left as f32) / state.scale_factor),
1125 Pixels((right as f32) / state.scale_factor),
1126 Pixels((top as f32) / state.scale_factor),
1127 Pixels((bottom as f32) / state.scale_factor),
1128 ];
1129
1130 bounds.origin.x += left;
1131 bounds.origin.y += top;
1132 bounds.size.width -= left + right;
1133 bounds.size.height -= top + bottom;
1134
1135 WindowBounds::Windowed(bounds)
1136 }
1137 }
1138
1139 fn content_size(&self) -> Size<Pixels> {
1140 // We divide by the scale factor here because this value is queried to determine how much to draw,
1141 // but it will be multiplied later by the scale to adjust for scaling.
1142 let state = self.0.state.borrow();
1143 state
1144 .content_size()
1145 .map(|size| size.div(state.scale_factor))
1146 }
1147
1148 fn scale_factor(&self) -> f32 {
1149 self.0.state.borrow().scale_factor
1150 }
1151
1152 fn appearance(&self) -> WindowAppearance {
1153 self.0.state.borrow().appearance
1154 }
1155
1156 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1157 Some(self.0.state.borrow().display.clone())
1158 }
1159
1160 fn mouse_position(&self) -> Point<Pixels> {
1161 let reply = get_reply(
1162 || "X11 QueryPointer failed.",
1163 self.0.xcb.query_pointer(self.0.x_window),
1164 )
1165 .unwrap();
1166 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1167 }
1168
1169 fn modifiers(&self) -> Modifiers {
1170 self.0
1171 .state
1172 .borrow()
1173 .client
1174 .0
1175 .upgrade()
1176 .map(|ref_cell| ref_cell.borrow().modifiers)
1177 .unwrap_or_default()
1178 }
1179
1180 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1181 self.0.state.borrow_mut().input_handler = Some(input_handler);
1182 }
1183
1184 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1185 self.0.state.borrow_mut().input_handler.take()
1186 }
1187
1188 fn prompt(
1189 &self,
1190 _level: PromptLevel,
1191 _msg: &str,
1192 _detail: Option<&str>,
1193 _answers: &[&str],
1194 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1195 None
1196 }
1197
1198 fn activate(&self) {
1199 let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1200 let message = xproto::ClientMessageEvent::new(
1201 32,
1202 self.0.x_window,
1203 self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1204 data,
1205 );
1206 self.0
1207 .xcb
1208 .send_event(
1209 false,
1210 self.0.state.borrow().x_root_window,
1211 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1212 message,
1213 )
1214 .log_err();
1215 self.0
1216 .xcb
1217 .set_input_focus(
1218 xproto::InputFocus::POINTER_ROOT,
1219 self.0.x_window,
1220 xproto::Time::CURRENT_TIME,
1221 )
1222 .log_err();
1223 self.flush().unwrap();
1224 }
1225
1226 fn is_active(&self) -> bool {
1227 self.0.state.borrow().active
1228 }
1229
1230 fn is_hovered(&self) -> bool {
1231 self.0.state.borrow().hovered
1232 }
1233
1234 fn set_title(&mut self, title: &str) {
1235 check_reply(
1236 || "X11 ChangeProperty8 on WM_NAME failed.",
1237 self.0.xcb.change_property8(
1238 xproto::PropMode::REPLACE,
1239 self.0.x_window,
1240 xproto::AtomEnum::WM_NAME,
1241 xproto::AtomEnum::STRING,
1242 title.as_bytes(),
1243 ),
1244 )
1245 .log_err();
1246
1247 check_reply(
1248 || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1249 self.0.xcb.change_property8(
1250 xproto::PropMode::REPLACE,
1251 self.0.x_window,
1252 self.0.state.borrow().atoms._NET_WM_NAME,
1253 self.0.state.borrow().atoms.UTF8_STRING,
1254 title.as_bytes(),
1255 ),
1256 )
1257 .log_err();
1258 self.flush().log_err();
1259 }
1260
1261 fn set_app_id(&mut self, app_id: &str) {
1262 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1263 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1264 data.push(b'\0');
1265 data.extend(app_id.bytes()); // class
1266
1267 check_reply(
1268 || "X11 ChangeProperty8 for WM_CLASS failed.",
1269 self.0.xcb.change_property8(
1270 xproto::PropMode::REPLACE,
1271 self.0.x_window,
1272 xproto::AtomEnum::WM_CLASS,
1273 xproto::AtomEnum::STRING,
1274 &data,
1275 ),
1276 )
1277 .unwrap();
1278 }
1279
1280 fn map_window(&mut self) -> anyhow::Result<()> {
1281 check_reply(
1282 || "X11 MapWindow failed.",
1283 self.0.xcb.map_window(self.0.x_window),
1284 )?;
1285 Ok(())
1286 }
1287
1288 fn set_edited(&mut self, _edited: bool) {
1289 log::info!("ignoring macOS specific set_edited");
1290 }
1291
1292 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1293 let mut state = self.0.state.borrow_mut();
1294 state.background_appearance = background_appearance;
1295 let transparent = state.is_transparent();
1296 state.renderer.update_transparency(transparent);
1297 }
1298
1299 fn show_character_palette(&self) {
1300 log::info!("ignoring macOS specific show_character_palette");
1301 }
1302
1303 fn minimize(&self) {
1304 let state = self.0.state.borrow();
1305 const WINDOW_ICONIC_STATE: u32 = 3;
1306 let message = ClientMessageEvent::new(
1307 32,
1308 self.0.x_window,
1309 state.atoms.WM_CHANGE_STATE,
1310 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1311 );
1312 check_reply(
1313 || "X11 SendEvent to minimize window failed.",
1314 self.0.xcb.send_event(
1315 false,
1316 state.x_root_window,
1317 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1318 message,
1319 ),
1320 )
1321 .unwrap();
1322 }
1323
1324 fn zoom(&self) {
1325 let state = self.0.state.borrow();
1326 self.set_wm_hints(
1327 || "X11 SendEvent to maximize a window failed.",
1328 WmHintPropertyState::Toggle,
1329 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1330 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1331 )
1332 .unwrap();
1333 }
1334
1335 fn toggle_fullscreen(&self) {
1336 let state = self.0.state.borrow();
1337 self.set_wm_hints(
1338 || "X11 SendEvent to fullscreen a window failed.",
1339 WmHintPropertyState::Toggle,
1340 state.atoms._NET_WM_STATE_FULLSCREEN,
1341 xproto::AtomEnum::NONE.into(),
1342 )
1343 .unwrap();
1344 }
1345
1346 fn is_fullscreen(&self) -> bool {
1347 self.0.state.borrow().fullscreen
1348 }
1349
1350 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1351 self.0.callbacks.borrow_mut().request_frame = Some(callback);
1352 }
1353
1354 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1355 self.0.callbacks.borrow_mut().input = Some(callback);
1356 }
1357
1358 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1359 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1360 }
1361
1362 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1363 self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1364 }
1365
1366 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1367 self.0.callbacks.borrow_mut().resize = Some(callback);
1368 }
1369
1370 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1371 self.0.callbacks.borrow_mut().moved = Some(callback);
1372 }
1373
1374 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1375 self.0.callbacks.borrow_mut().should_close = Some(callback);
1376 }
1377
1378 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1379 self.0.callbacks.borrow_mut().close = Some(callback);
1380 }
1381
1382 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1383 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1384 }
1385
1386 fn draw(&self, scene: &Scene) {
1387 let mut inner = self.0.state.borrow_mut();
1388 inner.renderer.draw(scene);
1389 }
1390
1391 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1392 let inner = self.0.state.borrow();
1393 inner.renderer.sprite_atlas().clone()
1394 }
1395
1396 fn show_window_menu(&self, position: Point<Pixels>) {
1397 let state = self.0.state.borrow();
1398
1399 check_reply(
1400 || "X11 UngrabPointer failed.",
1401 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1402 )
1403 .unwrap();
1404
1405 let coords = self.get_root_position(position).unwrap();
1406 let message = ClientMessageEvent::new(
1407 32,
1408 self.0.x_window,
1409 state.atoms._GTK_SHOW_WINDOW_MENU,
1410 [
1411 XINPUT_ALL_DEVICE_GROUPS as u32,
1412 coords.dst_x as u32,
1413 coords.dst_y as u32,
1414 0,
1415 0,
1416 ],
1417 );
1418 check_reply(
1419 || "X11 SendEvent to show window menu failed.",
1420 self.0.xcb.send_event(
1421 false,
1422 state.x_root_window,
1423 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1424 message,
1425 ),
1426 )
1427 .unwrap();
1428 }
1429
1430 fn start_window_move(&self) {
1431 const MOVERESIZE_MOVE: u32 = 8;
1432 self.send_moveresize(MOVERESIZE_MOVE).unwrap();
1433 }
1434
1435 fn start_window_resize(&self, edge: ResizeEdge) {
1436 self.send_moveresize(edge.to_moveresize()).unwrap();
1437 }
1438
1439 fn window_decorations(&self) -> crate::Decorations {
1440 let state = self.0.state.borrow();
1441
1442 // Client window decorations require compositor support
1443 if !state.client_side_decorations_supported {
1444 return Decorations::Server;
1445 }
1446
1447 match state.decorations {
1448 WindowDecorations::Server => Decorations::Server,
1449 WindowDecorations::Client => {
1450 let tiling = if state.fullscreen {
1451 Tiling::tiled()
1452 } else if let Some(edge_constraints) = &state.edge_constraints {
1453 edge_constraints.to_tiling()
1454 } else {
1455 // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1456 Tiling {
1457 top: state.maximized_vertical,
1458 bottom: state.maximized_vertical,
1459 left: state.maximized_horizontal,
1460 right: state.maximized_horizontal,
1461 }
1462 };
1463 Decorations::Client { tiling }
1464 }
1465 }
1466 }
1467
1468 fn set_client_inset(&self, inset: Pixels) {
1469 let mut state = self.0.state.borrow_mut();
1470
1471 let dp = (inset.0 * state.scale_factor) as u32;
1472
1473 let insets = if state.fullscreen {
1474 [0, 0, 0, 0]
1475 } else if let Some(edge_constraints) = &state.edge_constraints {
1476 let left = if edge_constraints.left_tiled { 0 } else { dp };
1477 let top = if edge_constraints.top_tiled { 0 } else { dp };
1478 let right = if edge_constraints.right_tiled { 0 } else { dp };
1479 let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1480
1481 [left, right, top, bottom]
1482 } else {
1483 let (left, right) = if state.maximized_horizontal {
1484 (0, 0)
1485 } else {
1486 (dp, dp)
1487 };
1488 let (top, bottom) = if state.maximized_vertical {
1489 (0, 0)
1490 } else {
1491 (dp, dp)
1492 };
1493 [left, right, top, bottom]
1494 };
1495
1496 if state.last_insets != insets {
1497 state.last_insets = insets;
1498
1499 check_reply(
1500 || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1501 self.0.xcb.change_property(
1502 xproto::PropMode::REPLACE,
1503 self.0.x_window,
1504 state.atoms._GTK_FRAME_EXTENTS,
1505 xproto::AtomEnum::CARDINAL,
1506 size_of::<u32>() as u8 * 8,
1507 4,
1508 bytemuck::cast_slice::<u32, u8>(&insets),
1509 ),
1510 )
1511 .unwrap();
1512 }
1513 }
1514
1515 fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1516 let mut state = self.0.state.borrow_mut();
1517
1518 if matches!(decorations, crate::WindowDecorations::Client)
1519 && !state.client_side_decorations_supported
1520 {
1521 log::info!(
1522 "x11: no compositor present, falling back to server-side window decorations"
1523 );
1524 decorations = crate::WindowDecorations::Server;
1525 }
1526
1527 // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1528 let hints_data: [u32; 5] = match decorations {
1529 WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1530 WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1531 };
1532
1533 check_reply(
1534 || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1535 self.0.xcb.change_property(
1536 xproto::PropMode::REPLACE,
1537 self.0.x_window,
1538 state.atoms._MOTIF_WM_HINTS,
1539 state.atoms._MOTIF_WM_HINTS,
1540 size_of::<u32>() as u8 * 8,
1541 5,
1542 bytemuck::cast_slice::<u32, u8>(&hints_data),
1543 ),
1544 )
1545 .unwrap();
1546
1547 match decorations {
1548 WindowDecorations::Server => {
1549 state.decorations = WindowDecorations::Server;
1550 let is_transparent = state.is_transparent();
1551 state.renderer.update_transparency(is_transparent);
1552 }
1553 WindowDecorations::Client => {
1554 state.decorations = WindowDecorations::Client;
1555 let is_transparent = state.is_transparent();
1556 state.renderer.update_transparency(is_transparent);
1557 }
1558 }
1559
1560 drop(state);
1561 let mut callbacks = self.0.callbacks.borrow_mut();
1562 if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1563 appearance_changed();
1564 }
1565 }
1566
1567 fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
1568 let mut state = self.0.state.borrow_mut();
1569 let client = state.client.clone();
1570 drop(state);
1571 client.update_ime_position(bounds);
1572 }
1573
1574 fn gpu_specs(&self) -> Option<GpuSpecs> {
1575 self.0.state.borrow().renderer.gpu_specs().into()
1576 }
1577}