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        self.0
774            .xcb_connection
775            .set_input_focus(
776                xproto::InputFocus::POINTER_ROOT,
777                self.0.x_window,
778                xproto::Time::CURRENT_TIME,
779            )
780            .log_err();
781    }
782
783    fn is_active(&self) -> bool {
784        let state = self.0.state.borrow();
785        self.get_wm_hints()
786            .contains(&state.atoms._NET_WM_STATE_FOCUSED)
787    }
788
789    fn set_title(&mut self, title: &str) {
790        self.0
791            .xcb_connection
792            .change_property8(
793                xproto::PropMode::REPLACE,
794                self.0.x_window,
795                xproto::AtomEnum::WM_NAME,
796                xproto::AtomEnum::STRING,
797                title.as_bytes(),
798            )
799            .unwrap();
800
801        self.0
802            .xcb_connection
803            .change_property8(
804                xproto::PropMode::REPLACE,
805                self.0.x_window,
806                self.0.state.borrow().atoms._NET_WM_NAME,
807                self.0.state.borrow().atoms.UTF8_STRING,
808                title.as_bytes(),
809            )
810            .unwrap();
811    }
812
813    fn set_app_id(&mut self, app_id: &str) {
814        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
815        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
816        data.push(b'\0');
817        data.extend(app_id.bytes()); // class
818
819        self.0
820            .xcb_connection
821            .change_property8(
822                xproto::PropMode::REPLACE,
823                self.0.x_window,
824                xproto::AtomEnum::WM_CLASS,
825                xproto::AtomEnum::STRING,
826                &data,
827            )
828            .unwrap();
829    }
830
831    fn set_edited(&mut self, _edited: bool) {
832        log::info!("ignoring macOS specific set_edited");
833    }
834
835    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
836        let mut inner = self.0.state.borrow_mut();
837        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
838        inner.renderer.update_transparency(transparent);
839    }
840
841    fn show_character_palette(&self) {
842        log::info!("ignoring macOS specific show_character_palette");
843    }
844
845    fn minimize(&self) {
846        let state = self.0.state.borrow();
847        const WINDOW_ICONIC_STATE: u32 = 3;
848        let message = ClientMessageEvent::new(
849            32,
850            self.0.x_window,
851            state.atoms.WM_CHANGE_STATE,
852            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
853        );
854        self.0
855            .xcb_connection
856            .send_event(
857                false,
858                state.x_root_window,
859                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
860                message,
861            )
862            .unwrap();
863    }
864
865    fn zoom(&self) {
866        let state = self.0.state.borrow();
867        self.set_wm_hints(
868            WmHintPropertyState::Toggle,
869            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
870            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
871        );
872    }
873
874    fn toggle_fullscreen(&self) {
875        let state = self.0.state.borrow();
876        self.set_wm_hints(
877            WmHintPropertyState::Toggle,
878            state.atoms._NET_WM_STATE_FULLSCREEN,
879            xproto::AtomEnum::NONE.into(),
880        );
881    }
882
883    fn is_fullscreen(&self) -> bool {
884        let state = self.0.state.borrow();
885        self.get_wm_hints()
886            .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
887    }
888
889    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
890        self.0.callbacks.borrow_mut().request_frame = Some(callback);
891    }
892
893    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
894        self.0.callbacks.borrow_mut().input = Some(callback);
895    }
896
897    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
898        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
899    }
900
901    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
902        self.0.callbacks.borrow_mut().resize = Some(callback);
903    }
904
905    fn on_moved(&self, callback: Box<dyn FnMut()>) {
906        self.0.callbacks.borrow_mut().moved = Some(callback);
907    }
908
909    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
910        self.0.callbacks.borrow_mut().should_close = Some(callback);
911    }
912
913    fn on_close(&self, callback: Box<dyn FnOnce()>) {
914        self.0.callbacks.borrow_mut().close = Some(callback);
915    }
916
917    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
918        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
919    }
920
921    fn draw(&self, scene: &Scene) {
922        let mut inner = self.0.state.borrow_mut();
923        inner.renderer.draw(scene);
924    }
925
926    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
927        let inner = self.0.state.borrow();
928        inner.renderer.sprite_atlas().clone()
929    }
930
931    fn show_window_menu(&self, position: Point<Pixels>) {
932        let state = self.0.state.borrow();
933        let coords = self.get_root_position(position);
934        let message = ClientMessageEvent::new(
935            32,
936            self.0.x_window,
937            state.atoms._GTK_SHOW_WINDOW_MENU,
938            [
939                XINPUT_MASTER_DEVICE as u32,
940                coords.dst_x as u32,
941                coords.dst_y as u32,
942                0,
943                0,
944            ],
945        );
946        self.0
947            .xcb_connection
948            .send_event(
949                false,
950                state.x_root_window,
951                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
952                message,
953            )
954            .unwrap();
955    }
956
957    fn start_system_move(&self) {
958        let state = self.0.state.borrow();
959        let pointer = self
960            .0
961            .xcb_connection
962            .query_pointer(self.0.x_window)
963            .unwrap()
964            .reply()
965            .unwrap();
966        const MOVERESIZE_MOVE: u32 = 8;
967        let message = ClientMessageEvent::new(
968            32,
969            self.0.x_window,
970            state.atoms._NET_WM_MOVERESIZE,
971            [
972                pointer.root_x as u32,
973                pointer.root_y as u32,
974                MOVERESIZE_MOVE,
975                1, // Left mouse button
976                1,
977            ],
978        );
979        self.0
980            .xcb_connection
981            .send_event(
982                false,
983                state.x_root_window,
984                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
985                message,
986            )
987            .unwrap();
988    }
989
990    fn should_render_window_controls(&self) -> bool {
991        false
992    }
993}