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