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