1use anyhow::Context;
2
3use crate::{
4 platform::blade::{BladeRenderer, BladeSurfaceConfig},
5 px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, Modifiers,
6 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
7 Point, PromptLevel, ResizeEdge, Scene, Size, Tiling, WindowAppearance,
8 WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowParams,
9 X11ClientStatePtr,
10};
11
12use blade_graphics as gpu;
13use raw_window_handle as rwh;
14use util::{maybe, ResultExt};
15use x11rb::{
16 connection::Connection,
17 protocol::{
18 sync,
19 xinput::{self, ConnectionExt as _},
20 xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
21 },
22 wrapper::ConnectionExt as _,
23 xcb_ffi::XCBConnection,
24};
25
26use std::{
27 cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
28 sync::Arc,
29};
30
31use super::{X11Display, XINPUT_MASTER_DEVICE};
32x11rb::atom_manager! {
33 pub XcbAtoms: AtomsCookie {
34 UTF8_STRING,
35 WM_PROTOCOLS,
36 WM_DELETE_WINDOW,
37 WM_CHANGE_STATE,
38 _NET_WM_NAME,
39 _NET_WM_STATE,
40 _NET_WM_STATE_MAXIMIZED_VERT,
41 _NET_WM_STATE_MAXIMIZED_HORZ,
42 _NET_WM_STATE_FULLSCREEN,
43 _NET_WM_STATE_HIDDEN,
44 _NET_WM_STATE_FOCUSED,
45 _NET_ACTIVE_WINDOW,
46 _NET_WM_SYNC_REQUEST,
47 _NET_WM_SYNC_REQUEST_COUNTER,
48 _NET_WM_BYPASS_COMPOSITOR,
49 _NET_WM_MOVERESIZE,
50 _NET_WM_WINDOW_TYPE,
51 _NET_WM_WINDOW_TYPE_NOTIFICATION,
52 _NET_WM_SYNC,
53 _NET_SUPPORTED,
54 _MOTIF_WM_HINTS,
55 _GTK_SHOW_WINDOW_MENU,
56 _GTK_FRAME_EXTENTS,
57 _GTK_EDGE_CONSTRAINTS,
58 }
59}
60
61fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
62 let reply = xcb_connection
63 .get_geometry(x_window)
64 .unwrap()
65 .reply()
66 .unwrap();
67 gpu::Extent {
68 width: reply.width as u32,
69 height: reply.height as u32,
70 depth: 1,
71 }
72}
73
74impl ResizeEdge {
75 fn to_moveresize(&self) -> u32 {
76 match self {
77 ResizeEdge::TopLeft => 0,
78 ResizeEdge::Top => 1,
79 ResizeEdge::TopRight => 2,
80 ResizeEdge::Right => 3,
81 ResizeEdge::BottomRight => 4,
82 ResizeEdge::Bottom => 5,
83 ResizeEdge::BottomLeft => 6,
84 ResizeEdge::Left => 7,
85 }
86 }
87}
88
89#[derive(Debug)]
90struct EdgeConstraints {
91 top_tiled: bool,
92 #[allow(dead_code)]
93 top_resizable: bool,
94
95 right_tiled: bool,
96 #[allow(dead_code)]
97 right_resizable: bool,
98
99 bottom_tiled: bool,
100 #[allow(dead_code)]
101 bottom_resizable: bool,
102
103 left_tiled: bool,
104 #[allow(dead_code)]
105 left_resizable: bool,
106}
107
108impl EdgeConstraints {
109 fn from_atom(atom: u32) -> Self {
110 EdgeConstraints {
111 top_tiled: (atom & (1 << 0)) != 0,
112 top_resizable: (atom & (1 << 1)) != 0,
113 right_tiled: (atom & (1 << 2)) != 0,
114 right_resizable: (atom & (1 << 3)) != 0,
115 bottom_tiled: (atom & (1 << 4)) != 0,
116 bottom_resizable: (atom & (1 << 5)) != 0,
117 left_tiled: (atom & (1 << 6)) != 0,
118 left_resizable: (atom & (1 << 7)) != 0,
119 }
120 }
121
122 fn to_tiling(&self) -> Tiling {
123 Tiling {
124 top: self.top_tiled,
125 right: self.right_tiled,
126 bottom: self.bottom_tiled,
127 left: self.left_tiled,
128 }
129 }
130}
131
132#[derive(Debug)]
133struct Visual {
134 id: xproto::Visualid,
135 colormap: u32,
136 depth: u8,
137}
138
139struct VisualSet {
140 inherit: Visual,
141 opaque: Option<Visual>,
142 transparent: Option<Visual>,
143 root: u32,
144 black_pixel: u32,
145}
146
147fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
148 let screen = &xcb_connection.setup().roots[screen_index];
149 let mut set = VisualSet {
150 inherit: Visual {
151 id: screen.root_visual,
152 colormap: screen.default_colormap,
153 depth: screen.root_depth,
154 },
155 opaque: None,
156 transparent: None,
157 root: screen.root,
158 black_pixel: screen.black_pixel,
159 };
160
161 for depth_info in screen.allowed_depths.iter() {
162 for visual_type in depth_info.visuals.iter() {
163 let visual = Visual {
164 id: visual_type.visual_id,
165 colormap: 0,
166 depth: depth_info.depth,
167 };
168 log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
169 visual_type.visual_id,
170 visual_type.class,
171 depth_info.depth,
172 visual_type.bits_per_rgb_value,
173 visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
174 );
175
176 if (
177 visual_type.red_mask,
178 visual_type.green_mask,
179 visual_type.blue_mask,
180 ) != (0xFF0000, 0xFF00, 0xFF)
181 {
182 continue;
183 }
184 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
185 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
186
187 if alpha_mask == 0 {
188 if set.opaque.is_none() {
189 set.opaque = Some(visual);
190 }
191 } else {
192 if set.transparent.is_none() {
193 set.transparent = Some(visual);
194 }
195 }
196 }
197 }
198
199 set
200}
201
202struct RawWindow {
203 connection: *mut c_void,
204 screen_id: usize,
205 window_id: u32,
206 visual_id: u32,
207}
208
209#[derive(Default)]
210pub struct Callbacks {
211 request_frame: Option<Box<dyn FnMut()>>,
212 input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
213 active_status_change: Option<Box<dyn FnMut(bool)>>,
214 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
215 moved: Option<Box<dyn FnMut()>>,
216 should_close: Option<Box<dyn FnMut() -> bool>>,
217 close: Option<Box<dyn FnOnce()>>,
218 appearance_changed: Option<Box<dyn FnMut()>>,
219}
220
221pub struct X11WindowState {
222 pub destroyed: bool,
223 client: X11ClientStatePtr,
224 executor: ForegroundExecutor,
225 atoms: XcbAtoms,
226 x_root_window: xproto::Window,
227 pub(crate) counter_id: sync::Counter,
228 pub(crate) last_sync_counter: Option<sync::Int64>,
229 _raw: RawWindow,
230 bounds: Bounds<Pixels>,
231 scale_factor: f32,
232 renderer: BladeRenderer,
233 display: Rc<dyn PlatformDisplay>,
234 input_handler: Option<PlatformInputHandler>,
235 appearance: WindowAppearance,
236 background_appearance: WindowBackgroundAppearance,
237 maximized_vertical: bool,
238 maximized_horizontal: bool,
239 hidden: bool,
240 active: bool,
241 fullscreen: bool,
242 client_side_decorations_supported: bool,
243 decorations: WindowDecorations,
244 edge_constraints: Option<EdgeConstraints>,
245 pub handle: AnyWindowHandle,
246 last_insets: [u32; 4],
247}
248
249impl X11WindowState {
250 fn is_transparent(&self) -> bool {
251 self.background_appearance != WindowBackgroundAppearance::Opaque
252 }
253}
254
255#[derive(Clone)]
256pub(crate) struct X11WindowStatePtr {
257 pub state: Rc<RefCell<X11WindowState>>,
258 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
259 xcb_connection: Rc<XCBConnection>,
260 x_window: xproto::Window,
261}
262
263impl rwh::HasWindowHandle for RawWindow {
264 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
265 let non_zero = NonZeroU32::new(self.window_id).unwrap();
266 let mut handle = rwh::XcbWindowHandle::new(non_zero);
267 handle.visual_id = NonZeroU32::new(self.visual_id);
268 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
269 }
270}
271impl rwh::HasDisplayHandle for RawWindow {
272 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
273 let non_zero = NonNull::new(self.connection).unwrap();
274 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
275 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
276 }
277}
278
279impl rwh::HasWindowHandle for X11Window {
280 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
281 unimplemented!()
282 }
283}
284impl rwh::HasDisplayHandle for X11Window {
285 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
286 unimplemented!()
287 }
288}
289
290impl X11WindowState {
291 #[allow(clippy::too_many_arguments)]
292 pub fn new(
293 handle: AnyWindowHandle,
294 client: X11ClientStatePtr,
295 executor: ForegroundExecutor,
296 params: WindowParams,
297 xcb_connection: &Rc<XCBConnection>,
298 client_side_decorations_supported: bool,
299 x_main_screen_index: usize,
300 x_window: xproto::Window,
301 atoms: &XcbAtoms,
302 scale_factor: f32,
303 appearance: WindowAppearance,
304 ) -> anyhow::Result<Self> {
305 let x_screen_index = params
306 .display_id
307 .map_or(x_main_screen_index, |did| did.0 as usize);
308
309 let visual_set = find_visuals(&xcb_connection, x_screen_index);
310
311 let visual = match visual_set.transparent {
312 Some(visual) => visual,
313 None => {
314 log::warn!("Unable to find a transparent visual",);
315 visual_set.inherit
316 }
317 };
318 log::info!("Using {:?}", visual);
319
320 let colormap = if visual.colormap != 0 {
321 visual.colormap
322 } else {
323 let id = xcb_connection.generate_id().unwrap();
324 log::info!("Creating colormap {}", id);
325 xcb_connection
326 .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
327 .unwrap()
328 .check()?;
329 id
330 };
331
332 let win_aux = xproto::CreateWindowAux::new()
333 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
334 .border_pixel(visual_set.black_pixel)
335 .colormap(colormap)
336 .event_mask(
337 xproto::EventMask::EXPOSURE
338 | xproto::EventMask::STRUCTURE_NOTIFY
339 | xproto::EventMask::FOCUS_CHANGE
340 | xproto::EventMask::KEY_PRESS
341 | xproto::EventMask::KEY_RELEASE
342 | EventMask::PROPERTY_CHANGE,
343 );
344
345 let mut bounds = params.bounds.to_device_pixels(scale_factor);
346 if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
347 log::warn!("Window bounds contain a zero value. height={}, width={}. Falling back to defaults.", bounds.size.height.0, bounds.size.width.0);
348 bounds.size.width = 800.into();
349 bounds.size.height = 600.into();
350 }
351
352 xcb_connection
353 .create_window(
354 visual.depth,
355 x_window,
356 visual_set.root,
357 (bounds.origin.x.0 + 2) as i16,
358 bounds.origin.y.0 as i16,
359 bounds.size.width.0 as u16,
360 bounds.size.height.0 as u16,
361 0,
362 xproto::WindowClass::INPUT_OUTPUT,
363 visual.id,
364 &win_aux,
365 )
366 .unwrap()
367 .check().with_context(|| {
368 format!("CreateWindow request to X server failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
369 visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
370 })?;
371
372 let reply = xcb_connection
373 .get_geometry(x_window)
374 .unwrap()
375 .reply()
376 .unwrap();
377 if reply.x == 0 && reply.y == 0 {
378 bounds.origin.x.0 += 2;
379 // Work around a bug where our rendered content appears
380 // outside the window bounds when opened at the default position
381 // (14px, 49px on X + Gnome + Ubuntu 22).
382 xcb_connection
383 .configure_window(
384 x_window,
385 &xproto::ConfigureWindowAux::new()
386 .x(bounds.origin.x.0)
387 .y(bounds.origin.y.0),
388 )
389 .unwrap();
390 }
391 if let Some(titlebar) = params.titlebar {
392 if let Some(title) = titlebar.title {
393 xcb_connection
394 .change_property8(
395 xproto::PropMode::REPLACE,
396 x_window,
397 xproto::AtomEnum::WM_NAME,
398 xproto::AtomEnum::STRING,
399 title.as_bytes(),
400 )
401 .unwrap();
402 }
403 }
404 if params.kind == WindowKind::PopUp {
405 xcb_connection
406 .change_property32(
407 xproto::PropMode::REPLACE,
408 x_window,
409 atoms._NET_WM_WINDOW_TYPE,
410 xproto::AtomEnum::ATOM,
411 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
412 )
413 .unwrap();
414 }
415
416 xcb_connection
417 .change_property32(
418 xproto::PropMode::REPLACE,
419 x_window,
420 atoms.WM_PROTOCOLS,
421 xproto::AtomEnum::ATOM,
422 &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
423 )
424 .unwrap();
425
426 sync::initialize(xcb_connection, 3, 1).unwrap();
427 let sync_request_counter = xcb_connection.generate_id().unwrap();
428 sync::create_counter(
429 xcb_connection,
430 sync_request_counter,
431 sync::Int64 { lo: 0, hi: 0 },
432 )
433 .unwrap();
434
435 xcb_connection
436 .change_property32(
437 xproto::PropMode::REPLACE,
438 x_window,
439 atoms._NET_WM_SYNC_REQUEST_COUNTER,
440 xproto::AtomEnum::CARDINAL,
441 &[sync_request_counter],
442 )
443 .unwrap();
444
445 xcb_connection
446 .xinput_xi_select_events(
447 x_window,
448 &[xinput::EventMask {
449 deviceid: XINPUT_MASTER_DEVICE,
450 mask: vec![
451 xinput::XIEventMask::MOTION
452 | xinput::XIEventMask::BUTTON_PRESS
453 | xinput::XIEventMask::BUTTON_RELEASE
454 | xinput::XIEventMask::ENTER
455 | xinput::XIEventMask::LEAVE,
456 ],
457 }],
458 )
459 .unwrap();
460
461 xcb_connection.flush().unwrap();
462
463 let raw = RawWindow {
464 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
465 xcb_connection,
466 ) as *mut _,
467 screen_id: x_screen_index,
468 window_id: x_window,
469 visual_id: visual.id,
470 };
471 let gpu = Arc::new(
472 unsafe {
473 gpu::Context::init_windowed(
474 &raw,
475 gpu::ContextDesc {
476 validation: false,
477 capture: false,
478 overlay: false,
479 },
480 )
481 }
482 .map_err(|e| anyhow::anyhow!("{:?}", e))?,
483 );
484
485 let config = BladeSurfaceConfig {
486 // Note: this has to be done after the GPU init, or otherwise
487 // the sizes are immediately invalidated.
488 size: query_render_extent(xcb_connection, x_window),
489 // We set it to transparent by default, even if we have client-side
490 // decorations, since those seem to work on X11 even without `true` here.
491 // If the window appearance changes, then the renderer will get updated
492 // too
493 transparent: false,
494 };
495 xcb_connection.map_window(x_window).unwrap();
496
497 Ok(Self {
498 client,
499 executor,
500 display: Rc::new(
501 X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
502 ),
503 _raw: raw,
504 x_root_window: visual_set.root,
505 bounds: bounds.to_pixels(scale_factor),
506 scale_factor,
507 renderer: BladeRenderer::new(gpu, config),
508 atoms: *atoms,
509 input_handler: None,
510 active: false,
511 fullscreen: false,
512 maximized_vertical: false,
513 maximized_horizontal: false,
514 hidden: false,
515 appearance,
516 handle,
517 background_appearance: WindowBackgroundAppearance::Opaque,
518 destroyed: false,
519 client_side_decorations_supported,
520 decorations: WindowDecorations::Server,
521 last_insets: [0, 0, 0, 0],
522 edge_constraints: None,
523 counter_id: sync_request_counter,
524 last_sync_counter: None,
525 })
526 }
527
528 fn content_size(&self) -> Size<Pixels> {
529 let size = self.renderer.viewport_size();
530 Size {
531 width: size.width.into(),
532 height: size.height.into(),
533 }
534 }
535}
536
537pub(crate) struct X11Window(pub X11WindowStatePtr);
538
539impl Drop for X11Window {
540 fn drop(&mut self) {
541 let mut state = self.0.state.borrow_mut();
542 state.renderer.destroy();
543
544 let destroy_x_window = maybe!({
545 self.0.xcb_connection.unmap_window(self.0.x_window)?;
546 self.0.xcb_connection.destroy_window(self.0.x_window)?;
547 self.0.xcb_connection.flush()?;
548
549 anyhow::Ok(())
550 })
551 .context("unmapping and destroying X11 window")
552 .log_err();
553
554 if destroy_x_window.is_some() {
555 // Mark window as destroyed so that we can filter out when X11 events
556 // for it still come in.
557 state.destroyed = true;
558
559 let this_ptr = self.0.clone();
560 let client_ptr = state.client.clone();
561 state
562 .executor
563 .spawn(async move {
564 this_ptr.close();
565 client_ptr.drop_window(this_ptr.x_window);
566 })
567 .detach();
568 }
569
570 drop(state);
571 }
572}
573
574enum WmHintPropertyState {
575 // Remove = 0,
576 // Add = 1,
577 Toggle = 2,
578}
579
580impl X11Window {
581 #[allow(clippy::too_many_arguments)]
582 pub fn new(
583 handle: AnyWindowHandle,
584 client: X11ClientStatePtr,
585 executor: ForegroundExecutor,
586 params: WindowParams,
587 xcb_connection: &Rc<XCBConnection>,
588 client_side_decorations_supported: bool,
589 x_main_screen_index: usize,
590 x_window: xproto::Window,
591 atoms: &XcbAtoms,
592 scale_factor: f32,
593 appearance: WindowAppearance,
594 ) -> anyhow::Result<Self> {
595 let ptr = X11WindowStatePtr {
596 state: Rc::new(RefCell::new(X11WindowState::new(
597 handle,
598 client,
599 executor,
600 params,
601 xcb_connection,
602 client_side_decorations_supported,
603 x_main_screen_index,
604 x_window,
605 atoms,
606 scale_factor,
607 appearance,
608 )?)),
609 callbacks: Rc::new(RefCell::new(Callbacks::default())),
610 xcb_connection: xcb_connection.clone(),
611 x_window,
612 };
613
614 let state = ptr.state.borrow_mut();
615 ptr.set_wm_properties(state);
616
617 Ok(Self(ptr))
618 }
619
620 fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
621 let state = self.0.state.borrow();
622 let message = ClientMessageEvent::new(
623 32,
624 self.0.x_window,
625 state.atoms._NET_WM_STATE,
626 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
627 );
628 self.0
629 .xcb_connection
630 .send_event(
631 false,
632 state.x_root_window,
633 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
634 message,
635 )
636 .unwrap()
637 .check()
638 .unwrap();
639 }
640
641 fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
642 let state = self.0.state.borrow();
643 self.0
644 .xcb_connection
645 .translate_coordinates(
646 self.0.x_window,
647 state.x_root_window,
648 (position.x.0 * state.scale_factor) as i16,
649 (position.y.0 * state.scale_factor) as i16,
650 )
651 .unwrap()
652 .reply()
653 .unwrap()
654 }
655
656 fn send_moveresize(&self, flag: u32) {
657 let state = self.0.state.borrow();
658
659 self.0
660 .xcb_connection
661 .ungrab_pointer(x11rb::CURRENT_TIME)
662 .unwrap()
663 .check()
664 .unwrap();
665
666 let pointer = self
667 .0
668 .xcb_connection
669 .query_pointer(self.0.x_window)
670 .unwrap()
671 .reply()
672 .unwrap();
673 let message = ClientMessageEvent::new(
674 32,
675 self.0.x_window,
676 state.atoms._NET_WM_MOVERESIZE,
677 [
678 pointer.root_x as u32,
679 pointer.root_y as u32,
680 flag,
681 0, // Left mouse button
682 0,
683 ],
684 );
685 self.0
686 .xcb_connection
687 .send_event(
688 false,
689 state.x_root_window,
690 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
691 message,
692 )
693 .unwrap();
694
695 self.0.xcb_connection.flush().unwrap();
696 }
697}
698
699impl X11WindowStatePtr {
700 pub fn should_close(&self) -> bool {
701 let mut cb = self.callbacks.borrow_mut();
702 if let Some(mut should_close) = cb.should_close.take() {
703 let result = (should_close)();
704 cb.should_close = Some(should_close);
705 result
706 } else {
707 true
708 }
709 }
710
711 pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) {
712 let mut state = self.state.borrow_mut();
713 if event.atom == state.atoms._NET_WM_STATE {
714 self.set_wm_properties(state);
715 } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
716 self.set_edge_constraints(state);
717 }
718 }
719
720 fn set_edge_constraints(&self, mut state: std::cell::RefMut<X11WindowState>) {
721 let reply = self
722 .xcb_connection
723 .get_property(
724 false,
725 self.x_window,
726 state.atoms._GTK_EDGE_CONSTRAINTS,
727 xproto::AtomEnum::CARDINAL,
728 0,
729 4,
730 )
731 .unwrap()
732 .reply()
733 .unwrap();
734
735 if reply.value_len != 0 {
736 let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
737 let edge_constraints = EdgeConstraints::from_atom(atom);
738 state.edge_constraints.replace(edge_constraints);
739 }
740 }
741
742 fn set_wm_properties(&self, mut state: std::cell::RefMut<X11WindowState>) {
743 let reply = self
744 .xcb_connection
745 .get_property(
746 false,
747 self.x_window,
748 state.atoms._NET_WM_STATE,
749 xproto::AtomEnum::ATOM,
750 0,
751 u32::MAX,
752 )
753 .unwrap()
754 .reply()
755 .unwrap();
756
757 let atoms = reply
758 .value
759 .chunks_exact(4)
760 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
761
762 state.active = false;
763 state.fullscreen = false;
764 state.maximized_vertical = false;
765 state.maximized_horizontal = false;
766 state.hidden = true;
767
768 for atom in atoms {
769 if atom == state.atoms._NET_WM_STATE_FOCUSED {
770 state.active = true;
771 } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
772 state.fullscreen = true;
773 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
774 state.maximized_vertical = true;
775 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
776 state.maximized_horizontal = true;
777 } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
778 state.hidden = true;
779 }
780 }
781 }
782
783 pub fn close(&self) {
784 let mut callbacks = self.callbacks.borrow_mut();
785 if let Some(fun) = callbacks.close.take() {
786 fun()
787 }
788 }
789
790 pub fn refresh(&self) {
791 let mut cb = self.callbacks.borrow_mut();
792 if let Some(ref mut fun) = cb.request_frame {
793 fun();
794 }
795 }
796
797 pub fn handle_input(&self, input: PlatformInput) {
798 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
799 if !fun(input.clone()).propagate {
800 return;
801 }
802 }
803 if let PlatformInput::KeyDown(event) = input {
804 let mut state = self.state.borrow_mut();
805 if let Some(mut input_handler) = state.input_handler.take() {
806 if let Some(ime_key) = &event.keystroke.ime_key {
807 drop(state);
808 input_handler.replace_text_in_range(None, ime_key);
809 state = self.state.borrow_mut();
810 }
811 state.input_handler = Some(input_handler);
812 }
813 }
814 }
815
816 pub fn handle_ime_commit(&self, text: String) {
817 let mut state = self.state.borrow_mut();
818 if let Some(mut input_handler) = state.input_handler.take() {
819 drop(state);
820 input_handler.replace_text_in_range(None, &text);
821 let mut state = self.state.borrow_mut();
822 state.input_handler = Some(input_handler);
823 }
824 }
825
826 pub fn handle_ime_preedit(&self, text: String) {
827 let mut state = self.state.borrow_mut();
828 if let Some(mut input_handler) = state.input_handler.take() {
829 drop(state);
830 input_handler.replace_and_mark_text_in_range(None, &text, None);
831 let mut state = self.state.borrow_mut();
832 state.input_handler = Some(input_handler);
833 }
834 }
835
836 pub fn handle_ime_unmark(&self) {
837 let mut state = self.state.borrow_mut();
838 if let Some(mut input_handler) = state.input_handler.take() {
839 drop(state);
840 input_handler.unmark_text();
841 let mut state = self.state.borrow_mut();
842 state.input_handler = Some(input_handler);
843 }
844 }
845
846 pub fn handle_ime_delete(&self) {
847 let mut state = self.state.borrow_mut();
848 if let Some(mut input_handler) = state.input_handler.take() {
849 drop(state);
850 if let Some(marked) = input_handler.marked_text_range() {
851 input_handler.replace_text_in_range(Some(marked), "");
852 }
853 let mut state = self.state.borrow_mut();
854 state.input_handler = Some(input_handler);
855 }
856 }
857
858 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
859 let mut state = self.state.borrow_mut();
860 let mut bounds: Option<Bounds<Pixels>> = None;
861 if let Some(mut input_handler) = state.input_handler.take() {
862 drop(state);
863 if let Some(range) = input_handler.selected_text_range() {
864 bounds = input_handler.bounds_for_range(range);
865 }
866 let mut state = self.state.borrow_mut();
867 state.input_handler = Some(input_handler);
868 };
869 bounds
870 }
871
872 pub fn configure(&self, bounds: Bounds<i32>) {
873 let mut resize_args = None;
874 let is_resize;
875 {
876 let mut state = self.state.borrow_mut();
877 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
878
879 is_resize = bounds.size.width != state.bounds.size.width
880 || bounds.size.height != state.bounds.size.height;
881
882 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
883 // because it contains wrong values.
884 if is_resize {
885 state.bounds.size = bounds.size;
886 } else {
887 state.bounds = bounds;
888 }
889
890 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
891 if true {
892 state.renderer.update_drawable_size(size(
893 DevicePixels(gpu_size.width as i32),
894 DevicePixels(gpu_size.height as i32),
895 ));
896 resize_args = Some((state.content_size(), state.scale_factor));
897 }
898 if let Some(value) = state.last_sync_counter.take() {
899 sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap();
900 }
901 }
902
903 let mut callbacks = self.callbacks.borrow_mut();
904 if let Some((content_size, scale_factor)) = resize_args {
905 if let Some(ref mut fun) = callbacks.resize {
906 fun(content_size, scale_factor)
907 }
908 }
909 if !is_resize {
910 if let Some(ref mut fun) = callbacks.moved {
911 fun()
912 }
913 }
914 }
915
916 pub fn set_focused(&self, focus: bool) {
917 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
918 fun(focus);
919 }
920 }
921
922 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
923 let mut state = self.state.borrow_mut();
924 state.appearance = appearance;
925 let is_transparent = state.is_transparent();
926 state.renderer.update_transparency(is_transparent);
927 state.appearance = appearance;
928 drop(state);
929 let mut callbacks = self.callbacks.borrow_mut();
930 if let Some(ref mut fun) = callbacks.appearance_changed {
931 (fun)()
932 }
933 }
934}
935
936impl PlatformWindow for X11Window {
937 fn bounds(&self) -> Bounds<Pixels> {
938 self.0.state.borrow().bounds
939 }
940
941 fn is_maximized(&self) -> bool {
942 let state = self.0.state.borrow();
943
944 // A maximized window that gets minimized will still retain its maximized state.
945 !state.hidden && state.maximized_vertical && state.maximized_horizontal
946 }
947
948 fn window_bounds(&self) -> WindowBounds {
949 let state = self.0.state.borrow();
950 if self.is_maximized() {
951 WindowBounds::Maximized(state.bounds)
952 } else {
953 WindowBounds::Windowed(state.bounds)
954 }
955 }
956
957 fn content_size(&self) -> Size<Pixels> {
958 // We divide by the scale factor here because this value is queried to determine how much to draw,
959 // but it will be multiplied later by the scale to adjust for scaling.
960 let state = self.0.state.borrow();
961 state
962 .content_size()
963 .map(|size| size.div(state.scale_factor))
964 }
965
966 fn scale_factor(&self) -> f32 {
967 self.0.state.borrow().scale_factor
968 }
969
970 fn appearance(&self) -> WindowAppearance {
971 self.0.state.borrow().appearance
972 }
973
974 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
975 Some(self.0.state.borrow().display.clone())
976 }
977
978 fn mouse_position(&self) -> Point<Pixels> {
979 let reply = self
980 .0
981 .xcb_connection
982 .query_pointer(self.0.x_window)
983 .unwrap()
984 .reply()
985 .unwrap();
986 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
987 }
988
989 fn modifiers(&self) -> Modifiers {
990 self.0
991 .state
992 .borrow()
993 .client
994 .0
995 .upgrade()
996 .map(|ref_cell| ref_cell.borrow().modifiers)
997 .unwrap_or_default()
998 }
999
1000 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1001 self.0.state.borrow_mut().input_handler = Some(input_handler);
1002 }
1003
1004 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1005 self.0.state.borrow_mut().input_handler.take()
1006 }
1007
1008 fn prompt(
1009 &self,
1010 _level: PromptLevel,
1011 _msg: &str,
1012 _detail: Option<&str>,
1013 _answers: &[&str],
1014 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1015 None
1016 }
1017
1018 fn activate(&self) {
1019 let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1020 let message = xproto::ClientMessageEvent::new(
1021 32,
1022 self.0.x_window,
1023 self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1024 data,
1025 );
1026 self.0
1027 .xcb_connection
1028 .send_event(
1029 false,
1030 self.0.state.borrow().x_root_window,
1031 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1032 message,
1033 )
1034 .log_err();
1035 self.0
1036 .xcb_connection
1037 .set_input_focus(
1038 xproto::InputFocus::POINTER_ROOT,
1039 self.0.x_window,
1040 xproto::Time::CURRENT_TIME,
1041 )
1042 .log_err();
1043 self.0.xcb_connection.flush().unwrap();
1044 }
1045
1046 fn is_active(&self) -> bool {
1047 self.0.state.borrow().active
1048 }
1049
1050 fn set_title(&mut self, title: &str) {
1051 self.0
1052 .xcb_connection
1053 .change_property8(
1054 xproto::PropMode::REPLACE,
1055 self.0.x_window,
1056 xproto::AtomEnum::WM_NAME,
1057 xproto::AtomEnum::STRING,
1058 title.as_bytes(),
1059 )
1060 .unwrap();
1061
1062 self.0
1063 .xcb_connection
1064 .change_property8(
1065 xproto::PropMode::REPLACE,
1066 self.0.x_window,
1067 self.0.state.borrow().atoms._NET_WM_NAME,
1068 self.0.state.borrow().atoms.UTF8_STRING,
1069 title.as_bytes(),
1070 )
1071 .unwrap();
1072 self.0.xcb_connection.flush().unwrap();
1073 }
1074
1075 fn set_app_id(&mut self, app_id: &str) {
1076 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1077 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1078 data.push(b'\0');
1079 data.extend(app_id.bytes()); // class
1080
1081 self.0
1082 .xcb_connection
1083 .change_property8(
1084 xproto::PropMode::REPLACE,
1085 self.0.x_window,
1086 xproto::AtomEnum::WM_CLASS,
1087 xproto::AtomEnum::STRING,
1088 &data,
1089 )
1090 .unwrap()
1091 .check()
1092 .unwrap();
1093 }
1094
1095 fn set_edited(&mut self, _edited: bool) {
1096 log::info!("ignoring macOS specific set_edited");
1097 }
1098
1099 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1100 let mut state = self.0.state.borrow_mut();
1101 state.background_appearance = background_appearance;
1102 let transparent = state.is_transparent();
1103 state.renderer.update_transparency(transparent);
1104 }
1105
1106 fn show_character_palette(&self) {
1107 log::info!("ignoring macOS specific show_character_palette");
1108 }
1109
1110 fn minimize(&self) {
1111 let state = self.0.state.borrow();
1112 const WINDOW_ICONIC_STATE: u32 = 3;
1113 let message = ClientMessageEvent::new(
1114 32,
1115 self.0.x_window,
1116 state.atoms.WM_CHANGE_STATE,
1117 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1118 );
1119 self.0
1120 .xcb_connection
1121 .send_event(
1122 false,
1123 state.x_root_window,
1124 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1125 message,
1126 )
1127 .unwrap()
1128 .check()
1129 .unwrap();
1130 }
1131
1132 fn zoom(&self) {
1133 let state = self.0.state.borrow();
1134 self.set_wm_hints(
1135 WmHintPropertyState::Toggle,
1136 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1137 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1138 );
1139 }
1140
1141 fn toggle_fullscreen(&self) {
1142 let state = self.0.state.borrow();
1143 self.set_wm_hints(
1144 WmHintPropertyState::Toggle,
1145 state.atoms._NET_WM_STATE_FULLSCREEN,
1146 xproto::AtomEnum::NONE.into(),
1147 );
1148 }
1149
1150 fn is_fullscreen(&self) -> bool {
1151 self.0.state.borrow().fullscreen
1152 }
1153
1154 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1155 self.0.callbacks.borrow_mut().request_frame = Some(callback);
1156 }
1157
1158 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1159 self.0.callbacks.borrow_mut().input = Some(callback);
1160 }
1161
1162 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1163 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1164 }
1165
1166 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1167 self.0.callbacks.borrow_mut().resize = Some(callback);
1168 }
1169
1170 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1171 self.0.callbacks.borrow_mut().moved = Some(callback);
1172 }
1173
1174 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1175 self.0.callbacks.borrow_mut().should_close = Some(callback);
1176 }
1177
1178 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1179 self.0.callbacks.borrow_mut().close = Some(callback);
1180 }
1181
1182 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1183 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1184 }
1185
1186 fn draw(&self, scene: &Scene) {
1187 let mut inner = self.0.state.borrow_mut();
1188 inner.renderer.draw(scene);
1189 }
1190
1191 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1192 let inner = self.0.state.borrow();
1193 inner.renderer.sprite_atlas().clone()
1194 }
1195
1196 fn show_window_menu(&self, position: Point<Pixels>) {
1197 let state = self.0.state.borrow();
1198 let coords = self.get_root_position(position);
1199 let message = ClientMessageEvent::new(
1200 32,
1201 self.0.x_window,
1202 state.atoms._GTK_SHOW_WINDOW_MENU,
1203 [
1204 XINPUT_MASTER_DEVICE as u32,
1205 coords.dst_x as u32,
1206 coords.dst_y as u32,
1207 0,
1208 0,
1209 ],
1210 );
1211 self.0
1212 .xcb_connection
1213 .send_event(
1214 false,
1215 state.x_root_window,
1216 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1217 message,
1218 )
1219 .unwrap()
1220 .check()
1221 .unwrap();
1222 }
1223
1224 fn start_window_move(&self) {
1225 const MOVERESIZE_MOVE: u32 = 8;
1226 self.send_moveresize(MOVERESIZE_MOVE);
1227 }
1228
1229 fn start_window_resize(&self, edge: ResizeEdge) {
1230 self.send_moveresize(edge.to_moveresize());
1231 }
1232
1233 fn window_decorations(&self) -> crate::Decorations {
1234 let state = self.0.state.borrow();
1235
1236 // Client window decorations require compositor support
1237 if !state.client_side_decorations_supported {
1238 return Decorations::Server;
1239 }
1240
1241 match state.decorations {
1242 WindowDecorations::Server => Decorations::Server,
1243 WindowDecorations::Client => {
1244 let tiling = if let Some(edge_constraints) = &state.edge_constraints {
1245 edge_constraints.to_tiling()
1246 } else {
1247 // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1248 Tiling {
1249 top: state.maximized_vertical,
1250 bottom: state.maximized_vertical,
1251 left: state.maximized_horizontal,
1252 right: state.maximized_horizontal,
1253 }
1254 };
1255
1256 Decorations::Client { tiling }
1257 }
1258 }
1259 }
1260
1261 fn set_client_inset(&self, inset: Pixels) {
1262 let mut state = self.0.state.borrow_mut();
1263
1264 let dp = (inset.0 * state.scale_factor) as u32;
1265
1266 let insets = if let Some(edge_constraints) = &state.edge_constraints {
1267 let left = if edge_constraints.left_tiled { 0 } else { dp };
1268 let top = if edge_constraints.top_tiled { 0 } else { dp };
1269 let right = if edge_constraints.right_tiled { 0 } else { dp };
1270 let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1271
1272 [left, right, top, bottom]
1273 } else {
1274 let (left, right) = if state.maximized_horizontal {
1275 (0, 0)
1276 } else {
1277 (dp, dp)
1278 };
1279 let (top, bottom) = if state.maximized_vertical {
1280 (0, 0)
1281 } else {
1282 (dp, dp)
1283 };
1284 [left, right, top, bottom]
1285 };
1286
1287 if state.last_insets != insets {
1288 state.last_insets = insets;
1289
1290 self.0
1291 .xcb_connection
1292 .change_property(
1293 xproto::PropMode::REPLACE,
1294 self.0.x_window,
1295 state.atoms._GTK_FRAME_EXTENTS,
1296 xproto::AtomEnum::CARDINAL,
1297 size_of::<u32>() as u8 * 8,
1298 4,
1299 bytemuck::cast_slice::<u32, u8>(&insets),
1300 )
1301 .unwrap()
1302 .check()
1303 .unwrap();
1304 }
1305 }
1306
1307 fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1308 let mut state = self.0.state.borrow_mut();
1309
1310 if matches!(decorations, crate::WindowDecorations::Client)
1311 && !state.client_side_decorations_supported
1312 {
1313 log::info!(
1314 "x11: no compositor present, falling back to server-side window decorations"
1315 );
1316 decorations = crate::WindowDecorations::Server;
1317 }
1318
1319 // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1320 let hints_data: [u32; 5] = match decorations {
1321 WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1322 WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1323 };
1324
1325 self.0
1326 .xcb_connection
1327 .change_property(
1328 xproto::PropMode::REPLACE,
1329 self.0.x_window,
1330 state.atoms._MOTIF_WM_HINTS,
1331 state.atoms._MOTIF_WM_HINTS,
1332 std::mem::size_of::<u32>() as u8 * 8,
1333 5,
1334 bytemuck::cast_slice::<u32, u8>(&hints_data),
1335 )
1336 .unwrap()
1337 .check()
1338 .unwrap();
1339
1340 match decorations {
1341 WindowDecorations::Server => {
1342 state.decorations = WindowDecorations::Server;
1343 let is_transparent = state.is_transparent();
1344 state.renderer.update_transparency(is_transparent);
1345 }
1346 WindowDecorations::Client => {
1347 state.decorations = WindowDecorations::Client;
1348 let is_transparent = state.is_transparent();
1349 state.renderer.update_transparency(is_transparent);
1350 }
1351 }
1352
1353 drop(state);
1354 let mut callbacks = self.0.callbacks.borrow_mut();
1355 if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1356 appearance_changed();
1357 }
1358 }
1359}