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