window.rs

  1use crate::display::WebDisplay;
  2use crate::events::{ClickState, WebEventListeners, is_mac_platform};
  3use std::sync::Arc;
  4use std::{cell::Cell, cell::RefCell, rc::Rc};
  5
  6use gpui::{
  7    AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, DispatchEventResult, GpuSpecs,
  8    Modifiers, MouseButton, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
  9    PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
 10    ResizeEdge, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
 11    WindowControlArea, WindowControls, WindowDecorations, WindowParams, px,
 12};
 13use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
 14use wasm_bindgen::prelude::*;
 15
 16#[derive(Default)]
 17pub(crate) struct WebWindowCallbacks {
 18    pub(crate) request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 19    pub(crate) input: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
 20    pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
 21    pub(crate) hover_status_change: Option<Box<dyn FnMut(bool)>>,
 22    pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 23    pub(crate) moved: Option<Box<dyn FnMut()>>,
 24    pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
 25    pub(crate) close: Option<Box<dyn FnOnce()>>,
 26    pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
 27    pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
 28}
 29
 30pub(crate) struct WebWindowMutableState {
 31    pub(crate) renderer: WgpuRenderer,
 32    pub(crate) bounds: Bounds<Pixels>,
 33    pub(crate) scale_factor: f32,
 34    pub(crate) max_texture_dimension: u32,
 35    pub(crate) title: String,
 36    pub(crate) input_handler: Option<PlatformInputHandler>,
 37    pub(crate) is_fullscreen: bool,
 38    pub(crate) is_active: bool,
 39    pub(crate) is_hovered: bool,
 40    pub(crate) mouse_position: Point<Pixels>,
 41    pub(crate) modifiers: Modifiers,
 42    pub(crate) capslock: Capslock,
 43}
 44
 45pub(crate) struct WebWindowInner {
 46    pub(crate) browser_window: web_sys::Window,
 47    pub(crate) canvas: web_sys::HtmlCanvasElement,
 48    pub(crate) has_device_pixel_support: bool,
 49    pub(crate) is_mac: bool,
 50    pub(crate) state: RefCell<WebWindowMutableState>,
 51    pub(crate) callbacks: RefCell<WebWindowCallbacks>,
 52    pub(crate) click_state: RefCell<ClickState>,
 53    pub(crate) pressed_button: Cell<Option<MouseButton>>,
 54    pub(crate) last_physical_size: Cell<(u32, u32)>,
 55    pub(crate) notify_scale: Cell<bool>,
 56    mql_handle: RefCell<Option<MqlHandle>>,
 57}
 58
 59pub struct WebWindow {
 60    inner: Rc<WebWindowInner>,
 61    display: Rc<dyn PlatformDisplay>,
 62    #[allow(dead_code)]
 63    handle: AnyWindowHandle,
 64    _raf_closure: Closure<dyn FnMut()>,
 65    _resize_observer: Option<web_sys::ResizeObserver>,
 66    _resize_observer_closure: Closure<dyn FnMut(js_sys::Array)>,
 67    _event_listeners: WebEventListeners,
 68}
 69
 70impl WebWindow {
 71    pub fn new(
 72        handle: AnyWindowHandle,
 73        _params: WindowParams,
 74        context: &WgpuContext,
 75        browser_window: web_sys::Window,
 76    ) -> anyhow::Result<Self> {
 77        let document = browser_window
 78            .document()
 79            .ok_or_else(|| anyhow::anyhow!("No `document` found on window"))?;
 80
 81        let canvas: web_sys::HtmlCanvasElement = document
 82            .create_element("canvas")
 83            .map_err(|e| anyhow::anyhow!("Failed to create canvas element: {e:?}"))?
 84            .dyn_into()
 85            .map_err(|e| anyhow::anyhow!("Created element is not a canvas: {e:?}"))?;
 86
 87        let dpr = browser_window.device_pixel_ratio() as f32;
 88        let max_texture_dimension = context.device.limits().max_texture_dimension_2d;
 89        let has_device_pixel_support = check_device_pixel_support();
 90
 91        canvas.set_tab_index(0);
 92
 93        let style = canvas.style();
 94        style
 95            .set_property("width", "100%")
 96            .map_err(|e| anyhow::anyhow!("Failed to set canvas width style: {e:?}"))?;
 97        style
 98            .set_property("height", "100%")
 99            .map_err(|e| anyhow::anyhow!("Failed to set canvas height style: {e:?}"))?;
100        style
101            .set_property("display", "block")
102            .map_err(|e| anyhow::anyhow!("Failed to set canvas display style: {e:?}"))?;
103        style
104            .set_property("outline", "none")
105            .map_err(|e| anyhow::anyhow!("Failed to set canvas outline style: {e:?}"))?;
106        style
107            .set_property("touch-action", "none")
108            .map_err(|e| anyhow::anyhow!("Failed to set touch-action style: {e:?}"))?;
109
110        let body = document
111            .body()
112            .ok_or_else(|| anyhow::anyhow!("No `body` found on document"))?;
113        body.append_child(&canvas)
114            .map_err(|e| anyhow::anyhow!("Failed to append canvas to body: {e:?}"))?;
115
116        canvas.focus().ok();
117
118        let device_size = Size {
119            width: DevicePixels(0),
120            height: DevicePixels(0),
121        };
122
123        let renderer_config = WgpuSurfaceConfig {
124            size: device_size,
125            transparent: false,
126        };
127
128        let renderer = WgpuRenderer::new_from_canvas(context, &canvas, renderer_config)?;
129
130        let display: Rc<dyn PlatformDisplay> = Rc::new(WebDisplay::new(browser_window.clone()));
131
132        let initial_bounds = Bounds {
133            origin: Point::default(),
134            size: Size::default(),
135        };
136
137        let mutable_state = WebWindowMutableState {
138            renderer,
139            bounds: initial_bounds,
140            scale_factor: dpr,
141            max_texture_dimension,
142            title: String::new(),
143            input_handler: None,
144            is_fullscreen: false,
145            is_active: true,
146            is_hovered: false,
147            mouse_position: Point::default(),
148            modifiers: Modifiers::default(),
149            capslock: Capslock::default(),
150        };
151
152        let is_mac = is_mac_platform(&browser_window);
153
154        let inner = Rc::new(WebWindowInner {
155            browser_window,
156            canvas,
157            has_device_pixel_support,
158            is_mac,
159            state: RefCell::new(mutable_state),
160            callbacks: RefCell::new(WebWindowCallbacks::default()),
161            click_state: RefCell::new(ClickState::default()),
162            pressed_button: Cell::new(None),
163            last_physical_size: Cell::new((0, 0)),
164            notify_scale: Cell::new(false),
165            mql_handle: RefCell::new(None),
166        });
167
168        let raf_closure = inner.create_raf_closure();
169        inner.schedule_raf(&raf_closure);
170
171        let resize_observer_closure = Self::create_resize_observer_closure(Rc::clone(&inner));
172        let resize_observer =
173            web_sys::ResizeObserver::new(resize_observer_closure.as_ref().unchecked_ref()).ok();
174
175        if let Some(ref observer) = resize_observer {
176            inner.observe_canvas(observer);
177            inner.watch_dpr_changes(observer);
178        }
179
180        let event_listeners = inner.register_event_listeners();
181
182        Ok(Self {
183            inner,
184            display,
185            handle,
186            _raf_closure: raf_closure,
187            _resize_observer: resize_observer,
188            _resize_observer_closure: resize_observer_closure,
189            _event_listeners: event_listeners,
190        })
191    }
192
193    fn create_resize_observer_closure(
194        inner: Rc<WebWindowInner>,
195    ) -> Closure<dyn FnMut(js_sys::Array)> {
196        Closure::new(move |entries: js_sys::Array| {
197            let entry: web_sys::ResizeObserverEntry = match entries.get(0).dyn_into().ok() {
198                Some(entry) => entry,
199                None => return,
200            };
201
202            let dpr = inner.browser_window.device_pixel_ratio();
203            let dpr_f32 = dpr as f32;
204
205            let (physical_width, physical_height, logical_width, logical_height) =
206                if inner.has_device_pixel_support {
207                    let size: web_sys::ResizeObserverSize = entry
208                        .device_pixel_content_box_size()
209                        .get(0)
210                        .unchecked_into();
211                    let pw = size.inline_size() as u32;
212                    let ph = size.block_size() as u32;
213                    let lw = pw as f64 / dpr;
214                    let lh = ph as f64 / dpr;
215                    (pw, ph, lw as f32, lh as f32)
216                } else {
217                    // Safari fallback: use contentRect (always CSS px).
218                    let rect = entry.content_rect();
219                    let lw = rect.width() as f32;
220                    let lh = rect.height() as f32;
221                    let pw = (lw as f64 * dpr).round() as u32;
222                    let ph = (lh as f64 * dpr).round() as u32;
223                    (pw, ph, lw, lh)
224                };
225
226            let scale_changed = inner.notify_scale.replace(false);
227            let prev = inner.last_physical_size.get();
228            let size_changed = prev != (physical_width, physical_height);
229
230            if !scale_changed && !size_changed {
231                return;
232            }
233            inner
234                .last_physical_size
235                .set((physical_width, physical_height));
236
237            // Skip rendering to a zero-size canvas (e.g. display:none).
238            if physical_width == 0 || physical_height == 0 {
239                let mut s = inner.state.borrow_mut();
240                s.bounds.size = Size::default();
241                s.scale_factor = dpr_f32;
242                // Still fire the callback so GPUI knows the window is gone.
243                drop(s);
244                let mut cbs = inner.callbacks.borrow_mut();
245                if let Some(ref mut callback) = cbs.resize {
246                    callback(Size::default(), dpr_f32);
247                }
248                return;
249            }
250
251            let max_texture_dimension = inner.state.borrow().max_texture_dimension;
252            let clamped_width = physical_width.min(max_texture_dimension);
253            let clamped_height = physical_height.min(max_texture_dimension);
254
255            inner.canvas.set_width(clamped_width);
256            inner.canvas.set_height(clamped_height);
257
258            {
259                let mut s = inner.state.borrow_mut();
260                s.bounds.size = Size {
261                    width: px(logical_width),
262                    height: px(logical_height),
263                };
264                s.scale_factor = dpr_f32;
265                s.renderer.update_drawable_size(Size {
266                    width: DevicePixels(clamped_width as i32),
267                    height: DevicePixels(clamped_height as i32),
268                });
269            }
270
271            let new_size = Size {
272                width: px(logical_width),
273                height: px(logical_height),
274            };
275
276            let mut cbs = inner.callbacks.borrow_mut();
277            if let Some(ref mut callback) = cbs.resize {
278                callback(new_size, dpr_f32);
279            }
280        })
281    }
282}
283
284impl WebWindowInner {
285    fn create_raf_closure(self: &Rc<Self>) -> Closure<dyn FnMut()> {
286        let raf_handle: Rc<RefCell<Option<js_sys::Function>>> = Rc::new(RefCell::new(None));
287        let raf_handle_inner = Rc::clone(&raf_handle);
288
289        let this = Rc::clone(self);
290        let closure = Closure::new(move || {
291            {
292                let mut callbacks = this.callbacks.borrow_mut();
293                if let Some(ref mut callback) = callbacks.request_frame {
294                    callback(RequestFrameOptions {
295                        require_presentation: true,
296                        force_render: false,
297                    });
298                }
299            }
300
301            // Re-schedule for the next frame
302            if let Some(ref func) = *raf_handle_inner.borrow() {
303                this.browser_window.request_animation_frame(func).ok();
304            }
305        });
306
307        let js_func: js_sys::Function =
308            closure.as_ref().unchecked_ref::<js_sys::Function>().clone();
309        *raf_handle.borrow_mut() = Some(js_func);
310
311        closure
312    }
313
314    fn schedule_raf(&self, closure: &Closure<dyn FnMut()>) {
315        self.browser_window
316            .request_animation_frame(closure.as_ref().unchecked_ref())
317            .ok();
318    }
319
320    fn observe_canvas(&self, observer: &web_sys::ResizeObserver) {
321        observer.unobserve(&self.canvas);
322        if self.has_device_pixel_support {
323            let options = web_sys::ResizeObserverOptions::new();
324            options.set_box(web_sys::ResizeObserverBoxOptions::DevicePixelContentBox);
325            observer.observe_with_options(&self.canvas, &options);
326        } else {
327            observer.observe(&self.canvas);
328        }
329    }
330
331    fn watch_dpr_changes(self: &Rc<Self>, observer: &web_sys::ResizeObserver) {
332        let current_dpr = self.browser_window.device_pixel_ratio();
333        let media_query =
334            format!("(resolution: {current_dpr}dppx), (-webkit-device-pixel-ratio: {current_dpr})");
335        let Some(mql) = self.browser_window.match_media(&media_query).ok().flatten() else {
336            return;
337        };
338
339        let this = Rc::clone(self);
340        let observer = observer.clone();
341
342        let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
343            this.notify_scale.set(true);
344            this.observe_canvas(&observer);
345            this.watch_dpr_changes(&observer);
346        });
347
348        mql.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
349            .ok();
350
351        *self.mql_handle.borrow_mut() = Some(MqlHandle {
352            mql,
353            _closure: closure,
354        });
355    }
356
357    pub(crate) fn register_visibility_change(
358        self: &Rc<Self>,
359    ) -> Option<Closure<dyn FnMut(JsValue)>> {
360        let document = self.browser_window.document()?;
361        let this = Rc::clone(self);
362
363        let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
364            let is_visible = this
365                .browser_window
366                .document()
367                .map(|doc| {
368                    let state_str: String = js_sys::Reflect::get(&doc, &"visibilityState".into())
369                        .ok()
370                        .and_then(|v| v.as_string())
371                        .unwrap_or_default();
372                    state_str == "visible"
373                })
374                .unwrap_or(true);
375
376            {
377                let mut state = this.state.borrow_mut();
378                state.is_active = is_visible;
379            }
380            let mut callbacks = this.callbacks.borrow_mut();
381            if let Some(ref mut callback) = callbacks.active_status_change {
382                callback(is_visible);
383            }
384        });
385
386        document
387            .add_event_listener_with_callback("visibilitychange", closure.as_ref().unchecked_ref())
388            .ok();
389
390        Some(closure)
391    }
392
393    pub(crate) fn register_appearance_change(
394        self: &Rc<Self>,
395    ) -> Option<Closure<dyn FnMut(JsValue)>> {
396        let mql = self
397            .browser_window
398            .match_media("(prefers-color-scheme: dark)")
399            .ok()??;
400
401        let this = Rc::clone(self);
402        let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
403            let mut callbacks = this.callbacks.borrow_mut();
404            if let Some(ref mut callback) = callbacks.appearance_changed {
405                callback();
406            }
407        });
408
409        mql.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
410            .ok();
411
412        Some(closure)
413    }
414}
415
416fn current_appearance(browser_window: &web_sys::Window) -> WindowAppearance {
417    let is_dark = browser_window
418        .match_media("(prefers-color-scheme: dark)")
419        .ok()
420        .flatten()
421        .map(|mql| mql.matches())
422        .unwrap_or(false);
423
424    if is_dark {
425        WindowAppearance::Dark
426    } else {
427        WindowAppearance::Light
428    }
429}
430
431struct MqlHandle {
432    mql: web_sys::MediaQueryList,
433    _closure: Closure<dyn FnMut(JsValue)>,
434}
435
436impl Drop for MqlHandle {
437    fn drop(&mut self) {
438        self.mql
439            .remove_event_listener_with_callback("change", self._closure.as_ref().unchecked_ref())
440            .ok();
441    }
442}
443
444// Safari does not support `devicePixelContentBoxSize`, so detect whether it's available.
445fn check_device_pixel_support() -> bool {
446    let global: JsValue = js_sys::global().into();
447    let Ok(constructor) = js_sys::Reflect::get(&global, &"ResizeObserverEntry".into()) else {
448        return false;
449    };
450    let Ok(prototype) = js_sys::Reflect::get(&constructor, &"prototype".into()) else {
451        return false;
452    };
453    let descriptor = js_sys::Object::get_own_property_descriptor(
454        &prototype.unchecked_into::<js_sys::Object>(),
455        &"devicePixelContentBoxSize".into(),
456    );
457    !descriptor.is_undefined()
458}
459
460impl raw_window_handle::HasWindowHandle for WebWindow {
461    fn window_handle(
462        &self,
463    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
464        let canvas_ref: &JsValue = self.inner.canvas.as_ref();
465        let obj = std::ptr::NonNull::from(canvas_ref).cast::<std::ffi::c_void>();
466        let handle = raw_window_handle::WebCanvasWindowHandle::new(obj);
467        Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) })
468    }
469}
470
471impl raw_window_handle::HasDisplayHandle for WebWindow {
472    fn display_handle(
473        &self,
474    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
475        Ok(raw_window_handle::DisplayHandle::web())
476    }
477}
478
479impl PlatformWindow for WebWindow {
480    fn bounds(&self) -> Bounds<Pixels> {
481        self.inner.state.borrow().bounds
482    }
483
484    fn is_maximized(&self) -> bool {
485        false
486    }
487
488    fn window_bounds(&self) -> WindowBounds {
489        WindowBounds::Windowed(self.bounds())
490    }
491
492    fn content_size(&self) -> Size<Pixels> {
493        self.inner.state.borrow().bounds.size
494    }
495
496    fn resize(&mut self, size: Size<Pixels>) {
497        let style = self.inner.canvas.style();
498        style
499            .set_property("width", &format!("{}px", f32::from(size.width)))
500            .ok();
501        style
502            .set_property("height", &format!("{}px", f32::from(size.height)))
503            .ok();
504    }
505
506    fn scale_factor(&self) -> f32 {
507        self.inner.state.borrow().scale_factor
508    }
509
510    fn appearance(&self) -> WindowAppearance {
511        current_appearance(&self.inner.browser_window)
512    }
513
514    fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
515        Some(self.display.clone())
516    }
517
518    fn mouse_position(&self) -> Point<Pixels> {
519        self.inner.state.borrow().mouse_position
520    }
521
522    fn modifiers(&self) -> Modifiers {
523        self.inner.state.borrow().modifiers
524    }
525
526    fn capslock(&self) -> Capslock {
527        self.inner.state.borrow().capslock
528    }
529
530    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
531        self.inner.state.borrow_mut().input_handler = Some(input_handler);
532    }
533
534    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
535        self.inner.state.borrow_mut().input_handler.take()
536    }
537
538    fn prompt(
539        &self,
540        _level: PromptLevel,
541        _msg: &str,
542        _detail: Option<&str>,
543        _answers: &[PromptButton],
544    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
545        None
546    }
547
548    fn activate(&self) {
549        self.inner.state.borrow_mut().is_active = true;
550    }
551
552    fn is_active(&self) -> bool {
553        self.inner.state.borrow().is_active
554    }
555
556    fn is_hovered(&self) -> bool {
557        self.inner.state.borrow().is_hovered
558    }
559
560    fn background_appearance(&self) -> WindowBackgroundAppearance {
561        WindowBackgroundAppearance::Opaque
562    }
563
564    fn set_title(&mut self, title: &str) {
565        self.inner.state.borrow_mut().title = title.to_owned();
566        if let Some(document) = self.inner.browser_window.document() {
567            document.set_title(title);
568        }
569    }
570
571    fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
572
573    fn minimize(&self) {
574        log::warn!("WebWindow::minimize is not supported in the browser");
575    }
576
577    fn zoom(&self) {
578        log::warn!("WebWindow::zoom is not supported in the browser");
579    }
580
581    fn toggle_fullscreen(&self) {
582        let mut state = self.inner.state.borrow_mut();
583        state.is_fullscreen = !state.is_fullscreen;
584
585        if state.is_fullscreen {
586            let canvas: &web_sys::Element = self.inner.canvas.as_ref();
587            canvas.request_fullscreen().ok();
588        } else {
589            if let Some(document) = self.inner.browser_window.document() {
590                document.exit_fullscreen();
591            }
592        }
593    }
594
595    fn is_fullscreen(&self) -> bool {
596        self.inner.state.borrow().is_fullscreen
597    }
598
599    fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
600        self.inner.callbacks.borrow_mut().request_frame = Some(callback);
601    }
602
603    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
604        self.inner.callbacks.borrow_mut().input = Some(callback);
605    }
606
607    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
608        self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
609    }
610
611    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
612        self.inner.callbacks.borrow_mut().hover_status_change = Some(callback);
613    }
614
615    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
616        self.inner.callbacks.borrow_mut().resize = Some(callback);
617    }
618
619    fn on_moved(&self, callback: Box<dyn FnMut()>) {
620        self.inner.callbacks.borrow_mut().moved = Some(callback);
621    }
622
623    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
624        self.inner.callbacks.borrow_mut().should_close = Some(callback);
625    }
626
627    fn on_close(&self, callback: Box<dyn FnOnce()>) {
628        self.inner.callbacks.borrow_mut().close = Some(callback);
629    }
630
631    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
632        self.inner.callbacks.borrow_mut().hit_test_window_control = Some(callback);
633    }
634
635    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
636        self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
637    }
638
639    fn draw(&self, scene: &Scene) {
640        self.inner.state.borrow_mut().renderer.draw(scene);
641    }
642
643    fn completed_frame(&self) {
644        // On web, presentation happens automatically via wgpu surface present
645    }
646
647    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
648        self.inner.state.borrow().renderer.sprite_atlas().clone()
649    }
650
651    fn is_subpixel_rendering_supported(&self) -> bool {
652        self.inner
653            .state
654            .borrow()
655            .renderer
656            .supports_dual_source_blending()
657    }
658
659    fn gpu_specs(&self) -> Option<GpuSpecs> {
660        Some(self.inner.state.borrow().renderer.gpu_specs())
661    }
662
663    fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}
664
665    fn request_decorations(&self, _decorations: WindowDecorations) {}
666
667    fn show_window_menu(&self, _position: Point<Pixels>) {}
668
669    fn start_window_move(&self) {}
670
671    fn start_window_resize(&self, _edge: ResizeEdge) {}
672
673    fn window_decorations(&self) -> Decorations {
674        Decorations::Server
675    }
676
677    fn set_app_id(&mut self, _app_id: &str) {}
678
679    fn window_controls(&self) -> WindowControls {
680        WindowControls {
681            fullscreen: true,
682            maximize: false,
683            minimize: false,
684            window_menu: false,
685        }
686    }
687
688    fn set_client_inset(&self, _inset: Pixels) {}
689}