window.rs

  1// todo(linux): remove
  2#![allow(unused)]
  3
  4use crate::{
  5    platform::blade::{BladeRenderer, BladeSurfaceConfig},
  6    size, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels, Platform, PlatformAtlas,
  7    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
  8    Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions,
  9    WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
 10};
 11
 12use blade_graphics as gpu;
 13use parking_lot::Mutex;
 14use raw_window_handle as rwh;
 15use util::ResultExt;
 16use x11rb::{
 17    connection::{Connection as _, RequestConnection as _},
 18    protocol::{
 19        render::{self, ConnectionExt as _},
 20        xinput::{self, ConnectionExt as _},
 21        xproto::{self, ConnectionExt as _, CreateWindowAux},
 22    },
 23    resource_manager::Database,
 24    wrapper::ConnectionExt as _,
 25    xcb_ffi::XCBConnection,
 26};
 27
 28use std::{
 29    cell::{Ref, RefCell, RefMut},
 30    collections::HashMap,
 31    ffi::c_void,
 32    iter::Zip,
 33    mem,
 34    num::NonZeroU32,
 35    ops::Div,
 36    ptr::NonNull,
 37    rc::Rc,
 38    sync::{self, Arc},
 39};
 40
 41use super::X11Display;
 42
 43x11rb::atom_manager! {
 44    pub XcbAtoms: AtomsCookie {
 45        UTF8_STRING,
 46        WM_PROTOCOLS,
 47        WM_DELETE_WINDOW,
 48        _NET_WM_NAME,
 49        _NET_WM_STATE,
 50        _NET_WM_STATE_MAXIMIZED_VERT,
 51        _NET_WM_STATE_MAXIMIZED_HORZ,
 52    }
 53}
 54
 55fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
 56    let reply = xcb_connection
 57        .get_geometry(x_window)
 58        .unwrap()
 59        .reply()
 60        .unwrap();
 61    gpu::Extent {
 62        width: reply.width as u32,
 63        height: reply.height as u32,
 64        depth: 1,
 65    }
 66}
 67
 68#[derive(Debug)]
 69struct Visual {
 70    id: xproto::Visualid,
 71    colormap: u32,
 72    depth: u8,
 73}
 74
 75struct VisualSet {
 76    inherit: Visual,
 77    opaque: Option<Visual>,
 78    transparent: Option<Visual>,
 79    root: u32,
 80    black_pixel: u32,
 81}
 82
 83fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
 84    let screen = &xcb_connection.setup().roots[screen_index];
 85    let mut set = VisualSet {
 86        inherit: Visual {
 87            id: screen.root_visual,
 88            colormap: screen.default_colormap,
 89            depth: screen.root_depth,
 90        },
 91        opaque: None,
 92        transparent: None,
 93        root: screen.root,
 94        black_pixel: screen.black_pixel,
 95    };
 96
 97    for depth_info in screen.allowed_depths.iter() {
 98        for visual_type in depth_info.visuals.iter() {
 99            let visual = Visual {
100                id: visual_type.visual_id,
101                colormap: 0,
102                depth: depth_info.depth,
103            };
104            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
105                visual_type.visual_id,
106                visual_type.class,
107                depth_info.depth,
108                visual_type.bits_per_rgb_value,
109                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
110            );
111
112            if (
113                visual_type.red_mask,
114                visual_type.green_mask,
115                visual_type.blue_mask,
116            ) != (0xFF0000, 0xFF00, 0xFF)
117            {
118                continue;
119            }
120            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
121            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
122
123            if alpha_mask == 0 {
124                if set.opaque.is_none() {
125                    set.opaque = Some(visual);
126                }
127            } else {
128                if set.transparent.is_none() {
129                    set.transparent = Some(visual);
130                }
131            }
132        }
133    }
134
135    set
136}
137
138struct RawWindow {
139    connection: *mut c_void,
140    screen_id: usize,
141    window_id: u32,
142    visual_id: u32,
143}
144
145#[derive(Default)]
146pub struct Callbacks {
147    request_frame: Option<Box<dyn FnMut()>>,
148    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
149    active_status_change: Option<Box<dyn FnMut(bool)>>,
150    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
151    moved: Option<Box<dyn FnMut()>>,
152    should_close: Option<Box<dyn FnMut() -> bool>>,
153    close: Option<Box<dyn FnOnce()>>,
154    appearance_changed: Option<Box<dyn FnMut()>>,
155}
156
157pub(crate) struct X11WindowState {
158    client: X11ClientStatePtr,
159    executor: ForegroundExecutor,
160    atoms: XcbAtoms,
161    raw: RawWindow,
162    bounds: Bounds<i32>,
163    scale_factor: f32,
164    renderer: BladeRenderer,
165    display: Rc<dyn PlatformDisplay>,
166    input_handler: Option<PlatformInputHandler>,
167}
168
169#[derive(Clone)]
170pub(crate) struct X11WindowStatePtr {
171    pub(crate) state: Rc<RefCell<X11WindowState>>,
172    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
173    xcb_connection: Rc<XCBConnection>,
174    x_window: xproto::Window,
175}
176
177// todo(linux): Remove other RawWindowHandle implementation
178impl rwh::HasWindowHandle for RawWindow {
179    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
180        let non_zero = NonZeroU32::new(self.window_id).unwrap();
181        let mut handle = rwh::XcbWindowHandle::new(non_zero);
182        handle.visual_id = NonZeroU32::new(self.visual_id);
183        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
184    }
185}
186impl rwh::HasDisplayHandle for RawWindow {
187    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
188        let non_zero = NonNull::new(self.connection).unwrap();
189        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
190        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
191    }
192}
193
194impl rwh::HasWindowHandle for X11Window {
195    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
196        unimplemented!()
197    }
198}
199impl rwh::HasDisplayHandle for X11Window {
200    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
201        unimplemented!()
202    }
203}
204
205impl X11WindowState {
206    #[allow(clippy::too_many_arguments)]
207    pub fn new(
208        client: X11ClientStatePtr,
209        executor: ForegroundExecutor,
210        params: WindowParams,
211        xcb_connection: &Rc<XCBConnection>,
212        x_main_screen_index: usize,
213        x_window: xproto::Window,
214        atoms: &XcbAtoms,
215        scale_factor: f32,
216    ) -> Self {
217        let x_screen_index = params
218            .display_id
219            .map_or(x_main_screen_index, |did| did.0 as usize);
220
221        let visual_set = find_visuals(&xcb_connection, x_screen_index);
222        let visual_maybe = match params.window_background {
223            WindowBackgroundAppearance::Opaque => visual_set.opaque,
224            WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
225                visual_set.transparent
226            }
227        };
228        let visual = match visual_maybe {
229            Some(visual) => visual,
230            None => {
231                log::warn!(
232                    "Unable to find a matching visual for {:?}",
233                    params.window_background
234                );
235                visual_set.inherit
236            }
237        };
238        log::info!("Using {:?}", visual);
239
240        let colormap = if visual.colormap != 0 {
241            visual.colormap
242        } else {
243            let id = xcb_connection.generate_id().unwrap();
244            log::info!("Creating colormap {}", id);
245            xcb_connection
246                .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
247                .unwrap()
248                .check()
249                .unwrap();
250            id
251        };
252
253        let win_aux = xproto::CreateWindowAux::new()
254            .background_pixel(x11rb::NONE)
255            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
256            .border_pixel(visual_set.black_pixel)
257            .colormap(colormap)
258            .event_mask(
259                xproto::EventMask::EXPOSURE
260                    | xproto::EventMask::STRUCTURE_NOTIFY
261                    | xproto::EventMask::ENTER_WINDOW
262                    | xproto::EventMask::LEAVE_WINDOW
263                    | xproto::EventMask::FOCUS_CHANGE
264                    | xproto::EventMask::KEY_PRESS
265                    | xproto::EventMask::KEY_RELEASE,
266            );
267
268        xcb_connection
269            .create_window(
270                visual.depth,
271                x_window,
272                visual_set.root,
273                params.bounds.origin.x.0 as i16,
274                params.bounds.origin.y.0 as i16,
275                params.bounds.size.width.0 as u16,
276                params.bounds.size.height.0 as u16,
277                0,
278                xproto::WindowClass::INPUT_OUTPUT,
279                visual.id,
280                &win_aux,
281            )
282            .unwrap()
283            .check()
284            .unwrap();
285
286        if let Some(titlebar) = params.titlebar {
287            if let Some(title) = titlebar.title {
288                xcb_connection
289                    .change_property8(
290                        xproto::PropMode::REPLACE,
291                        x_window,
292                        xproto::AtomEnum::WM_NAME,
293                        xproto::AtomEnum::STRING,
294                        title.as_bytes(),
295                    )
296                    .unwrap();
297            }
298        }
299
300        xcb_connection
301            .change_property32(
302                xproto::PropMode::REPLACE,
303                x_window,
304                atoms.WM_PROTOCOLS,
305                xproto::AtomEnum::ATOM,
306                &[atoms.WM_DELETE_WINDOW],
307            )
308            .unwrap();
309
310        xcb_connection
311            .xinput_xi_select_events(
312                x_window,
313                &[xinput::EventMask {
314                    deviceid: 1,
315                    mask: vec![
316                        xinput::XIEventMask::MOTION
317                            | xinput::XIEventMask::BUTTON_PRESS
318                            | xinput::XIEventMask::BUTTON_RELEASE
319                            | xinput::XIEventMask::LEAVE,
320                    ],
321                }],
322            )
323            .unwrap();
324
325        xcb_connection.map_window(x_window).unwrap();
326        xcb_connection.flush().unwrap();
327
328        let raw = RawWindow {
329            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
330                xcb_connection,
331            ) as *mut _,
332            screen_id: x_screen_index,
333            window_id: x_window,
334            visual_id: visual.id,
335        };
336        let gpu = Arc::new(
337            unsafe {
338                gpu::Context::init_windowed(
339                    &raw,
340                    gpu::ContextDesc {
341                        validation: false,
342                        capture: false,
343                        overlay: false,
344                    },
345                )
346            }
347            .unwrap(),
348        );
349
350        let config = BladeSurfaceConfig {
351            // Note: this has to be done after the GPU init, or otherwise
352            // the sizes are immediately invalidated.
353            size: query_render_extent(xcb_connection, x_window),
354            transparent: params.window_background != WindowBackgroundAppearance::Opaque,
355        };
356
357        Self {
358            client,
359            executor,
360            display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
361            raw,
362            bounds: params.bounds.map(|v| v.0),
363            scale_factor,
364            renderer: BladeRenderer::new(gpu, config),
365            atoms: *atoms,
366            input_handler: None,
367        }
368    }
369
370    fn content_size(&self) -> Size<Pixels> {
371        let size = self.renderer.viewport_size();
372        Size {
373            width: size.width.into(),
374            height: size.height.into(),
375        }
376    }
377}
378
379pub(crate) struct X11Window(pub X11WindowStatePtr);
380
381impl Drop for X11Window {
382    fn drop(&mut self) {
383        let mut state = self.0.state.borrow_mut();
384        state.renderer.destroy();
385
386        self.0.xcb_connection.unmap_window(self.0.x_window).unwrap();
387        self.0
388            .xcb_connection
389            .destroy_window(self.0.x_window)
390            .unwrap();
391        self.0.xcb_connection.flush().unwrap();
392
393        let this_ptr = self.0.clone();
394        let client_ptr = state.client.clone();
395        state
396            .executor
397            .spawn(async move {
398                this_ptr.close();
399                client_ptr.drop_window(this_ptr.x_window);
400            })
401            .detach();
402        drop(state);
403    }
404}
405
406impl X11Window {
407    #[allow(clippy::too_many_arguments)]
408    pub fn new(
409        client: X11ClientStatePtr,
410        executor: ForegroundExecutor,
411        params: WindowParams,
412        xcb_connection: &Rc<XCBConnection>,
413        x_main_screen_index: usize,
414        x_window: xproto::Window,
415        atoms: &XcbAtoms,
416        scale_factor: f32,
417    ) -> Self {
418        Self(X11WindowStatePtr {
419            state: Rc::new(RefCell::new(X11WindowState::new(
420                client,
421                executor,
422                params,
423                xcb_connection,
424                x_main_screen_index,
425                x_window,
426                atoms,
427                scale_factor,
428            ))),
429            callbacks: Rc::new(RefCell::new(Callbacks::default())),
430            xcb_connection: xcb_connection.clone(),
431            x_window,
432        })
433    }
434}
435
436impl X11WindowStatePtr {
437    pub fn should_close(&self) -> bool {
438        let mut cb = self.callbacks.borrow_mut();
439        if let Some(mut should_close) = cb.should_close.take() {
440            let result = (should_close)();
441            cb.should_close = Some(should_close);
442            result
443        } else {
444            true
445        }
446    }
447
448    pub fn close(&self) {
449        let mut callbacks = self.callbacks.borrow_mut();
450        if let Some(fun) = callbacks.close.take() {
451            fun()
452        }
453    }
454
455    pub fn refresh(&self) {
456        let mut cb = self.callbacks.borrow_mut();
457        if let Some(ref mut fun) = cb.request_frame {
458            fun();
459        }
460    }
461
462    pub fn handle_input(&self, input: PlatformInput) {
463        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
464            if !fun(input.clone()).propagate {
465                return;
466            }
467        }
468        if let PlatformInput::KeyDown(event) = input {
469            let mut state = self.state.borrow_mut();
470            if let Some(mut input_handler) = state.input_handler.take() {
471                if let Some(ime_key) = &event.keystroke.ime_key {
472                    drop(state);
473                    input_handler.replace_text_in_range(None, ime_key);
474                    state = self.state.borrow_mut();
475                }
476                state.input_handler = Some(input_handler);
477            }
478        }
479    }
480
481    pub fn handle_ime_commit(&self, text: String) {
482        let mut state = self.state.borrow_mut();
483        if let Some(mut input_handler) = state.input_handler.take() {
484            drop(state);
485            input_handler.replace_text_in_range(None, &text);
486            let mut state = self.state.borrow_mut();
487            state.input_handler = Some(input_handler);
488        }
489    }
490
491    pub fn handle_ime_preedit(&self, text: String) {
492        let mut state = self.state.borrow_mut();
493        if let Some(mut input_handler) = state.input_handler.take() {
494            drop(state);
495            input_handler.replace_and_mark_text_in_range(None, &text, None);
496            let mut state = self.state.borrow_mut();
497            state.input_handler = Some(input_handler);
498        }
499    }
500
501    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
502        let mut state = self.state.borrow_mut();
503        let mut bounds: Option<Bounds<Pixels>> = None;
504        if let Some(mut input_handler) = state.input_handler.take() {
505            drop(state);
506            if let Some(range) = input_handler.selected_text_range() {
507                bounds = input_handler.bounds_for_range(range);
508            }
509            let mut state = self.state.borrow_mut();
510            state.input_handler = Some(input_handler);
511        };
512        bounds
513    }
514
515    pub fn configure(&self, bounds: Bounds<i32>) {
516        let mut resize_args = None;
517        let do_move;
518        {
519            let mut state = self.state.borrow_mut();
520            let old_bounds = mem::replace(&mut state.bounds, bounds);
521            do_move = old_bounds.origin != bounds.origin;
522            // todo(linux): use normal GPUI types here, refactor out the double
523            // viewport check and extra casts ( )
524            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
525            if state.renderer.viewport_size() != gpu_size {
526                state
527                    .renderer
528                    .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
529                resize_args = Some((state.content_size(), state.scale_factor));
530            }
531        }
532
533        let mut callbacks = self.callbacks.borrow_mut();
534        if let Some((content_size, scale_factor)) = resize_args {
535            if let Some(ref mut fun) = callbacks.resize {
536                fun(content_size, scale_factor)
537            }
538        }
539        if do_move {
540            if let Some(ref mut fun) = callbacks.moved {
541                fun()
542            }
543        }
544    }
545
546    pub fn set_focused(&self, focus: bool) {
547        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
548            fun(focus);
549        }
550    }
551}
552
553impl PlatformWindow for X11Window {
554    fn bounds(&self) -> Bounds<DevicePixels> {
555        self.0.state.borrow().bounds.map(|v| v.into())
556    }
557
558    // todo(linux)
559    fn is_maximized(&self) -> bool {
560        false
561    }
562
563    // todo(linux)
564    fn window_bounds(&self) -> WindowBounds {
565        let state = self.0.state.borrow();
566        WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p)))
567    }
568
569    fn content_size(&self) -> Size<Pixels> {
570        // We divide by the scale factor here because this value is queried to determine how much to draw,
571        // but it will be multiplied later by the scale to adjust for scaling.
572        let state = self.0.state.borrow();
573        state
574            .content_size()
575            .map(|size| size.div(state.scale_factor))
576    }
577
578    fn scale_factor(&self) -> f32 {
579        self.0.state.borrow().scale_factor
580    }
581
582    // todo(linux)
583    fn appearance(&self) -> WindowAppearance {
584        WindowAppearance::Light
585    }
586
587    fn display(&self) -> Rc<dyn PlatformDisplay> {
588        self.0.state.borrow().display.clone()
589    }
590
591    fn mouse_position(&self) -> Point<Pixels> {
592        let reply = self
593            .0
594            .xcb_connection
595            .query_pointer(self.0.x_window)
596            .unwrap()
597            .reply()
598            .unwrap();
599        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
600    }
601
602    // todo(linux)
603    fn modifiers(&self) -> Modifiers {
604        Modifiers::default()
605    }
606
607    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
608        self.0.state.borrow_mut().input_handler = Some(input_handler);
609    }
610
611    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
612        self.0.state.borrow_mut().input_handler.take()
613    }
614
615    fn prompt(
616        &self,
617        _level: PromptLevel,
618        _msg: &str,
619        _detail: Option<&str>,
620        _answers: &[&str],
621    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
622        None
623    }
624
625    fn activate(&self) {
626        let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
627        self.0
628            .xcb_connection
629            .configure_window(self.0.x_window, &win_aux)
630            .log_err();
631    }
632
633    // todo(linux)
634    fn is_active(&self) -> bool {
635        false
636    }
637
638    fn set_title(&mut self, title: &str) {
639        self.0
640            .xcb_connection
641            .change_property8(
642                xproto::PropMode::REPLACE,
643                self.0.x_window,
644                xproto::AtomEnum::WM_NAME,
645                xproto::AtomEnum::STRING,
646                title.as_bytes(),
647            )
648            .unwrap();
649
650        self.0
651            .xcb_connection
652            .change_property8(
653                xproto::PropMode::REPLACE,
654                self.0.x_window,
655                self.0.state.borrow().atoms._NET_WM_NAME,
656                self.0.state.borrow().atoms.UTF8_STRING,
657                title.as_bytes(),
658            )
659            .unwrap();
660    }
661
662    fn set_app_id(&mut self, app_id: &str) {
663        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
664        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
665        data.push(b'\0');
666        data.extend(app_id.bytes()); // class
667
668        self.0.xcb_connection.change_property8(
669            xproto::PropMode::REPLACE,
670            self.0.x_window,
671            xproto::AtomEnum::WM_CLASS,
672            xproto::AtomEnum::STRING,
673            &data,
674        );
675    }
676
677    // todo(linux)
678    fn set_edited(&mut self, edited: bool) {}
679
680    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
681        let mut inner = self.0.state.borrow_mut();
682        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
683        inner.renderer.update_transparency(transparent);
684    }
685
686    // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
687    // but it looks like the equivalent for Linux is GTK specific:
688    //
689    // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
690    //
691    // This API might need to change, or we might need to build an emoji picker into GPUI
692    fn show_character_palette(&self) {
693        unimplemented!()
694    }
695
696    // todo(linux)
697    fn minimize(&self) {
698        unimplemented!()
699    }
700
701    // todo(linux)
702    fn zoom(&self) {
703        unimplemented!()
704    }
705
706    // todo(linux)
707    fn toggle_fullscreen(&self) {
708        unimplemented!()
709    }
710
711    // todo(linux)
712    fn is_fullscreen(&self) -> bool {
713        false
714    }
715
716    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
717        self.0.callbacks.borrow_mut().request_frame = Some(callback);
718    }
719
720    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
721        self.0.callbacks.borrow_mut().input = Some(callback);
722    }
723
724    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
725        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
726    }
727
728    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
729        self.0.callbacks.borrow_mut().resize = Some(callback);
730    }
731
732    fn on_moved(&self, callback: Box<dyn FnMut()>) {
733        self.0.callbacks.borrow_mut().moved = Some(callback);
734    }
735
736    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
737        self.0.callbacks.borrow_mut().should_close = Some(callback);
738    }
739
740    fn on_close(&self, callback: Box<dyn FnOnce()>) {
741        self.0.callbacks.borrow_mut().close = Some(callback);
742    }
743
744    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
745        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
746    }
747
748    fn draw(&self, scene: &Scene) {
749        let mut inner = self.0.state.borrow_mut();
750        inner.renderer.draw(scene);
751    }
752
753    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
754        let inner = self.0.state.borrow();
755        inner.renderer.sprite_atlas().clone()
756    }
757
758    // todo(linux)
759    fn show_window_menu(&self, _position: Point<Pixels>) {}
760
761    // todo(linux)
762    fn start_system_move(&self) {}
763
764    fn should_render_window_controls(&self) -> bool {
765        false
766    }
767}