1use anyhow::Context;
2
3use crate::{
4 platform::blade::{BladeRenderer, BladeSurfaceConfig},
5 px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
6 PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
7 PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
8 WindowKind, WindowParams, X11ClientStatePtr,
9};
10
11use blade_graphics as gpu;
12use raw_window_handle as rwh;
13use util::{maybe, ResultExt};
14use x11rb::{
15 connection::Connection,
16 protocol::{
17 randr::{self, ConnectionExt as _},
18 xinput::{self, ConnectionExt as _},
19 xproto::{
20 self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
21 },
22 },
23 wrapper::ConnectionExt as _,
24 xcb_ffi::XCBConnection,
25};
26
27use std::{
28 cell::RefCell,
29 ffi::c_void,
30 num::NonZeroU32,
31 ops::Div,
32 ptr::NonNull,
33 rc::Rc,
34 sync::{self, Arc},
35 time::{Duration, Instant},
36};
37
38use super::{X11Display, XINPUT_MASTER_DEVICE};
39
40x11rb::atom_manager! {
41 pub XcbAtoms: AtomsCookie {
42 UTF8_STRING,
43 WM_PROTOCOLS,
44 WM_DELETE_WINDOW,
45 WM_CHANGE_STATE,
46 _NET_WM_NAME,
47 _NET_WM_STATE,
48 _NET_WM_STATE_MAXIMIZED_VERT,
49 _NET_WM_STATE_MAXIMIZED_HORZ,
50 _NET_WM_STATE_FULLSCREEN,
51 _NET_WM_STATE_HIDDEN,
52 _NET_WM_STATE_FOCUSED,
53 _NET_WM_MOVERESIZE,
54 _NET_WM_WINDOW_TYPE,
55 _NET_WM_WINDOW_TYPE_NOTIFICATION,
56 _GTK_SHOW_WINDOW_MENU,
57 }
58}
59
60fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
61 let reply = xcb_connection
62 .get_geometry(x_window)
63 .unwrap()
64 .reply()
65 .unwrap();
66 gpu::Extent {
67 width: reply.width as u32,
68 height: reply.height as u32,
69 depth: 1,
70 }
71}
72
73#[derive(Debug)]
74struct Visual {
75 id: xproto::Visualid,
76 colormap: u32,
77 depth: u8,
78}
79
80struct VisualSet {
81 inherit: Visual,
82 opaque: Option<Visual>,
83 transparent: Option<Visual>,
84 root: u32,
85 black_pixel: u32,
86}
87
88fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
89 let screen = &xcb_connection.setup().roots[screen_index];
90 let mut set = VisualSet {
91 inherit: Visual {
92 id: screen.root_visual,
93 colormap: screen.default_colormap,
94 depth: screen.root_depth,
95 },
96 opaque: None,
97 transparent: None,
98 root: screen.root,
99 black_pixel: screen.black_pixel,
100 };
101
102 for depth_info in screen.allowed_depths.iter() {
103 for visual_type in depth_info.visuals.iter() {
104 let visual = Visual {
105 id: visual_type.visual_id,
106 colormap: 0,
107 depth: depth_info.depth,
108 };
109 log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
110 visual_type.visual_id,
111 visual_type.class,
112 depth_info.depth,
113 visual_type.bits_per_rgb_value,
114 visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
115 );
116
117 if (
118 visual_type.red_mask,
119 visual_type.green_mask,
120 visual_type.blue_mask,
121 ) != (0xFF0000, 0xFF00, 0xFF)
122 {
123 continue;
124 }
125 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
126 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
127
128 if alpha_mask == 0 {
129 if set.opaque.is_none() {
130 set.opaque = Some(visual);
131 }
132 } else {
133 if set.transparent.is_none() {
134 set.transparent = Some(visual);
135 }
136 }
137 }
138 }
139
140 set
141}
142
143struct RawWindow {
144 connection: *mut c_void,
145 screen_id: usize,
146 window_id: u32,
147 visual_id: u32,
148}
149
150#[derive(Default)]
151pub struct Callbacks {
152 request_frame: Option<Box<dyn FnMut()>>,
153 input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
154 active_status_change: Option<Box<dyn FnMut(bool)>>,
155 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
156 moved: Option<Box<dyn FnMut()>>,
157 should_close: Option<Box<dyn FnMut() -> bool>>,
158 close: Option<Box<dyn FnOnce()>>,
159 appearance_changed: Option<Box<dyn FnMut()>>,
160}
161
162pub struct X11WindowState {
163 pub destroyed: bool,
164 pub last_render_at: Option<Instant>,
165 pub refresh_rate: Duration,
166 client: X11ClientStatePtr,
167 executor: ForegroundExecutor,
168 atoms: XcbAtoms,
169 x_root_window: xproto::Window,
170 _raw: RawWindow,
171 bounds: Bounds<Pixels>,
172 scale_factor: f32,
173 renderer: BladeRenderer,
174 display: Rc<dyn PlatformDisplay>,
175 input_handler: Option<PlatformInputHandler>,
176 appearance: WindowAppearance,
177 pub handle: AnyWindowHandle,
178}
179
180#[derive(Clone)]
181pub(crate) struct X11WindowStatePtr {
182 pub state: Rc<RefCell<X11WindowState>>,
183 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
184 xcb_connection: Rc<XCBConnection>,
185 x_window: xproto::Window,
186}
187
188impl rwh::HasWindowHandle for RawWindow {
189 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
190 let non_zero = NonZeroU32::new(self.window_id).unwrap();
191 let mut handle = rwh::XcbWindowHandle::new(non_zero);
192 handle.visual_id = NonZeroU32::new(self.visual_id);
193 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
194 }
195}
196impl rwh::HasDisplayHandle for RawWindow {
197 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
198 let non_zero = NonNull::new(self.connection).unwrap();
199 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
200 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
201 }
202}
203
204impl rwh::HasWindowHandle for X11Window {
205 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
206 unimplemented!()
207 }
208}
209impl rwh::HasDisplayHandle for X11Window {
210 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
211 unimplemented!()
212 }
213}
214
215impl X11WindowState {
216 #[allow(clippy::too_many_arguments)]
217 pub fn new(
218 handle: AnyWindowHandle,
219 client: X11ClientStatePtr,
220 executor: ForegroundExecutor,
221 params: WindowParams,
222 xcb_connection: &Rc<XCBConnection>,
223 x_main_screen_index: usize,
224 x_window: xproto::Window,
225 atoms: &XcbAtoms,
226 scale_factor: f32,
227 appearance: WindowAppearance,
228 ) -> anyhow::Result<Self> {
229 let x_screen_index = params
230 .display_id
231 .map_or(x_main_screen_index, |did| did.0 as usize);
232
233 let visual_set = find_visuals(&xcb_connection, x_screen_index);
234 let visual_maybe = match params.window_background {
235 WindowBackgroundAppearance::Opaque => visual_set.opaque,
236 WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
237 visual_set.transparent
238 }
239 };
240 let visual = match visual_maybe {
241 Some(visual) => visual,
242 None => {
243 log::warn!(
244 "Unable to find a matching visual for {:?}",
245 params.window_background
246 );
247 visual_set.inherit
248 }
249 };
250 log::info!("Using {:?}", visual);
251
252 let colormap = if visual.colormap != 0 {
253 visual.colormap
254 } else {
255 let id = xcb_connection.generate_id().unwrap();
256 log::info!("Creating colormap {}", id);
257 xcb_connection
258 .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
259 .unwrap()
260 .check()?;
261 id
262 };
263
264 let win_aux = xproto::CreateWindowAux::new()
265 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
266 .border_pixel(visual_set.black_pixel)
267 .colormap(colormap)
268 .event_mask(
269 xproto::EventMask::EXPOSURE
270 | xproto::EventMask::STRUCTURE_NOTIFY
271 | xproto::EventMask::FOCUS_CHANGE
272 | xproto::EventMask::KEY_PRESS
273 | xproto::EventMask::KEY_RELEASE,
274 );
275
276 let mut bounds = params.bounds.to_device_pixels(scale_factor);
277
278 xcb_connection
279 .create_window(
280 visual.depth,
281 x_window,
282 visual_set.root,
283 (bounds.origin.x.0 + 2) as i16,
284 bounds.origin.y.0 as i16,
285 bounds.size.width.0 as u16,
286 bounds.size.height.0 as u16,
287 0,
288 xproto::WindowClass::INPUT_OUTPUT,
289 visual.id,
290 &win_aux,
291 )
292 .unwrap()
293 .check().with_context(|| {
294 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: {}",
295 visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
296 })?;
297
298 let reply = xcb_connection
299 .get_geometry(x_window)
300 .unwrap()
301 .reply()
302 .unwrap();
303 if reply.x == 0 && reply.y == 0 {
304 bounds.origin.x.0 += 2;
305 // Work around a bug where our rendered content appears
306 // outside the window bounds when opened at the default position
307 // (14px, 49px on X + Gnome + Ubuntu 22).
308 xcb_connection
309 .configure_window(
310 x_window,
311 &xproto::ConfigureWindowAux::new()
312 .x(bounds.origin.x.0)
313 .y(bounds.origin.y.0),
314 )
315 .unwrap();
316 }
317 if let Some(titlebar) = params.titlebar {
318 if let Some(title) = titlebar.title {
319 xcb_connection
320 .change_property8(
321 xproto::PropMode::REPLACE,
322 x_window,
323 xproto::AtomEnum::WM_NAME,
324 xproto::AtomEnum::STRING,
325 title.as_bytes(),
326 )
327 .unwrap();
328 }
329 }
330 if params.kind == WindowKind::PopUp {
331 xcb_connection
332 .change_property32(
333 xproto::PropMode::REPLACE,
334 x_window,
335 atoms._NET_WM_WINDOW_TYPE,
336 xproto::AtomEnum::ATOM,
337 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
338 )
339 .unwrap();
340 }
341
342 xcb_connection
343 .change_property32(
344 xproto::PropMode::REPLACE,
345 x_window,
346 atoms.WM_PROTOCOLS,
347 xproto::AtomEnum::ATOM,
348 &[atoms.WM_DELETE_WINDOW],
349 )
350 .unwrap();
351
352 xcb_connection
353 .xinput_xi_select_events(
354 x_window,
355 &[xinput::EventMask {
356 deviceid: XINPUT_MASTER_DEVICE,
357 mask: vec![
358 xinput::XIEventMask::MOTION
359 | xinput::XIEventMask::BUTTON_PRESS
360 | xinput::XIEventMask::BUTTON_RELEASE
361 | xinput::XIEventMask::LEAVE,
362 ],
363 }],
364 )
365 .unwrap();
366
367 xcb_connection.flush().unwrap();
368
369 let raw = RawWindow {
370 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
371 xcb_connection,
372 ) as *mut _,
373 screen_id: x_screen_index,
374 window_id: x_window,
375 visual_id: visual.id,
376 };
377 let gpu = Arc::new(
378 unsafe {
379 gpu::Context::init_windowed(
380 &raw,
381 gpu::ContextDesc {
382 validation: false,
383 capture: false,
384 overlay: false,
385 },
386 )
387 }
388 .map_err(|e| anyhow::anyhow!("{:?}", e))?,
389 );
390
391 let config = BladeSurfaceConfig {
392 // Note: this has to be done after the GPU init, or otherwise
393 // the sizes are immediately invalidated.
394 size: query_render_extent(xcb_connection, x_window),
395 transparent: params.window_background != WindowBackgroundAppearance::Opaque,
396 };
397 xcb_connection.map_window(x_window).unwrap();
398
399 let screen_resources = xcb_connection
400 .randr_get_screen_resources(x_window)
401 .unwrap()
402 .reply()
403 .expect("Could not find available screens");
404
405 let mode = screen_resources
406 .crtcs
407 .iter()
408 .find_map(|crtc| {
409 let crtc_info = xcb_connection
410 .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
411 .ok()?
412 .reply()
413 .ok()?;
414
415 screen_resources
416 .modes
417 .iter()
418 .find(|m| m.id == crtc_info.mode)
419 })
420 .expect("Unable to find screen refresh rate");
421
422 let refresh_rate = mode_refresh_rate(mode);
423
424 Ok(Self {
425 client,
426 executor,
427 display: Rc::new(
428 X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
429 ),
430 _raw: raw,
431 x_root_window: visual_set.root,
432 bounds: bounds.to_pixels(scale_factor),
433 scale_factor,
434 renderer: BladeRenderer::new(gpu, config),
435 atoms: *atoms,
436 input_handler: None,
437 appearance,
438 handle,
439 destroyed: false,
440 last_render_at: None,
441 refresh_rate,
442 })
443 }
444
445 fn content_size(&self) -> Size<Pixels> {
446 let size = self.renderer.viewport_size();
447 Size {
448 width: size.width.into(),
449 height: size.height.into(),
450 }
451 }
452}
453
454pub(crate) struct X11Window(pub X11WindowStatePtr);
455
456impl Drop for X11Window {
457 fn drop(&mut self) {
458 let mut state = self.0.state.borrow_mut();
459 state.renderer.destroy();
460
461 let destroy_x_window = maybe!({
462 self.0.xcb_connection.unmap_window(self.0.x_window)?;
463 self.0.xcb_connection.destroy_window(self.0.x_window)?;
464 self.0.xcb_connection.flush()?;
465
466 anyhow::Ok(())
467 })
468 .context("unmapping and destroying X11 window")
469 .log_err();
470
471 if destroy_x_window.is_some() {
472 // Mark window as destroyed so that we can filter out when X11 events
473 // for it still come in.
474 state.destroyed = true;
475
476 let this_ptr = self.0.clone();
477 let client_ptr = state.client.clone();
478 state
479 .executor
480 .spawn(async move {
481 this_ptr.close();
482 client_ptr.drop_window(this_ptr.x_window);
483 })
484 .detach();
485 }
486
487 drop(state);
488 }
489}
490
491enum WmHintPropertyState {
492 // Remove = 0,
493 // Add = 1,
494 Toggle = 2,
495}
496
497impl X11Window {
498 #[allow(clippy::too_many_arguments)]
499 pub fn new(
500 handle: AnyWindowHandle,
501 client: X11ClientStatePtr,
502 executor: ForegroundExecutor,
503 params: WindowParams,
504 xcb_connection: &Rc<XCBConnection>,
505 x_main_screen_index: usize,
506 x_window: xproto::Window,
507 atoms: &XcbAtoms,
508 scale_factor: f32,
509 appearance: WindowAppearance,
510 ) -> anyhow::Result<Self> {
511 Ok(Self(X11WindowStatePtr {
512 state: Rc::new(RefCell::new(X11WindowState::new(
513 handle,
514 client,
515 executor,
516 params,
517 xcb_connection,
518 x_main_screen_index,
519 x_window,
520 atoms,
521 scale_factor,
522 appearance,
523 )?)),
524 callbacks: Rc::new(RefCell::new(Callbacks::default())),
525 xcb_connection: xcb_connection.clone(),
526 x_window,
527 }))
528 }
529
530 fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
531 let state = self.0.state.borrow();
532 let message = ClientMessageEvent::new(
533 32,
534 self.0.x_window,
535 state.atoms._NET_WM_STATE,
536 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
537 );
538 self.0
539 .xcb_connection
540 .send_event(
541 false,
542 state.x_root_window,
543 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
544 message,
545 )
546 .unwrap();
547 }
548
549 fn get_wm_hints(&self) -> Vec<u32> {
550 let reply = self
551 .0
552 .xcb_connection
553 .get_property(
554 false,
555 self.0.x_window,
556 self.0.state.borrow().atoms._NET_WM_STATE,
557 xproto::AtomEnum::ATOM,
558 0,
559 u32::MAX,
560 )
561 .unwrap()
562 .reply()
563 .unwrap();
564 // Reply is in u8 but atoms are represented as u32
565 reply
566 .value
567 .chunks_exact(4)
568 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
569 .collect()
570 }
571
572 fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
573 let state = self.0.state.borrow();
574 self.0
575 .xcb_connection
576 .translate_coordinates(
577 self.0.x_window,
578 state.x_root_window,
579 (position.x.0 * state.scale_factor) as i16,
580 (position.y.0 * state.scale_factor) as i16,
581 )
582 .unwrap()
583 .reply()
584 .unwrap()
585 }
586}
587
588impl X11WindowStatePtr {
589 pub fn should_close(&self) -> bool {
590 let mut cb = self.callbacks.borrow_mut();
591 if let Some(mut should_close) = cb.should_close.take() {
592 let result = (should_close)();
593 cb.should_close = Some(should_close);
594 result
595 } else {
596 true
597 }
598 }
599
600 pub fn close(&self) {
601 let mut callbacks = self.callbacks.borrow_mut();
602 if let Some(fun) = callbacks.close.take() {
603 fun()
604 }
605 }
606
607 pub fn refresh(&self) {
608 let mut cb = self.callbacks.borrow_mut();
609 if let Some(ref mut fun) = cb.request_frame {
610 fun();
611
612 self.state
613 .borrow_mut()
614 .last_render_at
615 .replace(Instant::now());
616 }
617 }
618
619 pub fn handle_input(&self, input: PlatformInput) {
620 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
621 if !fun(input.clone()).propagate {
622 return;
623 }
624 }
625 if let PlatformInput::KeyDown(event) = input {
626 let mut state = self.state.borrow_mut();
627 if let Some(mut input_handler) = state.input_handler.take() {
628 if let Some(ime_key) = &event.keystroke.ime_key {
629 drop(state);
630 input_handler.replace_text_in_range(None, ime_key);
631 state = self.state.borrow_mut();
632 }
633 state.input_handler = Some(input_handler);
634 }
635 }
636 }
637
638 pub fn handle_ime_commit(&self, text: String) {
639 let mut state = self.state.borrow_mut();
640 if let Some(mut input_handler) = state.input_handler.take() {
641 drop(state);
642 input_handler.replace_text_in_range(None, &text);
643 let mut state = self.state.borrow_mut();
644 state.input_handler = Some(input_handler);
645 }
646 }
647
648 pub fn handle_ime_preedit(&self, text: String) {
649 let mut state = self.state.borrow_mut();
650 if let Some(mut input_handler) = state.input_handler.take() {
651 drop(state);
652 input_handler.replace_and_mark_text_in_range(None, &text, None);
653 let mut state = self.state.borrow_mut();
654 state.input_handler = Some(input_handler);
655 }
656 }
657
658 pub fn handle_ime_unmark(&self) {
659 let mut state = self.state.borrow_mut();
660 if let Some(mut input_handler) = state.input_handler.take() {
661 drop(state);
662 input_handler.unmark_text();
663 let mut state = self.state.borrow_mut();
664 state.input_handler = Some(input_handler);
665 }
666 }
667
668 pub fn handle_ime_delete(&self) {
669 let mut state = self.state.borrow_mut();
670 if let Some(mut input_handler) = state.input_handler.take() {
671 drop(state);
672 if let Some(marked) = input_handler.marked_text_range() {
673 input_handler.replace_text_in_range(Some(marked), "");
674 }
675 let mut state = self.state.borrow_mut();
676 state.input_handler = Some(input_handler);
677 }
678 }
679
680 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
681 let mut state = self.state.borrow_mut();
682 let mut bounds: Option<Bounds<Pixels>> = None;
683 if let Some(mut input_handler) = state.input_handler.take() {
684 drop(state);
685 if let Some(range) = input_handler.selected_text_range() {
686 bounds = input_handler.bounds_for_range(range);
687 }
688 let mut state = self.state.borrow_mut();
689 state.input_handler = Some(input_handler);
690 };
691 bounds
692 }
693
694 pub fn configure(&self, bounds: Bounds<i32>) {
695 let mut resize_args = None;
696 let is_resize;
697 {
698 let mut state = self.state.borrow_mut();
699 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
700
701 is_resize = bounds.size.width != state.bounds.size.width
702 || bounds.size.height != state.bounds.size.height;
703
704 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
705 // because it contains wrong values.
706 if is_resize {
707 state.bounds.size = bounds.size;
708 } else {
709 state.bounds = bounds;
710 }
711
712 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
713 if true {
714 state.renderer.update_drawable_size(size(
715 DevicePixels(gpu_size.width as i32),
716 DevicePixels(gpu_size.height as i32),
717 ));
718 resize_args = Some((state.content_size(), state.scale_factor));
719 }
720 }
721
722 let mut callbacks = self.callbacks.borrow_mut();
723 if let Some((content_size, scale_factor)) = resize_args {
724 if let Some(ref mut fun) = callbacks.resize {
725 fun(content_size, scale_factor)
726 }
727 }
728 if !is_resize {
729 if let Some(ref mut fun) = callbacks.moved {
730 fun()
731 }
732 }
733 }
734
735 pub fn set_focused(&self, focus: bool) {
736 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
737 fun(focus);
738 }
739 }
740
741 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
742 self.state.borrow_mut().appearance = appearance;
743
744 let mut callbacks = self.callbacks.borrow_mut();
745 if let Some(ref mut fun) = callbacks.appearance_changed {
746 (fun)()
747 }
748 }
749}
750
751impl PlatformWindow for X11Window {
752 fn bounds(&self) -> Bounds<Pixels> {
753 self.0.state.borrow().bounds
754 }
755
756 fn is_maximized(&self) -> bool {
757 let state = self.0.state.borrow();
758 let wm_hints = self.get_wm_hints();
759 // A maximized window that gets minimized will still retain its maximized state.
760 !wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
761 && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
762 && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
763 }
764
765 fn window_bounds(&self) -> WindowBounds {
766 let state = self.0.state.borrow();
767 if self.is_maximized() {
768 WindowBounds::Maximized(state.bounds)
769 } else {
770 WindowBounds::Windowed(state.bounds)
771 }
772 }
773
774 fn content_size(&self) -> Size<Pixels> {
775 // We divide by the scale factor here because this value is queried to determine how much to draw,
776 // but it will be multiplied later by the scale to adjust for scaling.
777 let state = self.0.state.borrow();
778 state
779 .content_size()
780 .map(|size| size.div(state.scale_factor))
781 }
782
783 fn scale_factor(&self) -> f32 {
784 self.0.state.borrow().scale_factor
785 }
786
787 fn appearance(&self) -> WindowAppearance {
788 self.0.state.borrow().appearance
789 }
790
791 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
792 Some(self.0.state.borrow().display.clone())
793 }
794
795 fn mouse_position(&self) -> Point<Pixels> {
796 let reply = self
797 .0
798 .xcb_connection
799 .query_pointer(self.0.x_window)
800 .unwrap()
801 .reply()
802 .unwrap();
803 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
804 }
805
806 fn modifiers(&self) -> Modifiers {
807 self.0
808 .state
809 .borrow()
810 .client
811 .0
812 .upgrade()
813 .map(|ref_cell| ref_cell.borrow().modifiers)
814 .unwrap_or_default()
815 }
816
817 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
818 self.0.state.borrow_mut().input_handler = Some(input_handler);
819 }
820
821 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
822 self.0.state.borrow_mut().input_handler.take()
823 }
824
825 fn prompt(
826 &self,
827 _level: PromptLevel,
828 _msg: &str,
829 _detail: Option<&str>,
830 _answers: &[&str],
831 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
832 None
833 }
834
835 fn activate(&self) {
836 let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
837 self.0
838 .xcb_connection
839 .configure_window(self.0.x_window, &win_aux)
840 .log_err();
841 self.0
842 .xcb_connection
843 .set_input_focus(
844 xproto::InputFocus::POINTER_ROOT,
845 self.0.x_window,
846 xproto::Time::CURRENT_TIME,
847 )
848 .log_err();
849 }
850
851 fn is_active(&self) -> bool {
852 let state = self.0.state.borrow();
853 self.get_wm_hints()
854 .contains(&state.atoms._NET_WM_STATE_FOCUSED)
855 }
856
857 fn set_title(&mut self, title: &str) {
858 self.0
859 .xcb_connection
860 .change_property8(
861 xproto::PropMode::REPLACE,
862 self.0.x_window,
863 xproto::AtomEnum::WM_NAME,
864 xproto::AtomEnum::STRING,
865 title.as_bytes(),
866 )
867 .unwrap();
868
869 self.0
870 .xcb_connection
871 .change_property8(
872 xproto::PropMode::REPLACE,
873 self.0.x_window,
874 self.0.state.borrow().atoms._NET_WM_NAME,
875 self.0.state.borrow().atoms.UTF8_STRING,
876 title.as_bytes(),
877 )
878 .unwrap();
879 }
880
881 fn set_app_id(&mut self, app_id: &str) {
882 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
883 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
884 data.push(b'\0');
885 data.extend(app_id.bytes()); // class
886
887 self.0
888 .xcb_connection
889 .change_property8(
890 xproto::PropMode::REPLACE,
891 self.0.x_window,
892 xproto::AtomEnum::WM_CLASS,
893 xproto::AtomEnum::STRING,
894 &data,
895 )
896 .unwrap();
897 }
898
899 fn set_edited(&mut self, _edited: bool) {
900 log::info!("ignoring macOS specific set_edited");
901 }
902
903 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
904 let mut inner = self.0.state.borrow_mut();
905 let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
906 inner.renderer.update_transparency(transparent);
907 }
908
909 fn show_character_palette(&self) {
910 log::info!("ignoring macOS specific show_character_palette");
911 }
912
913 fn minimize(&self) {
914 let state = self.0.state.borrow();
915 const WINDOW_ICONIC_STATE: u32 = 3;
916 let message = ClientMessageEvent::new(
917 32,
918 self.0.x_window,
919 state.atoms.WM_CHANGE_STATE,
920 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
921 );
922 self.0
923 .xcb_connection
924 .send_event(
925 false,
926 state.x_root_window,
927 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
928 message,
929 )
930 .unwrap();
931 }
932
933 fn zoom(&self) {
934 let state = self.0.state.borrow();
935 self.set_wm_hints(
936 WmHintPropertyState::Toggle,
937 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
938 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
939 );
940 }
941
942 fn toggle_fullscreen(&self) {
943 let state = self.0.state.borrow();
944 self.set_wm_hints(
945 WmHintPropertyState::Toggle,
946 state.atoms._NET_WM_STATE_FULLSCREEN,
947 xproto::AtomEnum::NONE.into(),
948 );
949 }
950
951 fn is_fullscreen(&self) -> bool {
952 let state = self.0.state.borrow();
953 self.get_wm_hints()
954 .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
955 }
956
957 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
958 self.0.callbacks.borrow_mut().request_frame = Some(callback);
959 }
960
961 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
962 self.0.callbacks.borrow_mut().input = Some(callback);
963 }
964
965 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
966 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
967 }
968
969 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
970 self.0.callbacks.borrow_mut().resize = Some(callback);
971 }
972
973 fn on_moved(&self, callback: Box<dyn FnMut()>) {
974 self.0.callbacks.borrow_mut().moved = Some(callback);
975 }
976
977 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
978 self.0.callbacks.borrow_mut().should_close = Some(callback);
979 }
980
981 fn on_close(&self, callback: Box<dyn FnOnce()>) {
982 self.0.callbacks.borrow_mut().close = Some(callback);
983 }
984
985 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
986 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
987 }
988
989 fn draw(&self, scene: &Scene) {
990 let mut inner = self.0.state.borrow_mut();
991 inner.renderer.draw(scene);
992 }
993
994 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
995 let inner = self.0.state.borrow();
996 inner.renderer.sprite_atlas().clone()
997 }
998
999 fn show_window_menu(&self, position: Point<Pixels>) {
1000 let state = self.0.state.borrow();
1001 let coords = self.get_root_position(position);
1002 let message = ClientMessageEvent::new(
1003 32,
1004 self.0.x_window,
1005 state.atoms._GTK_SHOW_WINDOW_MENU,
1006 [
1007 XINPUT_MASTER_DEVICE as u32,
1008 coords.dst_x as u32,
1009 coords.dst_y as u32,
1010 0,
1011 0,
1012 ],
1013 );
1014 self.0
1015 .xcb_connection
1016 .send_event(
1017 false,
1018 state.x_root_window,
1019 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1020 message,
1021 )
1022 .unwrap();
1023 }
1024
1025 fn start_system_move(&self) {
1026 let state = self.0.state.borrow();
1027 let pointer = self
1028 .0
1029 .xcb_connection
1030 .query_pointer(self.0.x_window)
1031 .unwrap()
1032 .reply()
1033 .unwrap();
1034 const MOVERESIZE_MOVE: u32 = 8;
1035 let message = ClientMessageEvent::new(
1036 32,
1037 self.0.x_window,
1038 state.atoms._NET_WM_MOVERESIZE,
1039 [
1040 pointer.root_x as u32,
1041 pointer.root_y as u32,
1042 MOVERESIZE_MOVE,
1043 1, // Left mouse button
1044 1,
1045 ],
1046 );
1047 self.0
1048 .xcb_connection
1049 .send_event(
1050 false,
1051 state.x_root_window,
1052 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1053 message,
1054 )
1055 .unwrap();
1056 }
1057
1058 fn should_render_window_controls(&self) -> bool {
1059 false
1060 }
1061}
1062
1063// Adatpted from:
1064// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
1065pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
1066 let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
1067 let micros = 1_000_000_000 / millihertz;
1068 log::info!("Refreshing at {} micros", micros);
1069 Duration::from_micros(micros)
1070}