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