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