1513 lines · 50.9 KB · Rust
RawDownload
use std::{    cell::{Cell, RefCell},    ffi::OsStr,    path::{Path, PathBuf},    rc::{Rc, Weak},    sync::{        Arc,        atomic::{AtomicBool, Ordering},    },}; use anyhow::{Context as _, Result, anyhow};use futures::channel::oneshot::{self, Receiver};use gpui_util::{ResultExt, get_windows_system_shell, new_std_command};use itertools::Itertools;use parking_lot::RwLock;use smallvec::SmallVec;use windows::{    UI::ViewManagement::UISettings,    Win32::{        Foundation::*,        Graphics::{Direct3D11::ID3D11Device, Gdi::*},        Security::Credentials::*,        System::{Com::*, LibraryLoader::*, Ole::*, Power::*, SystemInformation::*},        UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},    },    core::*,}; use crate::*;use gpui::*; pub struct WindowsPlatform {    inner: Rc<WindowsPlatformInner>,    raw_window_handles: Arc<RwLock<SmallVec<[SafeHwnd; 4]>>>,    // The below members will never change throughout the entire lifecycle of the app.    headless: bool,    icon: HICON,    background_executor: BackgroundExecutor,    foreground_executor: ForegroundExecutor,    text_system: Arc<dyn PlatformTextSystem>,    direct_write_text_system: Option<Arc<DirectWriteTextSystem>>,    drop_target_helper: Option<IDropTargetHelper>,    /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices    /// as resizing them has failed, causing us to have lost at least the render target.    invalidate_devices: Arc<AtomicBool>,    handle: HWND,    suspend_resume_notification: RefCell<Option<HPOWERNOTIFY>>,    disable_direct_composition: bool,    has_package_identity: bool,    app_identity: RefCell<Option<(String, String)>>,    system_notifications: RefCell<SystemNotificationState>,} struct WindowsPlatformInner {    state: WindowsPlatformState,    raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,    // The below members will never change throughout the entire lifecycle of the app.    validation_number: usize,    main_receiver: PriorityQueueReceiver<RunnableVariant>,    dispatcher: Arc<WindowsDispatcher>,} pub(crate) struct WindowsPlatformState {    callbacks: PlatformCallbacks,    menus: RefCell<Vec<OwnedMenu>>,    jump_list: RefCell<JumpList>,    // NOTE: standard cursor handles don't need to close.    pub(crate) current_cursor: Cell<Option<HCURSOR>>,    /// Shared with each window so `WM_SETCURSOR` can read it directly.    pub(crate) cursor_visible: Arc<AtomicBool>,    /// Shared with each window to coordinate draws across windows on the UI    /// thread; see [`DrawCoordinator`].    pub(crate) draw_coordinator: Rc<DrawCoordinator>,    directx_devices: RefCell<Option<DirectXDevices>>,} #[derive(Default)]struct PlatformCallbacks {    open_urls: Cell<Option<Box<dyn FnMut(Vec<String>)>>>,    quit: Cell<Option<Box<dyn FnMut()>>>,    reopen: Cell<Option<Box<dyn FnMut()>>>,    app_menu_action: Cell<Option<Box<dyn FnMut(&dyn Action)>>>,    will_open_app_menu: Cell<Option<Box<dyn FnMut()>>>,    validate_app_menu_command: Cell<Option<Box<dyn FnMut(&dyn Action) -> bool>>>,    keyboard_layout_change: Cell<Option<Box<dyn FnMut()>>>,    system_wake: Cell<Option<Box<dyn FnMut()>>>,} impl WindowsPlatformState {    fn new(directx_devices: Option<DirectXDevices>) -> Self {        let callbacks = PlatformCallbacks::default();        let jump_list = JumpList::new();        let current_cursor = load_cursor(CursorStyle::Arrow);         Self {            callbacks,            jump_list: RefCell::new(jump_list),            current_cursor: Cell::new(current_cursor),            cursor_visible: Arc::new(AtomicBool::new(true)),            draw_coordinator: Rc::new(DrawCoordinator::new()),            directx_devices: RefCell::new(directx_devices),            menus: RefCell::new(Vec::new()),        }    }} impl WindowsPlatform {    pub fn new(headless: bool) -> Result<Self> {        unsafe {            OleInitialize(None).context("unable to initialize Windows OLE")?;        }        let (directx_devices, text_system, direct_write_text_system) = if !headless {            let devices = DirectXDevices::new().context("Creating DirectX devices")?;            let dw_text_system = Arc::new(                DirectWriteTextSystem::new(&devices)                    .context("Error creating DirectWriteTextSystem")?,            );            (                Some(devices),                dw_text_system.clone() as Arc<dyn PlatformTextSystem>,                Some(dw_text_system),            )        } else {            (                None,                Arc::new(gpui::NoopTextSystem::new()) as Arc<dyn PlatformTextSystem>,                None,            )        };         let (main_sender, main_receiver) = PriorityQueueReceiver::new();        let validation_number = if usize::BITS == 64 {            rand::random::<u64>() as usize        } else {            rand::random::<u32>() as usize        };        let raw_window_handles = Arc::new(RwLock::new(SmallVec::new()));         register_platform_window_class();        let mut context = PlatformWindowCreateContext {            inner: None,            raw_window_handles: Arc::downgrade(&raw_window_handles),            validation_number,            main_sender: Some(main_sender),            main_receiver: Some(main_receiver),            directx_devices,            dispatcher: None,        };        let result = unsafe {            CreateWindowExW(                WINDOW_EX_STYLE(0),                PLATFORM_WINDOW_CLASS_NAME,                None,                WINDOW_STYLE(0),                0,                0,                0,                0,                Some(HWND_MESSAGE),                None,                None,                Some(&raw const context as *const _),            )        };        let inner = context            .inner            .take()            .context("CreateWindowExW did not run correctly")??;        let dispatcher = context            .dispatcher            .take()            .context("CreateWindowExW did not run correctly")?;        let handle = result?;         let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)            .is_ok_and(|value| value == "true" || value == "1");        let background_executor = BackgroundExecutor::new(dispatcher.clone());        let foreground_executor = ForegroundExecutor::new(dispatcher);         let drop_target_helper: Option<IDropTargetHelper> = if !headless {            Some(unsafe {                CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)                    .context("Error creating drop target helper.")?            })        } else {            None        };        let icon = if !headless {            load_icon().unwrap_or_default()        } else {            HICON::default()        };         Ok(Self {            inner,            handle,            raw_window_handles,            headless,            icon,            background_executor,            foreground_executor,            text_system,            direct_write_text_system,            suspend_resume_notification: RefCell::new(None),            disable_direct_composition,            has_package_identity: has_package_identity(),            drop_target_helper,            invalidate_devices: Arc::new(AtomicBool::new(false)),            app_identity: RefCell::new(None),            system_notifications: RefCell::new(SystemNotificationState::new()),        })    }     pub(crate) fn window_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {        self.raw_window_handles            .read()            .iter()            .find(|entry| entry.as_raw() == hwnd)            .and_then(|hwnd| window_from_hwnd(hwnd.as_raw()))    }     #[inline]    fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {        self.raw_window_handles            .read()            .iter()            .for_each(|handle| unsafe {                PostMessageW(Some(handle.as_raw()), message, wparam, lparam).log_err();            });    }     fn generate_creation_info(&self) -> WindowCreationInfo {        WindowCreationInfo {            icon: self.icon,            executor: self.foreground_executor.clone(),            current_cursor: self.inner.state.current_cursor.get(),            cursor_visible: self.inner.state.cursor_visible.clone(),            drop_target_helper: self.drop_target_helper.clone().unwrap(),            validation_number: self.inner.validation_number,            main_receiver: self.inner.main_receiver.clone(),            platform_window_handle: self.handle,            disable_direct_composition: self.disable_direct_composition,            directx_devices: self.inner.state.directx_devices.borrow().clone().unwrap(),            invalidate_devices: self.invalidate_devices.clone(),            draw_coordinator: self.inner.state.draw_coordinator.clone(),        }    }     fn set_dock_menus(&self, menus: Vec<MenuItem>) {        let mut actions = Vec::new();        menus.into_iter().for_each(|menu| {            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {                actions.push(dock_menu);            }        });        self.inner.state.jump_list.borrow_mut().dock_menus = actions;        let borrow = self.inner.state.jump_list.borrow();        let dock_menus = borrow            .dock_menus            .iter()            .map(|menu| (menu.name.clone(), menu.description.clone()))            .collect::<Vec<_>>();        let recent_workspaces = borrow.recent_workspaces.clone();        self.background_executor            .spawn(async move {                update_jump_list(&recent_workspaces, &dock_menus).log_err();            })            .detach();    }     fn update_jump_list(        &self,        menus: Vec<MenuItem>,        entries: Vec<SmallVec<[PathBuf; 2]>>,    ) -> Task<Vec<SmallVec<[PathBuf; 2]>>> {        let mut actions = Vec::new();        menus.into_iter().for_each(|menu| {            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {                actions.push(dock_menu);            }        });        let mut jump_list = self.inner.state.jump_list.borrow_mut();        jump_list.dock_menus = actions;        jump_list.recent_workspaces = entries.into();        let dock_menus = jump_list            .dock_menus            .iter()            .map(|menu| (menu.name.clone(), menu.description.clone()))            .collect::<Vec<_>>();        let recent_workspaces = jump_list.recent_workspaces.clone();        self.background_executor.spawn(async move {            update_jump_list(&recent_workspaces, &dock_menus)                .log_err()                .unwrap_or_default()        })    }     fn find_current_active_window(&self) -> Option<HWND> {        let active_window_hwnd = unsafe { GetActiveWindow() };        if active_window_hwnd.is_invalid() {            return None;        }        self.raw_window_handles            .read()            .iter()            .find(|hwnd| hwnd.as_raw() == active_window_hwnd)            .map(|hwnd| hwnd.as_raw())    }     fn begin_vsync_thread(&self) {        let Some(directx_devices) = self.inner.state.directx_devices.borrow().clone() else {            return;        };        let Some(direct_write_text_system) = &self.direct_write_text_system else {            return;        };        let mut directx_device = directx_devices;        let platform_window: SafeHwnd = self.handle.into();        let validation_number = self.inner.validation_number;        let all_windows = Arc::downgrade(&self.raw_window_handles);        let text_system = Arc::downgrade(direct_write_text_system);        let invalidate_devices = self.invalidate_devices.clone();         std::thread::Builder::new()            .name("VSyncProvider".to_owned())            .spawn(move || {                let vsync_provider = VSyncProvider::new();                loop {                    vsync_provider.wait_for_vsync();                    if check_device_lost(&directx_device.device)                        || invalidate_devices.fetch_and(false, Ordering::Acquire)                    {                        if let Err(err) = handle_gpu_device_lost(                            &mut directx_device,                            platform_window.as_raw(),                            validation_number,                            &all_windows,                            &text_system,                        ) {                            panic!("Device lost: {err}");                        }                    }                    let Some(all_windows) = all_windows.upgrade() else {                        break;                    };                    for hwnd in all_windows.read().iter() {                        unsafe {                            let _ = RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE);                        }                    }                }            })            .unwrap();    }} fn translate_accelerator(msg: &MSG) -> Option<()> {    if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN {        return None;    }     let result = unsafe {        SendMessageW(            msg.hwnd,            WM_GPUI_KEYDOWN,            Some(msg.wParam),            Some(msg.lParam),        )    };    (result.0 == 0).then_some(())} impl Platform for WindowsPlatform {    fn background_executor(&self) -> BackgroundExecutor {        self.background_executor.clone()    }     fn foreground_executor(&self) -> ForegroundExecutor {        self.foreground_executor.clone()    }     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {        self.text_system.clone()    }     fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {        Box::new(            WindowsKeyboardLayout::new()                .log_err()                .unwrap_or(WindowsKeyboardLayout::unknown()),        )    }     fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {        Rc::new(WindowsKeyboardMapper::new())    }     fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {        self.inner            .state            .callbacks            .keyboard_layout_change            .set(Some(callback));    }     fn on_thermal_state_change(&self, _callback: Box<dyn FnMut()>) {}     fn thermal_state(&self) -> ThermalState {        ThermalState::Nominal    }     fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {        on_finish_launching();        if !self.headless {            self.begin_vsync_thread();        }         let mut msg = MSG::default();        unsafe {            while GetMessageW(&mut msg, None, 0, 0).as_bool() {                if translate_accelerator(&msg).is_none() {                    _ = TranslateMessage(&msg);                    DispatchMessageW(&msg);                }            }        }         self.inner            .with_callback(|callbacks| &callbacks.quit, |callback| callback());    }     fn quit(&self) {        self.foreground_executor()            .spawn(async { unsafe { PostQuitMessage(0) } })            .detach();    }     fn restart(&self, binary_path: Option<PathBuf>) {        let pid = std::process::id();        let Some(app_path) = binary_path.or(self.app_path().log_err()) else {            return;        };        let script = format!(            r#"            $pidToWaitFor = {}            $exePath = "{}"             while ($true) {{                $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue                if (-not $process) {{                    Start-Process -FilePath $exePath                    break                }}                Start-Sleep -Seconds 0.1            }}            "#,            pid,            app_path.display(),        );         // Defer spawning to the foreground executor so it runs after the        // current `AppCell` borrow is released. On Windows, `Command::spawn()`        // can pump the Win32 message loop (via `CreateProcessW`), which        // re-enters message handling possibly resulting in another mutable        // borrow of the `AppCell` ending up with a double borrow panic        self.foreground_executor            .spawn(async move {                #[allow(                    clippy::disallowed_methods,                    reason = "We are restarting ourselves, using std command thus is fine"                )]                let restart_process = new_std_command(get_windows_system_shell())                    .arg("-command")                    .arg(script)                    .spawn();                 match restart_process {                    Ok(_) => unsafe { PostQuitMessage(0) },                    Err(e) => log::error!("failed to spawn restart script: {:?}", e),                }            })            .detach();    }     fn activate(&self, _ignoring_other_apps: bool) {}     fn hide(&self) {}     // todo(windows)    fn hide_other_apps(&self) {        unimplemented!()    }     // todo(windows)    fn unhide_other_apps(&self) {        unimplemented!()    }     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {

Lines 1–500 of 1513.