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