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