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