window.rs

  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}