platform.rs

   1use std::{
   2    cell::{Cell, RefCell},
   3    ffi::OsStr,
   4    path::{Path, PathBuf},
   5    rc::{Rc, Weak},
   6    sync::{
   7        Arc,
   8        atomic::{AtomicBool, Ordering},
   9    },
  10};
  11
  12use ::util::{ResultExt, paths::SanitizedPath};
  13use anyhow::{Context as _, Result, anyhow};
  14use futures::channel::oneshot::{self, Receiver};
  15use itertools::Itertools;
  16use parking_lot::RwLock;
  17use smallvec::SmallVec;
  18use windows::{
  19    UI::ViewManagement::UISettings,
  20    Win32::{
  21        Foundation::*,
  22        Graphics::{Direct3D11::ID3D11Device, Gdi::*},
  23        Security::Credentials::*,
  24        System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*},
  25        UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
  26    },
  27    core::*,
  28};
  29
  30use crate::*;
  31
  32pub(crate) struct WindowsPlatform {
  33    inner: Rc<WindowsPlatformInner>,
  34    raw_window_handles: Arc<RwLock<SmallVec<[SafeHwnd; 4]>>>,
  35    // The below members will never change throughout the entire lifecycle of the app.
  36    headless: bool,
  37    icon: HICON,
  38    background_executor: BackgroundExecutor,
  39    foreground_executor: ForegroundExecutor,
  40    text_system: Arc<dyn PlatformTextSystem>,
  41    direct_write_text_system: Option<Arc<DirectWriteTextSystem>>,
  42    windows_version: WindowsVersion,
  43    drop_target_helper: Option<IDropTargetHelper>,
  44    /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices
  45    /// as resizing them has failed, causing us to have lost at least the render target.
  46    invalidate_devices: Arc<AtomicBool>,
  47    handle: HWND,
  48    disable_direct_composition: bool,
  49}
  50
  51struct WindowsPlatformInner {
  52    state: WindowsPlatformState,
  53    raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
  54    // The below members will never change throughout the entire lifecycle of the app.
  55    validation_number: usize,
  56    main_receiver: PriorityQueueReceiver<RunnableVariant>,
  57    dispatcher: Arc<WindowsDispatcher>,
  58}
  59
  60pub(crate) struct WindowsPlatformState {
  61    callbacks: PlatformCallbacks,
  62    menus: RefCell<Vec<OwnedMenu>>,
  63    jump_list: RefCell<JumpList>,
  64    // NOTE: standard cursor handles don't need to close.
  65    pub(crate) current_cursor: Cell<Option<HCURSOR>>,
  66    directx_devices: RefCell<Option<DirectXDevices>>,
  67}
  68
  69#[derive(Default)]
  70struct PlatformCallbacks {
  71    open_urls: Cell<Option<Box<dyn FnMut(Vec<String>)>>>,
  72    quit: Cell<Option<Box<dyn FnMut()>>>,
  73    reopen: Cell<Option<Box<dyn FnMut()>>>,
  74    app_menu_action: Cell<Option<Box<dyn FnMut(&dyn Action)>>>,
  75    will_open_app_menu: Cell<Option<Box<dyn FnMut()>>>,
  76    validate_app_menu_command: Cell<Option<Box<dyn FnMut(&dyn Action) -> bool>>>,
  77    keyboard_layout_change: Cell<Option<Box<dyn FnMut()>>>,
  78}
  79
  80impl WindowsPlatformState {
  81    fn new(directx_devices: Option<DirectXDevices>) -> Self {
  82        let callbacks = PlatformCallbacks::default();
  83        let jump_list = JumpList::new();
  84        let current_cursor = load_cursor(CursorStyle::Arrow);
  85
  86        Self {
  87            callbacks,
  88            jump_list: RefCell::new(jump_list),
  89            current_cursor: Cell::new(current_cursor),
  90            directx_devices: RefCell::new(directx_devices),
  91            menus: RefCell::new(Vec::new()),
  92        }
  93    }
  94}
  95
  96impl WindowsPlatform {
  97    pub(crate) fn new(headless: bool) -> Result<Self> {
  98        unsafe {
  99            OleInitialize(None).context("unable to initialize Windows OLE")?;
 100        }
 101        let (directx_devices, text_system, direct_write_text_system) = if !headless {
 102            let devices = DirectXDevices::new().context("Creating DirectX devices")?;
 103            let dw_text_system = Arc::new(
 104                DirectWriteTextSystem::new(&devices)
 105                    .context("Error creating DirectWriteTextSystem")?,
 106            );
 107            (
 108                Some(devices),
 109                dw_text_system.clone() as Arc<dyn PlatformTextSystem>,
 110                Some(dw_text_system),
 111            )
 112        } else {
 113            (
 114                None,
 115                Arc::new(crate::NoopTextSystem::new()) as Arc<dyn PlatformTextSystem>,
 116                None,
 117            )
 118        };
 119
 120        let (main_sender, main_receiver) = PriorityQueueReceiver::new();
 121        let validation_number = if usize::BITS == 64 {
 122            rand::random::<u64>() as usize
 123        } else {
 124            rand::random::<u32>() as usize
 125        };
 126        let raw_window_handles = Arc::new(RwLock::new(SmallVec::new()));
 127
 128        register_platform_window_class();
 129        let mut context = PlatformWindowCreateContext {
 130            inner: None,
 131            raw_window_handles: Arc::downgrade(&raw_window_handles),
 132            validation_number,
 133            main_sender: Some(main_sender),
 134            main_receiver: Some(main_receiver),
 135            directx_devices,
 136            dispatcher: None,
 137        };
 138        let result = unsafe {
 139            CreateWindowExW(
 140                WINDOW_EX_STYLE(0),
 141                PLATFORM_WINDOW_CLASS_NAME,
 142                None,
 143                WINDOW_STYLE(0),
 144                0,
 145                0,
 146                0,
 147                0,
 148                Some(HWND_MESSAGE),
 149                None,
 150                None,
 151                Some(&raw const context as *const _),
 152            )
 153        };
 154        let inner = context
 155            .inner
 156            .take()
 157            .context("CreateWindowExW did not run correctly")??;
 158        let dispatcher = context
 159            .dispatcher
 160            .take()
 161            .context("CreateWindowExW did not run correctly")?;
 162        let handle = result?;
 163
 164        let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
 165            .is_ok_and(|value| value == "true" || value == "1");
 166        let background_executor = BackgroundExecutor::new(dispatcher.clone());
 167        let foreground_executor = ForegroundExecutor::new(dispatcher);
 168
 169        let drop_target_helper: Option<IDropTargetHelper> = if !headless {
 170            Some(unsafe {
 171                CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
 172                    .context("Error creating drop target helper.")?
 173            })
 174        } else {
 175            None
 176        };
 177        let icon = if !headless {
 178            load_icon().unwrap_or_default()
 179        } else {
 180            HICON::default()
 181        };
 182        let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
 183
 184        Ok(Self {
 185            inner,
 186            handle,
 187            raw_window_handles,
 188            headless,
 189            icon,
 190            background_executor,
 191            foreground_executor,
 192            text_system,
 193            direct_write_text_system,
 194            disable_direct_composition,
 195            windows_version,
 196            drop_target_helper,
 197            invalidate_devices: Arc::new(AtomicBool::new(false)),
 198        })
 199    }
 200
 201    pub fn window_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
 202        self.raw_window_handles
 203            .read()
 204            .iter()
 205            .find(|entry| entry.as_raw() == hwnd)
 206            .and_then(|hwnd| window_from_hwnd(hwnd.as_raw()))
 207    }
 208
 209    #[inline]
 210    fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {
 211        self.raw_window_handles
 212            .read()
 213            .iter()
 214            .for_each(|handle| unsafe {
 215                PostMessageW(Some(handle.as_raw()), message, wparam, lparam).log_err();
 216            });
 217    }
 218
 219    fn generate_creation_info(&self) -> WindowCreationInfo {
 220        WindowCreationInfo {
 221            icon: self.icon,
 222            executor: self.foreground_executor.clone(),
 223            current_cursor: self.inner.state.current_cursor.get(),
 224            windows_version: self.windows_version,
 225            drop_target_helper: self.drop_target_helper.clone().unwrap(),
 226            validation_number: self.inner.validation_number,
 227            main_receiver: self.inner.main_receiver.clone(),
 228            platform_window_handle: self.handle,
 229            disable_direct_composition: self.disable_direct_composition,
 230            directx_devices: self.inner.state.directx_devices.borrow().clone().unwrap(),
 231            invalidate_devices: self.invalidate_devices.clone(),
 232        }
 233    }
 234
 235    fn set_dock_menus(&self, menus: Vec<MenuItem>) {
 236        let mut actions = Vec::new();
 237        menus.into_iter().for_each(|menu| {
 238            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
 239                actions.push(dock_menu);
 240            }
 241        });
 242        self.inner.state.jump_list.borrow_mut().dock_menus = actions;
 243        update_jump_list(&self.inner.state.jump_list.borrow()).log_err();
 244    }
 245
 246    fn update_jump_list(
 247        &self,
 248        menus: Vec<MenuItem>,
 249        entries: Vec<SmallVec<[PathBuf; 2]>>,
 250    ) -> Vec<SmallVec<[PathBuf; 2]>> {
 251        let mut actions = Vec::new();
 252        menus.into_iter().for_each(|menu| {
 253            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
 254                actions.push(dock_menu);
 255            }
 256        });
 257        let mut jump_list = self.inner.state.jump_list.borrow_mut();
 258        jump_list.dock_menus = actions;
 259        jump_list.recent_workspaces = entries;
 260        update_jump_list(&jump_list).log_err().unwrap_or_default()
 261    }
 262
 263    fn find_current_active_window(&self) -> Option<HWND> {
 264        let active_window_hwnd = unsafe { GetActiveWindow() };
 265        if active_window_hwnd.is_invalid() {
 266            return None;
 267        }
 268        self.raw_window_handles
 269            .read()
 270            .iter()
 271            .find(|hwnd| hwnd.as_raw() == active_window_hwnd)
 272            .map(|hwnd| hwnd.as_raw())
 273    }
 274
 275    fn begin_vsync_thread(&self) {
 276        let Some(directx_devices) = self.inner.state.directx_devices.borrow().clone() else {
 277            return;
 278        };
 279        let Some(direct_write_text_system) = &self.direct_write_text_system else {
 280            return;
 281        };
 282        let mut directx_device = directx_devices;
 283        let platform_window: SafeHwnd = self.handle.into();
 284        let validation_number = self.inner.validation_number;
 285        let all_windows = Arc::downgrade(&self.raw_window_handles);
 286        let text_system = Arc::downgrade(direct_write_text_system);
 287        let invalidate_devices = self.invalidate_devices.clone();
 288
 289        std::thread::Builder::new()
 290            .name("VSyncProvider".to_owned())
 291            .spawn(move || {
 292                let vsync_provider = VSyncProvider::new();
 293                loop {
 294                    vsync_provider.wait_for_vsync();
 295                    if check_device_lost(&directx_device.device)
 296                        || invalidate_devices.fetch_and(false, Ordering::Acquire)
 297                    {
 298                        if let Err(err) = handle_gpu_device_lost(
 299                            &mut directx_device,
 300                            platform_window.as_raw(),
 301                            validation_number,
 302                            &all_windows,
 303                            &text_system,
 304                        ) {
 305                            panic!("Device lost: {err}");
 306                        }
 307                    }
 308                    let Some(all_windows) = all_windows.upgrade() else {
 309                        break;
 310                    };
 311                    for hwnd in all_windows.read().iter() {
 312                        unsafe {
 313                            let _ = RedrawWindow(Some(hwnd.as_raw()), None, None, RDW_INVALIDATE);
 314                        }
 315                    }
 316                }
 317            })
 318            .unwrap();
 319    }
 320}
 321
 322fn translate_accelerator(msg: &MSG) -> Option<()> {
 323    if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN {
 324        return None;
 325    }
 326
 327    let result = unsafe {
 328        SendMessageW(
 329            msg.hwnd,
 330            WM_GPUI_KEYDOWN,
 331            Some(msg.wParam),
 332            Some(msg.lParam),
 333        )
 334    };
 335    (result.0 == 0).then_some(())
 336}
 337
 338impl Platform for WindowsPlatform {
 339    fn background_executor(&self) -> BackgroundExecutor {
 340        self.background_executor.clone()
 341    }
 342
 343    fn foreground_executor(&self) -> ForegroundExecutor {
 344        self.foreground_executor.clone()
 345    }
 346
 347    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
 348        self.text_system.clone()
 349    }
 350
 351    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
 352        Box::new(
 353            WindowsKeyboardLayout::new()
 354                .log_err()
 355                .unwrap_or(WindowsKeyboardLayout::unknown()),
 356        )
 357    }
 358
 359    fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
 360        Rc::new(WindowsKeyboardMapper::new())
 361    }
 362
 363    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
 364        self.inner
 365            .state
 366            .callbacks
 367            .keyboard_layout_change
 368            .set(Some(callback));
 369    }
 370
 371    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
 372        on_finish_launching();
 373        if !self.headless {
 374            self.begin_vsync_thread();
 375        }
 376
 377        let mut msg = MSG::default();
 378        unsafe {
 379            while GetMessageW(&mut msg, None, 0, 0).as_bool() {
 380                if translate_accelerator(&msg).is_none() {
 381                    _ = TranslateMessage(&msg);
 382                    DispatchMessageW(&msg);
 383                }
 384            }
 385        }
 386
 387        self.inner
 388            .with_callback(|callbacks| &callbacks.quit, |callback| callback());
 389    }
 390
 391    fn quit(&self) {
 392        self.foreground_executor()
 393            .spawn(async { unsafe { PostQuitMessage(0) } })
 394            .detach();
 395    }
 396
 397    fn restart(&self, binary_path: Option<PathBuf>) {
 398        let pid = std::process::id();
 399        let Some(app_path) = binary_path.or(self.app_path().log_err()) else {
 400            return;
 401        };
 402        let script = format!(
 403            r#"
 404            $pidToWaitFor = {}
 405            $exePath = "{}"
 406
 407            while ($true) {{
 408                $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue
 409                if (-not $process) {{
 410                    Start-Process -FilePath $exePath
 411                    break
 412                }}
 413                Start-Sleep -Seconds 0.1
 414            }}
 415            "#,
 416            pid,
 417            app_path.display(),
 418        );
 419
 420        #[allow(
 421            clippy::disallowed_methods,
 422            reason = "We are restarting ourselves, using std command thus is fine"
 423        )] // todo(shell): There might be no powershell on the system
 424        let restart_process =
 425            util::command::new_std_command(util::shell::get_windows_system_shell())
 426                .arg("-command")
 427                .arg(script)
 428                .spawn();
 429
 430        match restart_process {
 431            Ok(_) => self.quit(),
 432            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
 433        }
 434    }
 435
 436    fn activate(&self, _ignoring_other_apps: bool) {}
 437
 438    fn hide(&self) {}
 439
 440    // todo(windows)
 441    fn hide_other_apps(&self) {
 442        unimplemented!()
 443    }
 444
 445    // todo(windows)
 446    fn unhide_other_apps(&self) {
 447        unimplemented!()
 448    }
 449
 450    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
 451        WindowsDisplay::displays()
 452    }
 453
 454    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
 455        WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
 456    }
 457
 458    #[cfg(feature = "screen-capture")]
 459    fn is_screen_capture_supported(&self) -> bool {
 460        true
 461    }
 462
 463    #[cfg(feature = "screen-capture")]
 464    fn screen_capture_sources(
 465        &self,
 466    ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
 467        crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
 468    }
 469
 470    fn active_window(&self) -> Option<AnyWindowHandle> {
 471        let active_window_hwnd = unsafe { GetActiveWindow() };
 472        self.window_from_hwnd(active_window_hwnd)
 473            .map(|inner| inner.handle)
 474    }
 475
 476    fn open_window(
 477        &self,
 478        handle: AnyWindowHandle,
 479        options: WindowParams,
 480    ) -> Result<Box<dyn PlatformWindow>> {
 481        let window = WindowsWindow::new(handle, options, self.generate_creation_info())?;
 482        let handle = window.get_raw_handle();
 483        self.raw_window_handles.write().push(handle.into());
 484
 485        Ok(Box::new(window))
 486    }
 487
 488    fn window_appearance(&self) -> WindowAppearance {
 489        system_appearance().log_err().unwrap_or_default()
 490    }
 491
 492    fn open_url(&self, url: &str) {
 493        if url.is_empty() {
 494            return;
 495        }
 496        let url_string = url.to_string();
 497        self.background_executor()
 498            .spawn(async move {
 499                open_target(&url_string)
 500                    .with_context(|| format!("Opening url: {}", url_string))
 501                    .log_err();
 502            })
 503            .detach();
 504    }
 505
 506    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
 507        self.inner.state.callbacks.open_urls.set(Some(callback));
 508    }
 509
 510    fn prompt_for_paths(
 511        &self,
 512        options: PathPromptOptions,
 513    ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
 514        let (tx, rx) = oneshot::channel();
 515        let window = self.find_current_active_window();
 516        self.foreground_executor()
 517            .spawn(async move {
 518                let _ = tx.send(file_open_dialog(options, window));
 519            })
 520            .detach();
 521
 522        rx
 523    }
 524
 525    fn prompt_for_new_path(
 526        &self,
 527        directory: &Path,
 528        suggested_name: Option<&str>,
 529    ) -> Receiver<Result<Option<PathBuf>>> {
 530        let directory = directory.to_owned();
 531        let suggested_name = suggested_name.map(|s| s.to_owned());
 532        let (tx, rx) = oneshot::channel();
 533        let window = self.find_current_active_window();
 534        self.foreground_executor()
 535            .spawn(async move {
 536                let _ = tx.send(file_save_dialog(directory, suggested_name, window));
 537            })
 538            .detach();
 539
 540        rx
 541    }
 542
 543    fn can_select_mixed_files_and_dirs(&self) -> bool {
 544        // The FOS_PICKFOLDERS flag toggles between "only files" and "only folders".
 545        false
 546    }
 547
 548    fn reveal_path(&self, path: &Path) {
 549        if path.as_os_str().is_empty() {
 550            return;
 551        }
 552        let path = path.to_path_buf();
 553        self.background_executor()
 554            .spawn(async move {
 555                open_target_in_explorer(&path)
 556                    .with_context(|| format!("Revealing path {} in explorer", path.display()))
 557                    .log_err();
 558            })
 559            .detach();
 560    }
 561
 562    fn open_with_system(&self, path: &Path) {
 563        if path.as_os_str().is_empty() {
 564            return;
 565        }
 566        let path = path.to_path_buf();
 567        self.background_executor()
 568            .spawn(async move {
 569                open_target(&path)
 570                    .with_context(|| format!("Opening {} with system", path.display()))
 571                    .log_err();
 572            })
 573            .detach();
 574    }
 575
 576    fn on_quit(&self, callback: Box<dyn FnMut()>) {
 577        self.inner.state.callbacks.quit.set(Some(callback));
 578    }
 579
 580    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
 581        self.inner.state.callbacks.reopen.set(Some(callback));
 582    }
 583
 584    fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
 585        *self.inner.state.menus.borrow_mut() = menus.into_iter().map(|menu| menu.owned()).collect();
 586    }
 587
 588    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
 589        Some(self.inner.state.menus.borrow().clone())
 590    }
 591
 592    fn set_dock_menu(&self, menus: Vec<MenuItem>, _keymap: &Keymap) {
 593        self.set_dock_menus(menus);
 594    }
 595
 596    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
 597        self.inner
 598            .state
 599            .callbacks
 600            .app_menu_action
 601            .set(Some(callback));
 602    }
 603
 604    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
 605        self.inner
 606            .state
 607            .callbacks
 608            .will_open_app_menu
 609            .set(Some(callback));
 610    }
 611
 612    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
 613        self.inner
 614            .state
 615            .callbacks
 616            .validate_app_menu_command
 617            .set(Some(callback));
 618    }
 619
 620    fn app_path(&self) -> Result<PathBuf> {
 621        Ok(std::env::current_exe()?)
 622    }
 623
 624    // todo(windows)
 625    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
 626        anyhow::bail!("not yet implemented");
 627    }
 628
 629    fn set_cursor_style(&self, style: CursorStyle) {
 630        let hcursor = load_cursor(style);
 631        if self.inner.state.current_cursor.get().map(|c| c.0) != hcursor.map(|c| c.0) {
 632            self.post_message(
 633                WM_GPUI_CURSOR_STYLE_CHANGED,
 634                WPARAM(0),
 635                LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
 636            );
 637            self.inner.state.current_cursor.set(hcursor);
 638        }
 639    }
 640
 641    fn should_auto_hide_scrollbars(&self) -> bool {
 642        should_auto_hide_scrollbars().log_err().unwrap_or(false)
 643    }
 644
 645    fn write_to_clipboard(&self, item: ClipboardItem) {
 646        write_to_clipboard(item);
 647    }
 648
 649    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
 650        read_from_clipboard()
 651    }
 652
 653    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
 654        let mut password = password.to_vec();
 655        let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
 656        let mut target_name = windows_credentials_target_name(url)
 657            .encode_utf16()
 658            .chain(Some(0))
 659            .collect_vec();
 660        self.foreground_executor().spawn(async move {
 661            let credentials = CREDENTIALW {
 662                LastWritten: unsafe { GetSystemTimeAsFileTime() },
 663                Flags: CRED_FLAGS(0),
 664                Type: CRED_TYPE_GENERIC,
 665                TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
 666                CredentialBlobSize: password.len() as u32,
 667                CredentialBlob: password.as_ptr() as *mut _,
 668                Persist: CRED_PERSIST_LOCAL_MACHINE,
 669                UserName: PWSTR::from_raw(username.as_mut_ptr()),
 670                ..CREDENTIALW::default()
 671            };
 672            unsafe {
 673                CredWriteW(&credentials, 0).map_err(|err| {
 674                    anyhow!(
 675                        "Failed to write credentials to Windows Credential Manager: {}",
 676                        err,
 677                    )
 678                })?;
 679            }
 680            Ok(())
 681        })
 682    }
 683
 684    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
 685        let mut target_name = windows_credentials_target_name(url)
 686            .encode_utf16()
 687            .chain(Some(0))
 688            .collect_vec();
 689        self.foreground_executor().spawn(async move {
 690            let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
 691            let result = unsafe {
 692                CredReadW(
 693                    PCWSTR::from_raw(target_name.as_ptr()),
 694                    CRED_TYPE_GENERIC,
 695                    None,
 696                    &mut credentials,
 697                )
 698            };
 699
 700            if let Err(err) = result {
 701                // ERROR_NOT_FOUND means the credential doesn't exist.
 702                // Return Ok(None) to match macOS and Linux behavior.
 703                if err.code() == ERROR_NOT_FOUND.to_hresult() {
 704                    return Ok(None);
 705                }
 706                return Err(err.into());
 707            }
 708
 709            if credentials.is_null() {
 710                Ok(None)
 711            } else {
 712                let username: String = unsafe { (*credentials).UserName.to_string()? };
 713                let credential_blob = unsafe {
 714                    std::slice::from_raw_parts(
 715                        (*credentials).CredentialBlob,
 716                        (*credentials).CredentialBlobSize as usize,
 717                    )
 718                };
 719                let password = credential_blob.to_vec();
 720                unsafe { CredFree(credentials as *const _ as _) };
 721                Ok(Some((username, password)))
 722            }
 723        })
 724    }
 725
 726    fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
 727        let mut target_name = windows_credentials_target_name(url)
 728            .encode_utf16()
 729            .chain(Some(0))
 730            .collect_vec();
 731        self.foreground_executor().spawn(async move {
 732            unsafe {
 733                CredDeleteW(
 734                    PCWSTR::from_raw(target_name.as_ptr()),
 735                    CRED_TYPE_GENERIC,
 736                    None,
 737                )?
 738            };
 739            Ok(())
 740        })
 741    }
 742
 743    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
 744        Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
 745    }
 746
 747    fn perform_dock_menu_action(&self, action: usize) {
 748        unsafe {
 749            PostMessageW(
 750                Some(self.handle),
 751                WM_GPUI_DOCK_MENU_ACTION,
 752                WPARAM(self.inner.validation_number),
 753                LPARAM(action as isize),
 754            )
 755            .log_err();
 756        }
 757    }
 758
 759    fn update_jump_list(
 760        &self,
 761        menus: Vec<MenuItem>,
 762        entries: Vec<SmallVec<[PathBuf; 2]>>,
 763    ) -> Vec<SmallVec<[PathBuf; 2]>> {
 764        self.update_jump_list(menus, entries)
 765    }
 766}
 767
 768impl WindowsPlatformInner {
 769    fn new(context: &mut PlatformWindowCreateContext) -> Result<Rc<Self>> {
 770        let state = WindowsPlatformState::new(context.directx_devices.take());
 771        Ok(Rc::new(Self {
 772            state,
 773            raw_window_handles: context.raw_window_handles.clone(),
 774            dispatcher: context
 775                .dispatcher
 776                .as_ref()
 777                .context("missing dispatcher")?
 778                .clone(),
 779            validation_number: context.validation_number,
 780            main_receiver: context
 781                .main_receiver
 782                .take()
 783                .context("missing main receiver")?,
 784        }))
 785    }
 786
 787    /// Calls `project` to project to the corresponding callback field, removes it from callbacks, calls `f` with the callback and then puts the callback back.
 788    fn with_callback<T>(
 789        &self,
 790        project: impl Fn(&PlatformCallbacks) -> &Cell<Option<T>>,
 791        f: impl FnOnce(&mut T),
 792    ) {
 793        let callback = project(&self.state.callbacks).take();
 794        if let Some(mut callback) = callback {
 795            f(&mut callback);
 796            project(&self.state.callbacks).set(Some(callback));
 797        }
 798    }
 799
 800    fn handle_msg(
 801        self: &Rc<Self>,
 802        handle: HWND,
 803        msg: u32,
 804        wparam: WPARAM,
 805        lparam: LPARAM,
 806    ) -> LRESULT {
 807        let handled = match msg {
 808            WM_GPUI_CLOSE_ONE_WINDOW
 809            | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD
 810            | WM_GPUI_DOCK_MENU_ACTION
 811            | WM_GPUI_KEYBOARD_LAYOUT_CHANGED
 812            | WM_GPUI_GPU_DEVICE_LOST => self.handle_gpui_events(msg, wparam, lparam),
 813            _ => None,
 814        };
 815        if let Some(result) = handled {
 816            LRESULT(result)
 817        } else {
 818            unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
 819        }
 820    }
 821
 822    fn handle_gpui_events(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 823        if wparam.0 != self.validation_number {
 824            log::error!("Wrong validation number while processing message: {message}");
 825            return None;
 826        }
 827        match message {
 828            WM_GPUI_CLOSE_ONE_WINDOW => {
 829                self.close_one_window(HWND(lparam.0 as _));
 830                Some(0)
 831            }
 832            WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(),
 833            WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _),
 834            WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_keyboard_layout_change(),
 835            WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
 836            _ => unreachable!(),
 837        }
 838    }
 839
 840    fn close_one_window(&self, target_window: HWND) -> bool {
 841        let Some(all_windows) = self.raw_window_handles.upgrade() else {
 842            log::error!("Failed to upgrade raw window handles");
 843            return false;
 844        };
 845        let mut lock = all_windows.write();
 846        let index = lock
 847            .iter()
 848            .position(|handle| handle.as_raw() == target_window)
 849            .unwrap();
 850        lock.remove(index);
 851
 852        lock.is_empty()
 853    }
 854
 855    #[inline]
 856    fn run_foreground_task(&self) -> Option<isize> {
 857        const MAIN_TASK_TIMEOUT: u128 = 10;
 858
 859        let start = std::time::Instant::now();
 860        'tasks: loop {
 861            'timeout_loop: loop {
 862                if start.elapsed().as_millis() >= MAIN_TASK_TIMEOUT {
 863                    log::debug!("foreground task timeout reached");
 864                    // we spent our budget on gpui tasks, we likely have a lot of work queued so drain system events first to stay responsive
 865                    // then quit out of foreground work to allow us to process other gpui events first before returning back to foreground task work
 866                    // if we don't we might not for example process window quit events
 867                    let mut msg = MSG::default();
 868                    let process_message = |msg: &_| {
 869                        if translate_accelerator(msg).is_none() {
 870                            _ = unsafe { TranslateMessage(msg) };
 871                            unsafe { DispatchMessageW(msg) };
 872                        }
 873                    };
 874                    let peek_msg = |msg: &mut _, msg_kind| unsafe {
 875                        PeekMessageW(msg, None, 0, 0, PM_REMOVE | msg_kind).as_bool()
 876                    };
 877                    // We need to process a paint message here as otherwise we will re-enter `run_foreground_task` before painting if we have work remaining.
 878                    // The reason for this is that windows prefers custom application message processing over system messages.
 879                    if peek_msg(&mut msg, PM_QS_PAINT) {
 880                        process_message(&msg);
 881                    }
 882                    while peek_msg(&mut msg, PM_QS_INPUT) {
 883                        process_message(&msg);
 884                    }
 885                    // Allow the main loop to process other gpui events before going back into `run_foreground_task`
 886                    unsafe {
 887                        if let Err(_) = PostMessageW(
 888                            Some(self.dispatcher.platform_window_handle.as_raw()),
 889                            WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
 890                            WPARAM(self.validation_number),
 891                            LPARAM(0),
 892                        ) {
 893                            self.dispatcher.wake_posted.store(false, Ordering::Release);
 894                        };
 895                    }
 896                    break 'tasks;
 897                }
 898                let mut main_receiver = self.main_receiver.clone();
 899                match main_receiver.try_pop() {
 900                    Ok(Some(runnable)) => WindowsDispatcher::execute_runnable(runnable),
 901                    _ => break 'timeout_loop,
 902                }
 903            }
 904
 905            // Someone could enqueue a Runnable here. The flag is still true, so they will not PostMessage.
 906            // We need to check for those Runnables after we clear the flag.
 907            self.dispatcher.wake_posted.store(false, Ordering::Release);
 908            let mut main_receiver = self.main_receiver.clone();
 909            match main_receiver.try_pop() {
 910                Ok(Some(runnable)) => {
 911                    self.dispatcher.wake_posted.store(true, Ordering::Release);
 912
 913                    WindowsDispatcher::execute_runnable(runnable);
 914                }
 915                _ => break 'tasks,
 916            }
 917        }
 918
 919        Some(0)
 920    }
 921
 922    fn handle_dock_action_event(&self, action_idx: usize) -> Option<isize> {
 923        let Some(action) = self
 924            .state
 925            .jump_list
 926            .borrow()
 927            .dock_menus
 928            .get(action_idx)
 929            .map(|dock_menu| dock_menu.action.boxed_clone())
 930        else {
 931            log::error!("Dock menu for index {action_idx} not found");
 932            return Some(1);
 933        };
 934        self.with_callback(
 935            |callbacks| &callbacks.app_menu_action,
 936            |callback| callback(&*action),
 937        );
 938        Some(0)
 939    }
 940
 941    fn handle_keyboard_layout_change(&self) -> Option<isize> {
 942        self.with_callback(
 943            |callbacks| &callbacks.keyboard_layout_change,
 944            |callback| callback(),
 945        );
 946        Some(0)
 947    }
 948
 949    fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
 950        let directx_devices = lparam.0 as *const DirectXDevices;
 951        let directx_devices = unsafe { &*directx_devices };
 952        self.state.directx_devices.borrow_mut().take();
 953        *self.state.directx_devices.borrow_mut() = Some(directx_devices.clone());
 954
 955        Some(0)
 956    }
 957}
 958
 959impl Drop for WindowsPlatform {
 960    fn drop(&mut self) {
 961        unsafe {
 962            DestroyWindow(self.handle)
 963                .context("Destroying platform window")
 964                .log_err();
 965            OleUninitialize();
 966        }
 967    }
 968}
 969
 970pub(crate) struct WindowCreationInfo {
 971    pub(crate) icon: HICON,
 972    pub(crate) executor: ForegroundExecutor,
 973    pub(crate) current_cursor: Option<HCURSOR>,
 974    pub(crate) windows_version: WindowsVersion,
 975    pub(crate) drop_target_helper: IDropTargetHelper,
 976    pub(crate) validation_number: usize,
 977    pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
 978    pub(crate) platform_window_handle: HWND,
 979    pub(crate) disable_direct_composition: bool,
 980    pub(crate) directx_devices: DirectXDevices,
 981    /// Flag to instruct the `VSyncProvider` thread to invalidate the directx devices
 982    /// as resizing them has failed, causing us to have lost at least the render target.
 983    pub(crate) invalidate_devices: Arc<AtomicBool>,
 984}
 985
 986struct PlatformWindowCreateContext {
 987    inner: Option<Result<Rc<WindowsPlatformInner>>>,
 988    raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
 989    validation_number: usize,
 990    main_sender: Option<PriorityQueueSender<RunnableVariant>>,
 991    main_receiver: Option<PriorityQueueReceiver<RunnableVariant>>,
 992    directx_devices: Option<DirectXDevices>,
 993    dispatcher: Option<Arc<WindowsDispatcher>>,
 994}
 995
 996fn open_target(target: impl AsRef<OsStr>) -> Result<()> {
 997    let target = target.as_ref();
 998    let ret = unsafe {
 999        ShellExecuteW(
1000            None,
1001            windows::core::w!("open"),
1002            &HSTRING::from(target),
1003            None,
1004            None,
1005            SW_SHOWDEFAULT,
1006        )
1007    };
1008    if ret.0 as isize <= 32 {
1009        Err(anyhow::anyhow!(
1010            "Unable to open target: {}",
1011            std::io::Error::last_os_error()
1012        ))
1013    } else {
1014        Ok(())
1015    }
1016}
1017
1018fn open_target_in_explorer(target: &Path) -> Result<()> {
1019    let dir = target.parent().context("No parent folder found")?;
1020    let desktop = unsafe { SHGetDesktopFolder()? };
1021
1022    let mut dir_item = std::ptr::null_mut();
1023    unsafe {
1024        desktop.ParseDisplayName(
1025            HWND::default(),
1026            None,
1027            &HSTRING::from(dir),
1028            None,
1029            &mut dir_item,
1030            std::ptr::null_mut(),
1031        )?;
1032    }
1033
1034    let mut file_item = std::ptr::null_mut();
1035    unsafe {
1036        desktop.ParseDisplayName(
1037            HWND::default(),
1038            None,
1039            &HSTRING::from(target),
1040            None,
1041            &mut file_item,
1042            std::ptr::null_mut(),
1043        )?;
1044    }
1045
1046    let highlight = [file_item as *const _];
1047    unsafe { SHOpenFolderAndSelectItems(dir_item as _, Some(&highlight), 0) }.or_else(|err| {
1048        if err.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
1049            // On some systems, the above call mysteriously fails with "file not
1050            // found" even though the file is there.  In these cases, ShellExecute()
1051            // seems to work as a fallback (although it won't select the file).
1052            open_target(dir).context("Opening target parent folder")
1053        } else {
1054            Err(anyhow::anyhow!("Can not open target path: {}", err))
1055        }
1056    })
1057}
1058
1059fn file_open_dialog(
1060    options: PathPromptOptions,
1061    window: Option<HWND>,
1062) -> Result<Option<Vec<PathBuf>>> {
1063    let folder_dialog: IFileOpenDialog =
1064        unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? };
1065
1066    let mut dialog_options = FOS_FILEMUSTEXIST;
1067    if options.multiple {
1068        dialog_options |= FOS_ALLOWMULTISELECT;
1069    }
1070    if options.directories {
1071        dialog_options |= FOS_PICKFOLDERS;
1072    }
1073
1074    unsafe {
1075        folder_dialog.SetOptions(dialog_options)?;
1076
1077        if let Some(prompt) = options.prompt {
1078            let prompt: &str = &prompt;
1079            folder_dialog.SetOkButtonLabel(&HSTRING::from(prompt))?;
1080        }
1081
1082        if folder_dialog.Show(window).is_err() {
1083            // User cancelled
1084            return Ok(None);
1085        }
1086    }
1087
1088    let results = unsafe { folder_dialog.GetResults()? };
1089    let file_count = unsafe { results.GetCount()? };
1090    if file_count == 0 {
1091        return Ok(None);
1092    }
1093
1094    let mut paths = Vec::with_capacity(file_count as usize);
1095    for i in 0..file_count {
1096        let item = unsafe { results.GetItemAt(i)? };
1097        let path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
1098        paths.push(PathBuf::from(path));
1099    }
1100
1101    Ok(Some(paths))
1102}
1103
1104fn file_save_dialog(
1105    directory: PathBuf,
1106    suggested_name: Option<String>,
1107    window: Option<HWND>,
1108) -> Result<Option<PathBuf>> {
1109    let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
1110    if !directory.to_string_lossy().is_empty()
1111        && let Some(full_path) = directory
1112            .canonicalize()
1113            .context("failed to canonicalize directory")
1114            .log_err()
1115    {
1116        let full_path = SanitizedPath::new(&full_path);
1117        let full_path_string = full_path.to_string();
1118        let path_item: IShellItem =
1119            unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? };
1120        unsafe {
1121            dialog
1122                .SetFolder(&path_item)
1123                .context("failed to set dialog folder")
1124                .log_err()
1125        };
1126    }
1127
1128    if let Some(suggested_name) = suggested_name {
1129        unsafe {
1130            dialog
1131                .SetFileName(&HSTRING::from(suggested_name))
1132                .context("failed to set file name")
1133                .log_err()
1134        };
1135    }
1136
1137    unsafe {
1138        dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
1139            pszName: windows::core::w!("All files"),
1140            pszSpec: windows::core::w!("*.*"),
1141        }])?;
1142        if dialog.Show(window).is_err() {
1143            // User cancelled
1144            return Ok(None);
1145        }
1146    }
1147    let shell_item = unsafe { dialog.GetResult()? };
1148    let file_path_string = unsafe {
1149        let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?;
1150        let string = pwstr.to_string()?;
1151        CoTaskMemFree(Some(pwstr.0 as _));
1152        string
1153    };
1154    Ok(Some(PathBuf::from(file_path_string)))
1155}
1156
1157fn load_icon() -> Result<HICON> {
1158    let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
1159    let handle = unsafe {
1160        LoadImageW(
1161            Some(module.into()),
1162            windows::core::PCWSTR(1 as _),
1163            IMAGE_ICON,
1164            0,
1165            0,
1166            LR_DEFAULTSIZE | LR_SHARED,
1167        )
1168        .context("unable to load icon file")?
1169    };
1170    Ok(HICON(handle.0))
1171}
1172
1173#[inline]
1174fn should_auto_hide_scrollbars() -> Result<bool> {
1175    let ui_settings = UISettings::new()?;
1176    Ok(ui_settings.AutoHideScrollBars()?)
1177}
1178
1179fn check_device_lost(device: &ID3D11Device) -> bool {
1180    let device_state = unsafe { device.GetDeviceRemovedReason() };
1181    match device_state {
1182        Ok(_) => false,
1183        Err(err) => {
1184            log::error!("DirectX device lost detected: {:?}", err);
1185            true
1186        }
1187    }
1188}
1189
1190fn handle_gpu_device_lost(
1191    directx_devices: &mut DirectXDevices,
1192    platform_window: HWND,
1193    validation_number: usize,
1194    all_windows: &std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
1195    text_system: &std::sync::Weak<DirectWriteTextSystem>,
1196) -> Result<()> {
1197    // Here we wait a bit to ensure the system has time to recover from the device lost state.
1198    // If we don't wait, the final drawing result will be blank.
1199    std::thread::sleep(std::time::Duration::from_millis(350));
1200
1201    *directx_devices = try_to_recover_from_device_lost(|| {
1202        DirectXDevices::new().context("Failed to recreate new DirectX devices after device lost")
1203    })?;
1204    log::info!("DirectX devices successfully recreated.");
1205
1206    let lparam = LPARAM(directx_devices as *const _ as _);
1207    unsafe {
1208        SendMessageW(
1209            platform_window,
1210            WM_GPUI_GPU_DEVICE_LOST,
1211            Some(WPARAM(validation_number)),
1212            Some(lparam),
1213        );
1214    }
1215
1216    if let Some(text_system) = text_system.upgrade() {
1217        text_system.handle_gpu_lost(&directx_devices)?;
1218    }
1219    if let Some(all_windows) = all_windows.upgrade() {
1220        for window in all_windows.read().iter() {
1221            unsafe {
1222                SendMessageW(
1223                    window.as_raw(),
1224                    WM_GPUI_GPU_DEVICE_LOST,
1225                    Some(WPARAM(validation_number)),
1226                    Some(lparam),
1227                );
1228            }
1229        }
1230        std::thread::sleep(std::time::Duration::from_millis(200));
1231        for window in all_windows.read().iter() {
1232            unsafe {
1233                SendMessageW(
1234                    window.as_raw(),
1235                    WM_GPUI_FORCE_UPDATE_WINDOW,
1236                    Some(WPARAM(validation_number)),
1237                    None,
1238                );
1239            }
1240        }
1241    }
1242    Ok(())
1243}
1244
1245const PLATFORM_WINDOW_CLASS_NAME: PCWSTR = w!("Zed::PlatformWindow");
1246
1247fn register_platform_window_class() {
1248    let wc = WNDCLASSW {
1249        lpfnWndProc: Some(window_procedure),
1250        lpszClassName: PCWSTR(PLATFORM_WINDOW_CLASS_NAME.as_ptr()),
1251        ..Default::default()
1252    };
1253    unsafe { RegisterClassW(&wc) };
1254}
1255
1256unsafe extern "system" fn window_procedure(
1257    hwnd: HWND,
1258    msg: u32,
1259    wparam: WPARAM,
1260    lparam: LPARAM,
1261) -> LRESULT {
1262    if msg == WM_NCCREATE {
1263        let params = unsafe { &*(lparam.0 as *const CREATESTRUCTW) };
1264        let creation_context = params.lpCreateParams as *mut PlatformWindowCreateContext;
1265        let creation_context = unsafe { &mut *creation_context };
1266
1267        let Some(main_sender) = creation_context.main_sender.take() else {
1268            creation_context.inner = Some(Err(anyhow!("missing main sender")));
1269            return LRESULT(0);
1270        };
1271        creation_context.dispatcher = Some(Arc::new(WindowsDispatcher::new(
1272            main_sender,
1273            hwnd,
1274            creation_context.validation_number,
1275        )));
1276
1277        return match WindowsPlatformInner::new(creation_context) {
1278            Ok(inner) => {
1279                let weak = Box::new(Rc::downgrade(&inner));
1280                unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1281                creation_context.inner = Some(Ok(inner));
1282                unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1283            }
1284            Err(error) => {
1285                creation_context.inner = Some(Err(error));
1286                LRESULT(0)
1287            }
1288        };
1289    }
1290
1291    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsPlatformInner>;
1292    if ptr.is_null() {
1293        return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1294    }
1295    let inner = unsafe { &*ptr };
1296    let result = if let Some(inner) = inner.upgrade() {
1297        inner.handle_msg(hwnd, msg, wparam, lparam)
1298    } else {
1299        unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1300    };
1301
1302    if msg == WM_NCDESTROY {
1303        unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1304        unsafe { drop(Box::from_raw(ptr)) };
1305    }
1306
1307    result
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312    use crate::{ClipboardItem, read_from_clipboard, write_to_clipboard};
1313
1314    #[test]
1315    fn test_clipboard() {
1316        let item = ClipboardItem::new_string("你好,我是张小白".to_string());
1317        write_to_clipboard(item.clone());
1318        assert_eq!(read_from_clipboard(), Some(item));
1319
1320        let item = ClipboardItem::new_string("12345".to_string());
1321        write_to_clipboard(item.clone());
1322        assert_eq!(read_from_clipboard(), Some(item));
1323
1324        let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]);
1325        write_to_clipboard(item.clone());
1326        assert_eq!(read_from_clipboard(), Some(item));
1327    }
1328}