window.rs

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