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