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