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 xcb_connection
271 .create_window(
272 visual.depth,
273 x_window,
274 visual_set.root,
275 (params.bounds.origin.x.0 * scale_factor) as i16,
276 (params.bounds.origin.y.0 * scale_factor) as i16,
277 (params.bounds.size.width.0 * scale_factor) as u16,
278 (params.bounds.size.height.0 * scale_factor) as u16,
279 0,
280 xproto::WindowClass::INPUT_OUTPUT,
281 visual.id,
282 &win_aux,
283 )
284 .unwrap()
285 .check()?;
286
287 if let Some(titlebar) = params.titlebar {
288 if let Some(title) = titlebar.title {
289 xcb_connection
290 .change_property8(
291 xproto::PropMode::REPLACE,
292 x_window,
293 xproto::AtomEnum::WM_NAME,
294 xproto::AtomEnum::STRING,
295 title.as_bytes(),
296 )
297 .unwrap();
298 }
299 }
300 if params.kind == WindowKind::PopUp {
301 xcb_connection
302 .change_property32(
303 xproto::PropMode::REPLACE,
304 x_window,
305 atoms._NET_WM_WINDOW_TYPE,
306 xproto::AtomEnum::ATOM,
307 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
308 )
309 .unwrap();
310 }
311
312 xcb_connection
313 .change_property32(
314 xproto::PropMode::REPLACE,
315 x_window,
316 atoms.WM_PROTOCOLS,
317 xproto::AtomEnum::ATOM,
318 &[atoms.WM_DELETE_WINDOW],
319 )
320 .unwrap();
321
322 xcb_connection
323 .xinput_xi_select_events(
324 x_window,
325 &[xinput::EventMask {
326 deviceid: XINPUT_MASTER_DEVICE,
327 mask: vec![
328 xinput::XIEventMask::MOTION
329 | xinput::XIEventMask::BUTTON_PRESS
330 | xinput::XIEventMask::BUTTON_RELEASE
331 | xinput::XIEventMask::LEAVE,
332 ],
333 }],
334 )
335 .unwrap();
336
337 xcb_connection.flush().unwrap();
338
339 let raw = RawWindow {
340 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
341 xcb_connection,
342 ) as *mut _,
343 screen_id: x_screen_index,
344 window_id: x_window,
345 visual_id: visual.id,
346 };
347 let gpu = Arc::new(
348 unsafe {
349 gpu::Context::init_windowed(
350 &raw,
351 gpu::ContextDesc {
352 validation: false,
353 capture: false,
354 overlay: false,
355 },
356 )
357 }
358 .map_err(|e| anyhow::anyhow!("{:?}", e))?,
359 );
360
361 let config = BladeSurfaceConfig {
362 // Note: this has to be done after the GPU init, or otherwise
363 // the sizes are immediately invalidated.
364 size: query_render_extent(xcb_connection, x_window),
365 transparent: params.window_background != WindowBackgroundAppearance::Opaque,
366 };
367 xcb_connection.map_window(x_window).unwrap();
368
369 Ok(Self {
370 client,
371 executor,
372 display: Rc::new(
373 X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
374 ),
375 _raw: raw,
376 x_root_window: visual_set.root,
377 bounds: params.bounds,
378 scale_factor,
379 renderer: BladeRenderer::new(gpu, config),
380 atoms: *atoms,
381 input_handler: None,
382 appearance,
383 handle,
384 destroyed: false,
385 })
386 }
387
388 fn content_size(&self) -> Size<Pixels> {
389 let size = self.renderer.viewport_size();
390 Size {
391 width: size.width.into(),
392 height: size.height.into(),
393 }
394 }
395}
396
397pub(crate) struct X11Window(pub X11WindowStatePtr);
398
399impl Drop for X11Window {
400 fn drop(&mut self) {
401 let mut state = self.0.state.borrow_mut();
402 state.renderer.destroy();
403
404 self.0.xcb_connection.unmap_window(self.0.x_window).unwrap();
405 self.0
406 .xcb_connection
407 .destroy_window(self.0.x_window)
408 .unwrap();
409 self.0.xcb_connection.flush().unwrap();
410
411 // Mark window as destroyed so that we can filter out when X11 events
412 // for it still come in.
413 state.destroyed = true;
414
415 let this_ptr = self.0.clone();
416 let client_ptr = state.client.clone();
417 state
418 .executor
419 .spawn(async move {
420 this_ptr.close();
421 client_ptr.drop_window(this_ptr.x_window);
422 })
423 .detach();
424 drop(state);
425 }
426}
427
428enum WmHintPropertyState {
429 // Remove = 0,
430 // Add = 1,
431 Toggle = 2,
432}
433
434impl X11Window {
435 #[allow(clippy::too_many_arguments)]
436 pub fn new(
437 handle: AnyWindowHandle,
438 client: X11ClientStatePtr,
439 executor: ForegroundExecutor,
440 params: WindowParams,
441 xcb_connection: &Rc<XCBConnection>,
442 x_main_screen_index: usize,
443 x_window: xproto::Window,
444 atoms: &XcbAtoms,
445 scale_factor: f32,
446 appearance: WindowAppearance,
447 ) -> anyhow::Result<Self> {
448 Ok(Self(X11WindowStatePtr {
449 state: Rc::new(RefCell::new(X11WindowState::new(
450 handle,
451 client,
452 executor,
453 params,
454 xcb_connection,
455 x_main_screen_index,
456 x_window,
457 atoms,
458 scale_factor,
459 appearance,
460 )?)),
461 callbacks: Rc::new(RefCell::new(Callbacks::default())),
462 xcb_connection: xcb_connection.clone(),
463 x_window,
464 }))
465 }
466
467 fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
468 let state = self.0.state.borrow();
469 let message = ClientMessageEvent::new(
470 32,
471 self.0.x_window,
472 state.atoms._NET_WM_STATE,
473 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
474 );
475 self.0
476 .xcb_connection
477 .send_event(
478 false,
479 state.x_root_window,
480 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
481 message,
482 )
483 .unwrap();
484 }
485
486 fn get_wm_hints(&self) -> Vec<u32> {
487 let reply = self
488 .0
489 .xcb_connection
490 .get_property(
491 false,
492 self.0.x_window,
493 self.0.state.borrow().atoms._NET_WM_STATE,
494 xproto::AtomEnum::ATOM,
495 0,
496 u32::MAX,
497 )
498 .unwrap()
499 .reply()
500 .unwrap();
501 // Reply is in u8 but atoms are represented as u32
502 reply
503 .value
504 .chunks_exact(4)
505 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
506 .collect()
507 }
508
509 fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
510 let state = self.0.state.borrow();
511 self.0
512 .xcb_connection
513 .translate_coordinates(
514 self.0.x_window,
515 state.x_root_window,
516 (position.x.0 * state.scale_factor) as i16,
517 (position.y.0 * state.scale_factor) as i16,
518 )
519 .unwrap()
520 .reply()
521 .unwrap()
522 }
523}
524
525impl X11WindowStatePtr {
526 pub fn should_close(&self) -> bool {
527 let mut cb = self.callbacks.borrow_mut();
528 if let Some(mut should_close) = cb.should_close.take() {
529 let result = (should_close)();
530 cb.should_close = Some(should_close);
531 result
532 } else {
533 true
534 }
535 }
536
537 pub fn close(&self) {
538 let mut callbacks = self.callbacks.borrow_mut();
539 if let Some(fun) = callbacks.close.take() {
540 fun()
541 }
542 }
543
544 pub fn refresh(&self) {
545 let mut cb = self.callbacks.borrow_mut();
546 if let Some(ref mut fun) = cb.request_frame {
547 fun();
548 }
549 }
550
551 pub fn handle_input(&self, input: PlatformInput) {
552 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
553 if !fun(input.clone()).propagate {
554 return;
555 }
556 }
557 if let PlatformInput::KeyDown(event) = input {
558 let mut state = self.state.borrow_mut();
559 if let Some(mut input_handler) = state.input_handler.take() {
560 if let Some(ime_key) = &event.keystroke.ime_key {
561 drop(state);
562 input_handler.replace_text_in_range(None, ime_key);
563 state = self.state.borrow_mut();
564 }
565 state.input_handler = Some(input_handler);
566 }
567 }
568 }
569
570 pub fn handle_ime_commit(&self, text: String) {
571 let mut state = self.state.borrow_mut();
572 if let Some(mut input_handler) = state.input_handler.take() {
573 drop(state);
574 input_handler.replace_text_in_range(None, &text);
575 let mut state = self.state.borrow_mut();
576 state.input_handler = Some(input_handler);
577 }
578 }
579
580 pub fn handle_ime_preedit(&self, text: String) {
581 let mut state = self.state.borrow_mut();
582 if let Some(mut input_handler) = state.input_handler.take() {
583 drop(state);
584 input_handler.replace_and_mark_text_in_range(None, &text, None);
585 let mut state = self.state.borrow_mut();
586 state.input_handler = Some(input_handler);
587 }
588 }
589
590 pub fn handle_ime_unmark(&self) {
591 let mut state = self.state.borrow_mut();
592 if let Some(mut input_handler) = state.input_handler.take() {
593 drop(state);
594 input_handler.unmark_text();
595 let mut state = self.state.borrow_mut();
596 state.input_handler = Some(input_handler);
597 }
598 }
599
600 pub fn handle_ime_delete(&self) {
601 let mut state = self.state.borrow_mut();
602 if let Some(mut input_handler) = state.input_handler.take() {
603 drop(state);
604 if let Some(marked) = input_handler.marked_text_range() {
605 input_handler.replace_text_in_range(Some(marked), "");
606 }
607 let mut state = self.state.borrow_mut();
608 state.input_handler = Some(input_handler);
609 }
610 }
611
612 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
613 let mut state = self.state.borrow_mut();
614 let mut bounds: Option<Bounds<Pixels>> = None;
615 if let Some(mut input_handler) = state.input_handler.take() {
616 drop(state);
617 if let Some(range) = input_handler.selected_text_range() {
618 bounds = input_handler.bounds_for_range(range);
619 }
620 let mut state = self.state.borrow_mut();
621 state.input_handler = Some(input_handler);
622 };
623 bounds
624 }
625
626 pub fn configure(&self, bounds: Bounds<i32>) {
627 let mut resize_args = None;
628 let is_resize;
629 {
630 let mut state = self.state.borrow_mut();
631 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
632
633 is_resize = bounds.size.width != state.bounds.size.width
634 || bounds.size.height != state.bounds.size.height;
635
636 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
637 // because it contains wrong values.
638 if is_resize {
639 state.bounds.size = bounds.size;
640 } else {
641 state.bounds = bounds;
642 }
643
644 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
645 if state.renderer.viewport_size() != gpu_size {
646 state.renderer.update_drawable_size(size(
647 DevicePixels(gpu_size.width as i32),
648 DevicePixels(gpu_size.height as i32),
649 ));
650 resize_args = Some((state.content_size(), state.scale_factor));
651 }
652 }
653
654 let mut callbacks = self.callbacks.borrow_mut();
655 if let Some((content_size, scale_factor)) = resize_args {
656 if let Some(ref mut fun) = callbacks.resize {
657 fun(content_size, scale_factor)
658 }
659 }
660 if !is_resize {
661 if let Some(ref mut fun) = callbacks.moved {
662 fun()
663 }
664 }
665 }
666
667 pub fn set_focused(&self, focus: bool) {
668 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
669 fun(focus);
670 }
671 }
672
673 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
674 self.state.borrow_mut().appearance = appearance;
675
676 let mut callbacks = self.callbacks.borrow_mut();
677 if let Some(ref mut fun) = callbacks.appearance_changed {
678 (fun)()
679 }
680 }
681}
682
683impl PlatformWindow for X11Window {
684 fn bounds(&self) -> Bounds<Pixels> {
685 self.0.state.borrow().bounds
686 }
687
688 fn is_maximized(&self) -> bool {
689 let state = self.0.state.borrow();
690 let wm_hints = self.get_wm_hints();
691 // A maximized window that gets minimized will still retain its maximized state.
692 !wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
693 && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
694 && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
695 }
696
697 fn window_bounds(&self) -> WindowBounds {
698 let state = self.0.state.borrow();
699 if self.is_maximized() {
700 WindowBounds::Maximized(state.bounds)
701 } else {
702 WindowBounds::Windowed(state.bounds)
703 }
704 }
705
706 fn content_size(&self) -> Size<Pixels> {
707 // We divide by the scale factor here because this value is queried to determine how much to draw,
708 // but it will be multiplied later by the scale to adjust for scaling.
709 let state = self.0.state.borrow();
710 state
711 .content_size()
712 .map(|size| size.div(state.scale_factor))
713 }
714
715 fn scale_factor(&self) -> f32 {
716 self.0.state.borrow().scale_factor
717 }
718
719 fn appearance(&self) -> WindowAppearance {
720 self.0.state.borrow().appearance
721 }
722
723 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
724 Some(self.0.state.borrow().display.clone())
725 }
726
727 fn mouse_position(&self) -> Point<Pixels> {
728 let reply = self
729 .0
730 .xcb_connection
731 .query_pointer(self.0.x_window)
732 .unwrap()
733 .reply()
734 .unwrap();
735 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
736 }
737
738 fn modifiers(&self) -> Modifiers {
739 self.0
740 .state
741 .borrow()
742 .client
743 .0
744 .upgrade()
745 .map(|ref_cell| ref_cell.borrow().modifiers)
746 .unwrap_or_default()
747 }
748
749 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
750 self.0.state.borrow_mut().input_handler = Some(input_handler);
751 }
752
753 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
754 self.0.state.borrow_mut().input_handler.take()
755 }
756
757 fn prompt(
758 &self,
759 _level: PromptLevel,
760 _msg: &str,
761 _detail: Option<&str>,
762 _answers: &[&str],
763 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
764 None
765 }
766
767 fn activate(&self) {
768 let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
769 self.0
770 .xcb_connection
771 .configure_window(self.0.x_window, &win_aux)
772 .log_err();
773 }
774
775 fn is_active(&self) -> bool {
776 let state = self.0.state.borrow();
777 self.get_wm_hints()
778 .contains(&state.atoms._NET_WM_STATE_FOCUSED)
779 }
780
781 fn set_title(&mut self, title: &str) {
782 self.0
783 .xcb_connection
784 .change_property8(
785 xproto::PropMode::REPLACE,
786 self.0.x_window,
787 xproto::AtomEnum::WM_NAME,
788 xproto::AtomEnum::STRING,
789 title.as_bytes(),
790 )
791 .unwrap();
792
793 self.0
794 .xcb_connection
795 .change_property8(
796 xproto::PropMode::REPLACE,
797 self.0.x_window,
798 self.0.state.borrow().atoms._NET_WM_NAME,
799 self.0.state.borrow().atoms.UTF8_STRING,
800 title.as_bytes(),
801 )
802 .unwrap();
803 }
804
805 fn set_app_id(&mut self, app_id: &str) {
806 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
807 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
808 data.push(b'\0');
809 data.extend(app_id.bytes()); // class
810
811 self.0
812 .xcb_connection
813 .change_property8(
814 xproto::PropMode::REPLACE,
815 self.0.x_window,
816 xproto::AtomEnum::WM_CLASS,
817 xproto::AtomEnum::STRING,
818 &data,
819 )
820 .unwrap();
821 }
822
823 fn set_edited(&mut self, _edited: bool) {
824 log::info!("ignoring macOS specific set_edited");
825 }
826
827 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
828 let mut inner = self.0.state.borrow_mut();
829 let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
830 inner.renderer.update_transparency(transparent);
831 }
832
833 fn show_character_palette(&self) {
834 log::info!("ignoring macOS specific show_character_palette");
835 }
836
837 fn minimize(&self) {
838 let state = self.0.state.borrow();
839 const WINDOW_ICONIC_STATE: u32 = 3;
840 let message = ClientMessageEvent::new(
841 32,
842 self.0.x_window,
843 state.atoms.WM_CHANGE_STATE,
844 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
845 );
846 self.0
847 .xcb_connection
848 .send_event(
849 false,
850 state.x_root_window,
851 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
852 message,
853 )
854 .unwrap();
855 }
856
857 fn zoom(&self) {
858 let state = self.0.state.borrow();
859 self.set_wm_hints(
860 WmHintPropertyState::Toggle,
861 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
862 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
863 );
864 }
865
866 fn toggle_fullscreen(&self) {
867 let state = self.0.state.borrow();
868 self.set_wm_hints(
869 WmHintPropertyState::Toggle,
870 state.atoms._NET_WM_STATE_FULLSCREEN,
871 xproto::AtomEnum::NONE.into(),
872 );
873 }
874
875 fn is_fullscreen(&self) -> bool {
876 let state = self.0.state.borrow();
877 self.get_wm_hints()
878 .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
879 }
880
881 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
882 self.0.callbacks.borrow_mut().request_frame = Some(callback);
883 }
884
885 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
886 self.0.callbacks.borrow_mut().input = Some(callback);
887 }
888
889 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
890 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
891 }
892
893 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
894 self.0.callbacks.borrow_mut().resize = Some(callback);
895 }
896
897 fn on_moved(&self, callback: Box<dyn FnMut()>) {
898 self.0.callbacks.borrow_mut().moved = Some(callback);
899 }
900
901 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
902 self.0.callbacks.borrow_mut().should_close = Some(callback);
903 }
904
905 fn on_close(&self, callback: Box<dyn FnOnce()>) {
906 self.0.callbacks.borrow_mut().close = Some(callback);
907 }
908
909 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
910 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
911 }
912
913 fn draw(&self, scene: &Scene) {
914 let mut inner = self.0.state.borrow_mut();
915 inner.renderer.draw(scene);
916 }
917
918 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
919 let inner = self.0.state.borrow();
920 inner.renderer.sprite_atlas().clone()
921 }
922
923 fn show_window_menu(&self, position: Point<Pixels>) {
924 let state = self.0.state.borrow();
925 let coords = self.get_root_position(position);
926 let message = ClientMessageEvent::new(
927 32,
928 self.0.x_window,
929 state.atoms._GTK_SHOW_WINDOW_MENU,
930 [
931 XINPUT_MASTER_DEVICE as u32,
932 coords.dst_x as u32,
933 coords.dst_y as u32,
934 0,
935 0,
936 ],
937 );
938 self.0
939 .xcb_connection
940 .send_event(
941 false,
942 state.x_root_window,
943 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
944 message,
945 )
946 .unwrap();
947 }
948
949 fn start_system_move(&self) {
950 let state = self.0.state.borrow();
951 let pointer = self
952 .0
953 .xcb_connection
954 .query_pointer(self.0.x_window)
955 .unwrap()
956 .reply()
957 .unwrap();
958 const MOVERESIZE_MOVE: u32 = 8;
959 let message = ClientMessageEvent::new(
960 32,
961 self.0.x_window,
962 state.atoms._NET_WM_MOVERESIZE,
963 [
964 pointer.root_x as u32,
965 pointer.root_y as u32,
966 MOVERESIZE_MOVE,
967 1, // Left mouse button
968 1,
969 ],
970 );
971 self.0
972 .xcb_connection
973 .send_event(
974 false,
975 state.x_root_window,
976 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
977 message,
978 )
979 .unwrap();
980 }
981
982 fn should_render_window_controls(&self) -> bool {
983 false
984 }
985}