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