window.rs

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