287 lines · 7.6 KB · Rust
RawDownload
//! Windows for the headless platform client.//!//! A headless window has no compositor surface and no GPU: layout, text//! shaping, and entity plumbing run normally, `draw` discards the scene, and//! the sprite atlas hands out tiles without uploading pixels (mirroring//! GPUI's `TestWindow`/`TestAtlas`). This lets command-line tools drive real//! `Window`-based code paths without a display server. use std::cell::RefCell;use std::rc::Rc;use std::sync::Arc; use collections::HashMap;use parking_lot::Mutex;use uuid::Uuid; use gpui::{    AtlasKey, AtlasTextureId, AtlasTile, Bounds, Capslock, DevicePixels, DispatchEventResult,    DisplayId, GpuSpecs, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,    PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,    Scene, Size, TileId, WindowAppearance, WindowBackgroundAppearance, WindowBounds,    WindowControlArea, WindowParams, px,}; #[derive(Debug)]pub(crate) struct HeadlessDisplay {    bounds: Bounds<Pixels>,} impl HeadlessDisplay {    pub(crate) fn new() -> Self {        Self {            bounds: Bounds::from_corners(Point::default(), Point::new(px(1920.), px(1080.))),        }    }} impl PlatformDisplay for HeadlessDisplay {    fn id(&self) -> DisplayId {        DisplayId::new(0)    }     fn uuid(&self) -> anyhow::Result<Uuid> {        // Stable identity: there is exactly one headless display.        Ok(Uuid::nil())    }     fn bounds(&self) -> Bounds<Pixels> {        self.bounds    }} struct HeadlessWindowState {    bounds: Bounds<Pixels>,    display: Rc<dyn PlatformDisplay>,    input_handler: Option<PlatformInputHandler>,    title: Option<String>,    is_fullscreen: bool,} pub(crate) struct HeadlessWindow(Rc<RefCell<HeadlessWindowState>>); impl raw_window_handle::HasWindowHandle for HeadlessWindow {    fn window_handle(        &self,    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {        // Headless windows are not backed by a native window.        Err(raw_window_handle::HandleError::NotSupported)    }} impl raw_window_handle::HasDisplayHandle for HeadlessWindow {    fn display_handle(        &self,    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {        Err(raw_window_handle::HandleError::NotSupported)    }} impl HeadlessWindow {    pub(crate) fn new(params: WindowParams, display: Rc<dyn PlatformDisplay>) -> Self {        Self(Rc::new(RefCell::new(HeadlessWindowState {            bounds: params.bounds,            display,            input_handler: None,            title: None,            is_fullscreen: false,        })))    }} impl PlatformWindow for HeadlessWindow {    fn bounds(&self) -> Bounds<Pixels> {        self.0.borrow().bounds    }     fn is_maximized(&self) -> bool {        false    }     fn window_bounds(&self) -> WindowBounds {        WindowBounds::Windowed(self.bounds())    }     fn content_size(&self) -> Size<Pixels> {        self.bounds().size    }     fn resize(&mut self, size: Size<Pixels>) {        self.0.borrow_mut().bounds.size = size;    }     fn scale_factor(&self) -> f32 {        1.0    }     fn appearance(&self) -> WindowAppearance {        WindowAppearance::Dark    }     fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {        Some(self.0.borrow().display.clone())    }     fn mouse_position(&self) -> Point<Pixels> {        Point::default()    }     fn modifiers(&self) -> Modifiers {        Modifiers::default()    }     fn capslock(&self) -> Capslock {        Capslock::default()    }     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {        self.0.borrow_mut().input_handler = Some(input_handler);    }     fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {        self.0.borrow_mut().input_handler.take()    }     fn prompt(        &self,        _level: PromptLevel,        _msg: &str,        _detail: Option<&str>,        _answers: &[PromptButton],    ) -> Option<futures::channel::oneshot::Receiver<usize>> {        // Fall back to GPUI's rendered prompts.        None    }     fn activate(&self) {}     fn is_active(&self) -> bool {        false    }     fn is_hovered(&self) -> bool {        false    }     fn background_appearance(&self) -> WindowBackgroundAppearance {        WindowBackgroundAppearance::Opaque    }     fn set_title(&mut self, title: &str) {        self.0.borrow_mut().title = Some(title.to_owned());    }     fn get_title(&self) -> String {        self.0.borrow().title.clone().unwrap_or_default()    }     fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}     fn minimize(&self) {}     fn zoom(&self) {}     fn toggle_fullscreen(&self) {        let mut state = self.0.borrow_mut();        state.is_fullscreen = !state.is_fullscreen;    }     fn is_fullscreen(&self) -> bool {        self.0.borrow().is_fullscreen    }     // No compositor drives a frame loop, so frame and status callbacks are    // dropped: anything that awaits a frame will never resolve headlessly.    fn on_request_frame(&self, _callback: Box<dyn FnMut(RequestFrameOptions)>) {}     fn on_input(&self, _callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {}     fn on_active_status_change(&self, _callback: Box<dyn FnMut(bool)>) {}     fn on_hover_status_change(&self, _callback: Box<dyn FnMut(bool)>) {}     fn on_resize(&self, _callback: Box<dyn FnMut(Size<Pixels>, f32)>) {}     fn on_moved(&self, _callback: Box<dyn FnMut()>) {}     fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {}     fn on_close(&self, _callback: Box<dyn FnOnce()>) {}     fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {    }     fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}     fn draw(&self, _scene: &Scene) {}     fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {        Arc::new(HeadlessAtlas::default())    }     fn is_subpixel_rendering_supported(&self) -> bool {        false    }     fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}     fn gpu_specs(&self) -> Option<GpuSpecs> {        None    }} /// Allocates atlas tiles without uploading pixels, so glyph and sprite/// painting completes headlessly.#[derive(Default)]struct HeadlessAtlas(Mutex<HeadlessAtlasState>); #[derive(Default)]struct HeadlessAtlasState {    next_id: u32,    tiles: HashMap<AtlasKey, AtlasTile>,} impl PlatformAtlas for HeadlessAtlas {    fn get_or_insert_with<'a>(        &self,        key: &AtlasKey,        build: &mut dyn FnMut() -> anyhow::Result<            Option<(Size<DevicePixels>, std::borrow::Cow<'a, [u8]>)>,        >,    ) -> anyhow::Result<Option<AtlasTile>> {        {            let state = self.0.lock();            if let Some(&tile) = state.tiles.get(key) {                return Ok(Some(tile));            }        }         let Some((size, _)) = build()? else {            return Ok(None);        };         let mut state = self.0.lock();        state.next_id += 1;        let texture_id = state.next_id;        state.next_id += 1;        let tile_id = state.next_id;        let tile = AtlasTile {            texture_id: AtlasTextureId {                index: texture_id,                kind: key.texture_kind(),            },            tile_id: TileId(tile_id),            padding: 0,            bounds: Bounds {                origin: Point::default(),                size,            },        };        state.tiles.insert(key.clone(), tile);        Ok(Some(tile))    }     fn remove(&self, key: &AtlasKey) {        self.0.lock().tiles.remove(key);    }}