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