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