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