platform.rs

  1// todo(windows): remove
  2#![allow(unused_variables)]
  3
  4use std::{
  5    cell::{Cell, RefCell},
  6    ffi::{c_uint, c_void, OsString},
  7    iter::once,
  8    mem::transmute,
  9    os::windows::ffi::{OsStrExt, OsStringExt},
 10    path::{Path, PathBuf},
 11    rc::Rc,
 12    sync::{Arc, OnceLock},
 13};
 14
 15use ::util::{ResultExt, SemanticVersion};
 16use anyhow::{anyhow, Context, Result};
 17use async_task::Runnable;
 18use copypasta::{ClipboardContext, ClipboardProvider};
 19use futures::channel::oneshot::{self, Receiver};
 20use itertools::Itertools;
 21use parking_lot::{Mutex, RwLock};
 22use smallvec::SmallVec;
 23use time::UtcOffset;
 24use windows::{
 25    core::*,
 26    Wdk::System::SystemServices::*,
 27    Win32::{
 28        Foundation::*,
 29        Graphics::Gdi::*,
 30        Media::*,
 31        Security::Credentials::*,
 32        Storage::FileSystem::*,
 33        System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
 34        UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
 35    },
 36};
 37
 38use crate::*;
 39
 40pub(crate) struct WindowsPlatform {
 41    inner: Rc<WindowsPlatformInner>,
 42}
 43
 44/// Windows settings pulled from SystemParametersInfo
 45/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
 46#[derive(Default, Debug)]
 47pub(crate) struct WindowsPlatformSystemSettings {
 48    /// SEE: SPI_GETWHEELSCROLLCHARS
 49    pub(crate) wheel_scroll_chars: u32,
 50
 51    /// SEE: SPI_GETWHEELSCROLLLINES
 52    pub(crate) wheel_scroll_lines: u32,
 53}
 54
 55pub(crate) struct WindowsPlatformInner {
 56    background_executor: BackgroundExecutor,
 57    pub(crate) foreground_executor: ForegroundExecutor,
 58    main_receiver: flume::Receiver<Runnable>,
 59    text_system: Arc<WindowsTextSystem>,
 60    callbacks: Mutex<Callbacks>,
 61    pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
 62    pub(crate) dispatch_event: OwnedHandle,
 63    pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
 64    pub icon: HICON,
 65    // NOTE: standard cursor handles don't need to close.
 66    pub(crate) current_cursor: Cell<HCURSOR>,
 67}
 68
 69impl WindowsPlatformInner {
 70    pub(crate) fn try_get_windows_inner_from_hwnd(
 71        &self,
 72        hwnd: HWND,
 73    ) -> Option<Rc<WindowsWindowInner>> {
 74        self.raw_window_handles
 75            .read()
 76            .iter()
 77            .find(|entry| *entry == &hwnd)
 78            .and_then(|hwnd| try_get_window_inner(*hwnd))
 79    }
 80}
 81
 82#[derive(Default)]
 83struct Callbacks {
 84    open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
 85    become_active: Option<Box<dyn FnMut()>>,
 86    resign_active: Option<Box<dyn FnMut()>>,
 87    quit: Option<Box<dyn FnMut()>>,
 88    reopen: Option<Box<dyn FnMut()>>,
 89    event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
 90    app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
 91    will_open_app_menu: Option<Box<dyn FnMut()>>,
 92    validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
 93}
 94
 95enum WindowsMessageWaitResult {
 96    ForegroundExecution,
 97    WindowsMessage(MSG),
 98    Error,
 99}
100
101impl WindowsPlatformSystemSettings {
102    fn new() -> Self {
103        let mut settings = Self::default();
104        settings.update_all();
105        settings
106    }
107
108    pub(crate) fn update_all(&mut self) {
109        self.update_wheel_scroll_lines();
110        self.update_wheel_scroll_chars();
111    }
112
113    pub(crate) fn update_wheel_scroll_lines(&mut self) {
114        let mut value = c_uint::default();
115        let result = unsafe {
116            SystemParametersInfoW(
117                SPI_GETWHEELSCROLLLINES,
118                0,
119                Some((&mut value) as *mut c_uint as *mut c_void),
120                SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
121            )
122        };
123
124        if result.log_err() != None {
125            self.wheel_scroll_lines = value;
126        }
127    }
128
129    pub(crate) fn update_wheel_scroll_chars(&mut self) {
130        let mut value = c_uint::default();
131        let result = unsafe {
132            SystemParametersInfoW(
133                SPI_GETWHEELSCROLLCHARS,
134                0,
135                Some((&mut value) as *mut c_uint as *mut c_void),
136                SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
137            )
138        };
139
140        if result.log_err() != None {
141            self.wheel_scroll_chars = value;
142        }
143    }
144}
145
146impl WindowsPlatform {
147    pub(crate) fn new() -> Self {
148        unsafe {
149            OleInitialize(None).expect("unable to initialize Windows OLE");
150        }
151        let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
152        let dispatch_event =
153            OwnedHandle::new(unsafe { CreateEventW(None, false, false, None) }.unwrap());
154        let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, dispatch_event.to_raw()));
155        let background_executor = BackgroundExecutor::new(dispatcher.clone());
156        let foreground_executor = ForegroundExecutor::new(dispatcher);
157        let text_system = Arc::new(WindowsTextSystem::new());
158        let callbacks = Mutex::new(Callbacks::default());
159        let raw_window_handles = RwLock::new(SmallVec::new());
160        let settings = RefCell::new(WindowsPlatformSystemSettings::new());
161        let icon = load_icon().unwrap_or_default();
162        let current_cursor = Cell::new(load_cursor(CursorStyle::Arrow));
163        let inner = Rc::new(WindowsPlatformInner {
164            background_executor,
165            foreground_executor,
166            main_receiver,
167            text_system,
168            callbacks,
169            raw_window_handles,
170            dispatch_event,
171            settings,
172            icon,
173            current_cursor,
174        });
175        Self { inner }
176    }
177
178    fn run_foreground_tasks(&self) {
179        for runnable in self.inner.main_receiver.drain() {
180            runnable.run();
181        }
182    }
183
184    fn redraw_all(&self) {
185        for handle in self.inner.raw_window_handles.read().iter() {
186            unsafe {
187                RedrawWindow(
188                    *handle,
189                    None,
190                    HRGN::default(),
191                    RDW_INVALIDATE | RDW_UPDATENOW,
192                );
193            }
194        }
195    }
196}
197
198impl Platform for WindowsPlatform {
199    fn background_executor(&self) -> BackgroundExecutor {
200        self.inner.background_executor.clone()
201    }
202
203    fn foreground_executor(&self) -> ForegroundExecutor {
204        self.inner.foreground_executor.clone()
205    }
206
207    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
208        self.inner.text_system.clone()
209    }
210
211    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
212        on_finish_launching();
213        let dispatch_event = self.inner.dispatch_event.to_raw();
214        let vsync_event = create_event().unwrap();
215        let timer_stop_event = create_event().unwrap();
216        let raw_timer_stop_event = timer_stop_event.to_raw();
217        begin_vsync_timer(vsync_event.to_raw(), timer_stop_event);
218        'a: loop {
219            let wait_result = unsafe {
220                MsgWaitForMultipleObjects(
221                    Some(&[vsync_event.to_raw(), dispatch_event]),
222                    false,
223                    INFINITE,
224                    QS_ALLINPUT,
225                )
226            };
227
228            match wait_result {
229                // compositor clock ticked so we should draw a frame
230                WAIT_EVENT(0) => {
231                    self.redraw_all();
232                }
233                // foreground tasks are dispatched
234                WAIT_EVENT(1) => {
235                    self.run_foreground_tasks();
236                }
237                // Windows thread messages are posted
238                WAIT_EVENT(2) => {
239                    let mut msg = MSG::default();
240                    unsafe {
241                        while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() {
242                            if msg.message == WM_QUIT {
243                                break 'a;
244                            }
245                            if msg.message == WM_SETTINGCHANGE {
246                                self.inner.settings.borrow_mut().update_all();
247                                continue;
248                            }
249                            TranslateMessage(&msg);
250                            DispatchMessageW(&msg);
251                        }
252                    }
253
254                    // foreground tasks may have been queued in the message handlers
255                    self.run_foreground_tasks();
256                }
257                _ => {
258                    log::error!("Something went wrong while waiting {:?}", wait_result);
259                    break;
260                }
261            }
262        }
263        end_vsync_timer(raw_timer_stop_event);
264
265        let mut callbacks = self.inner.callbacks.lock();
266        if let Some(callback) = callbacks.quit.as_mut() {
267            callback()
268        }
269    }
270
271    fn quit(&self) {
272        self.foreground_executor()
273            .spawn(async { unsafe { PostQuitMessage(0) } })
274            .detach();
275    }
276
277    // todo(windows)
278    fn restart(&self) {
279        unimplemented!()
280    }
281
282    // todo(windows)
283    fn activate(&self, ignoring_other_apps: bool) {}
284
285    // todo(windows)
286    fn hide(&self) {
287        unimplemented!()
288    }
289
290    // todo(windows)
291    fn hide_other_apps(&self) {
292        unimplemented!()
293    }
294
295    // todo(windows)
296    fn unhide_other_apps(&self) {
297        unimplemented!()
298    }
299
300    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
301        WindowsDisplay::displays()
302    }
303
304    fn display(&self, id: crate::DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
305        if let Some(display) = WindowsDisplay::new(id) {
306            Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
307        } else {
308            None
309        }
310    }
311
312    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
313        if let Some(display) = WindowsDisplay::primary_monitor() {
314            Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
315        } else {
316            None
317        }
318    }
319
320    fn active_window(&self) -> Option<AnyWindowHandle> {
321        let active_window_hwnd = unsafe { GetActiveWindow() };
322        self.inner
323            .try_get_windows_inner_from_hwnd(active_window_hwnd)
324            .map(|inner| inner.handle)
325    }
326
327    fn open_window(
328        &self,
329        handle: AnyWindowHandle,
330        options: WindowParams,
331    ) -> Box<dyn PlatformWindow> {
332        Box::new(WindowsWindow::new(self.inner.clone(), handle, options))
333    }
334
335    // todo(windows)
336    fn window_appearance(&self) -> WindowAppearance {
337        WindowAppearance::Dark
338    }
339
340    fn open_url(&self, url: &str) {
341        let url_string = url.to_string();
342        self.background_executor()
343            .spawn(async move {
344                if url_string.is_empty() {
345                    return;
346                }
347                open_target(url_string.as_str());
348            })
349            .detach();
350    }
351
352    // todo(windows)
353    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
354        self.inner.callbacks.lock().open_urls = Some(callback);
355    }
356
357    fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
358        let (tx, rx) = oneshot::channel();
359
360        self.foreground_executor()
361            .spawn(async move {
362                let tx = Cell::new(Some(tx));
363
364                // create file open dialog
365                let folder_dialog: IFileOpenDialog = unsafe {
366                    CoCreateInstance::<std::option::Option<&IUnknown>, IFileOpenDialog>(
367                        &FileOpenDialog,
368                        None,
369                        CLSCTX_ALL,
370                    )
371                    .unwrap()
372                };
373
374                // dialog options
375                let mut dialog_options: FILEOPENDIALOGOPTIONS = FOS_FILEMUSTEXIST;
376                if options.multiple {
377                    dialog_options |= FOS_ALLOWMULTISELECT;
378                }
379                if options.directories {
380                    dialog_options |= FOS_PICKFOLDERS;
381                }
382
383                unsafe {
384                    folder_dialog.SetOptions(dialog_options).unwrap();
385                    folder_dialog
386                        .SetTitle(&HSTRING::from(OsString::from("Select a folder")))
387                        .unwrap();
388                }
389
390                let hr = unsafe { folder_dialog.Show(None) };
391
392                if hr.is_err() {
393                    if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
394                        // user canceled error
395                        if let Some(tx) = tx.take() {
396                            tx.send(None).unwrap();
397                        }
398                        return;
399                    }
400                }
401
402                let mut results = unsafe { folder_dialog.GetResults().unwrap() };
403
404                let mut paths: Vec<PathBuf> = Vec::new();
405                for i in 0..unsafe { results.GetCount().unwrap() } {
406                    let mut item: IShellItem = unsafe { results.GetItemAt(i).unwrap() };
407                    let mut path: PWSTR =
408                        unsafe { item.GetDisplayName(SIGDN_FILESYSPATH).unwrap() };
409                    let mut path_os_string = OsString::from_wide(unsafe { path.as_wide() });
410
411                    paths.push(PathBuf::from(path_os_string));
412                }
413
414                if let Some(tx) = tx.take() {
415                    if paths.len() == 0 {
416                        tx.send(None).unwrap();
417                    } else {
418                        tx.send(Some(paths)).unwrap();
419                    }
420                }
421            })
422            .detach();
423
424        rx
425    }
426
427    fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
428        let directory = directory.to_owned();
429        let (tx, rx) = oneshot::channel();
430        self.foreground_executor()
431            .spawn(async move {
432                unsafe {
433                    let Ok(dialog) = show_savefile_dialog(directory) else {
434                        let _ = tx.send(None);
435                        return;
436                    };
437                    let Ok(_) = dialog.Show(None) else {
438                        let _ = tx.send(None); // user cancel
439                        return;
440                    };
441                    if let Ok(shell_item) = dialog.GetResult() {
442                        if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
443                            let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap())));
444                            return;
445                        }
446                    }
447                    let _ = tx.send(None);
448                }
449            })
450            .detach();
451
452        rx
453    }
454
455    fn reveal_path(&self, path: &Path) {
456        let Ok(file_full_path) = path.canonicalize() else {
457            log::error!("unable to parse file path");
458            return;
459        };
460        self.background_executor()
461            .spawn(async move {
462                let Some(path) = file_full_path.to_str() else {
463                    return;
464                };
465                if path.is_empty() {
466                    return;
467                }
468                open_target(path);
469            })
470            .detach();
471    }
472
473    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
474        self.inner.callbacks.lock().become_active = Some(callback);
475    }
476
477    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
478        self.inner.callbacks.lock().resign_active = Some(callback);
479    }
480
481    fn on_quit(&self, callback: Box<dyn FnMut()>) {
482        self.inner.callbacks.lock().quit = Some(callback);
483    }
484
485    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
486        self.inner.callbacks.lock().reopen = Some(callback);
487    }
488
489    fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
490        self.inner.callbacks.lock().event = Some(callback);
491    }
492
493    // todo(windows)
494    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
495
496    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
497        self.inner.callbacks.lock().app_menu_action = Some(callback);
498    }
499
500    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
501        self.inner.callbacks.lock().will_open_app_menu = Some(callback);
502    }
503
504    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
505        self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
506    }
507
508    fn os_name(&self) -> &'static str {
509        "Windows"
510    }
511
512    fn os_version(&self) -> Result<SemanticVersion> {
513        let mut info = unsafe { std::mem::zeroed() };
514        let status = unsafe { RtlGetVersion(&mut info) };
515        if status.is_ok() {
516            Ok(SemanticVersion {
517                major: info.dwMajorVersion as _,
518                minor: info.dwMinorVersion as _,
519                patch: info.dwBuildNumber as _,
520            })
521        } else {
522            Err(anyhow::anyhow!(
523                "unable to get Windows version: {}",
524                std::io::Error::last_os_error()
525            ))
526        }
527    }
528
529    fn app_version(&self) -> Result<SemanticVersion> {
530        let mut file_name_buffer = vec![0u16; MAX_PATH as usize];
531        let file_name = {
532            let mut file_name_buffer_capacity = MAX_PATH as usize;
533            let mut file_name_length;
534            loop {
535                file_name_length =
536                    unsafe { GetModuleFileNameW(None, &mut file_name_buffer) } as usize;
537                if file_name_length < file_name_buffer_capacity {
538                    break;
539                }
540                // buffer too small
541                file_name_buffer_capacity *= 2;
542                file_name_buffer = vec![0u16; file_name_buffer_capacity];
543            }
544            PCWSTR::from_raw(file_name_buffer[0..(file_name_length + 1)].as_ptr())
545        };
546
547        let version_info_block = {
548            let mut version_handle = 0;
549            let version_info_size =
550                unsafe { GetFileVersionInfoSizeW(file_name, Some(&mut version_handle)) } as usize;
551            if version_info_size == 0 {
552                log::error!(
553                    "unable to get version info size: {}",
554                    std::io::Error::last_os_error()
555                );
556                return Err(anyhow!("unable to get version info size"));
557            }
558            let mut version_data = vec![0u8; version_info_size + 2];
559            unsafe {
560                GetFileVersionInfoW(
561                    file_name,
562                    version_handle,
563                    version_info_size as u32,
564                    version_data.as_mut_ptr() as _,
565                )
566            }
567            .inspect_err(|_| {
568                log::error!(
569                    "unable to retrieve version info: {}",
570                    std::io::Error::last_os_error()
571                )
572            })?;
573            version_data
574        };
575
576        let version_info_raw = {
577            let mut buffer = unsafe { std::mem::zeroed() };
578            let mut size = 0;
579            let entry = "\\".encode_utf16().chain(Some(0)).collect_vec();
580            if !unsafe {
581                VerQueryValueW(
582                    version_info_block.as_ptr() as _,
583                    PCWSTR::from_raw(entry.as_ptr()),
584                    &mut buffer,
585                    &mut size,
586                )
587            }
588            .as_bool()
589            {
590                log::error!(
591                    "unable to query version info data: {}",
592                    std::io::Error::last_os_error()
593                );
594                return Err(anyhow!("the specified resource is not valid"));
595            }
596            if size == 0 {
597                log::error!(
598                    "unable to query version info data: {}",
599                    std::io::Error::last_os_error()
600                );
601                return Err(anyhow!("no value is available for the specified name"));
602            }
603            buffer
604        };
605
606        let version_info = unsafe { &*(version_info_raw as *mut VS_FIXEDFILEINFO) };
607        // https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
608        if version_info.dwSignature == 0xFEEF04BD {
609            return Ok(SemanticVersion {
610                major: ((version_info.dwProductVersionMS >> 16) & 0xFFFF) as usize,
611                minor: (version_info.dwProductVersionMS & 0xFFFF) as usize,
612                patch: ((version_info.dwProductVersionLS >> 16) & 0xFFFF) as usize,
613            });
614        } else {
615            log::error!(
616                "no version info present: {}",
617                std::io::Error::last_os_error()
618            );
619            return Err(anyhow!("no version info present"));
620        }
621    }
622
623    // todo(windows)
624    fn app_path(&self) -> Result<PathBuf> {
625        Err(anyhow!("not yet implemented"))
626    }
627
628    fn local_timezone(&self) -> UtcOffset {
629        let mut info = unsafe { std::mem::zeroed() };
630        let ret = unsafe { GetTimeZoneInformation(&mut info) };
631        if ret == TIME_ZONE_ID_INVALID {
632            log::error!(
633                "Unable to get local timezone: {}",
634                std::io::Error::last_os_error()
635            );
636            return UtcOffset::UTC;
637        }
638        // Windows treat offset as:
639        // UTC = localtime + offset
640        // so we add a minus here
641        let hours = -info.Bias / 60;
642        let minutes = -info.Bias % 60;
643
644        UtcOffset::from_hms(hours as _, minutes as _, 0).unwrap()
645    }
646
647    // todo(windows)
648    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
649        Err(anyhow!("not yet implemented"))
650    }
651
652    fn set_cursor_style(&self, style: CursorStyle) {
653        self.inner.current_cursor.set(load_cursor(style));
654    }
655
656    // todo(windows)
657    fn should_auto_hide_scrollbars(&self) -> bool {
658        false
659    }
660
661    fn write_to_clipboard(&self, item: ClipboardItem) {
662        let mut ctx = ClipboardContext::new().unwrap();
663        ctx.set_contents(item.text().to_owned()).unwrap();
664    }
665
666    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
667        let mut ctx = ClipboardContext::new().unwrap();
668        let content = ctx.get_contents().unwrap();
669        Some(ClipboardItem {
670            text: content,
671            metadata: None,
672        })
673    }
674
675    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
676        let mut password = password.to_vec();
677        let mut username = username.encode_utf16().chain(once(0)).collect_vec();
678        let mut target_name = windows_credentials_target_name(url)
679            .encode_utf16()
680            .chain(once(0))
681            .collect_vec();
682        self.foreground_executor().spawn(async move {
683            let credentials = CREDENTIALW {
684                LastWritten: unsafe { GetSystemTimeAsFileTime() },
685                Flags: CRED_FLAGS(0),
686                Type: CRED_TYPE_GENERIC,
687                TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
688                CredentialBlobSize: password.len() as u32,
689                CredentialBlob: password.as_ptr() as *mut _,
690                Persist: CRED_PERSIST_LOCAL_MACHINE,
691                UserName: PWSTR::from_raw(username.as_mut_ptr()),
692                ..CREDENTIALW::default()
693            };
694            unsafe { CredWriteW(&credentials, 0) }?;
695            Ok(())
696        })
697    }
698
699    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
700        let mut target_name = windows_credentials_target_name(url)
701            .encode_utf16()
702            .chain(once(0))
703            .collect_vec();
704        self.foreground_executor().spawn(async move {
705            let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
706            unsafe {
707                CredReadW(
708                    PCWSTR::from_raw(target_name.as_ptr()),
709                    CRED_TYPE_GENERIC,
710                    0,
711                    &mut credentials,
712                )?
713            };
714
715            if credentials.is_null() {
716                Ok(None)
717            } else {
718                let username: String = unsafe { (*credentials).UserName.to_string()? };
719                let credential_blob = unsafe {
720                    std::slice::from_raw_parts(
721                        (*credentials).CredentialBlob,
722                        (*credentials).CredentialBlobSize as usize,
723                    )
724                };
725                let mut password: Vec<u8> = Vec::with_capacity(credential_blob.len());
726                password.resize(password.capacity(), 0);
727                password.clone_from_slice(&credential_blob);
728                unsafe { CredFree(credentials as *const c_void) };
729                Ok(Some((username, password)))
730            }
731        })
732    }
733
734    fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
735        let mut target_name = windows_credentials_target_name(url)
736            .encode_utf16()
737            .chain(once(0))
738            .collect_vec();
739        self.foreground_executor().spawn(async move {
740            unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? };
741            Ok(())
742        })
743    }
744
745    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
746        Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
747    }
748}
749
750impl Drop for WindowsPlatform {
751    fn drop(&mut self) {
752        unsafe {
753            OleUninitialize();
754        }
755    }
756}
757
758fn open_target(target: &str) {
759    unsafe {
760        let ret = ShellExecuteW(
761            None,
762            windows::core::w!("open"),
763            &HSTRING::from(target),
764            None,
765            None,
766            SW_SHOWDEFAULT,
767        );
768        if ret.0 <= 32 {
769            log::error!("Unable to open target: {}", std::io::Error::last_os_error());
770        }
771    }
772}
773
774unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
775    let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
776    let bind_context = CreateBindCtx(0)?;
777    let Ok(full_path) = directory.canonicalize() else {
778        return Ok(dialog);
779    };
780    let dir_str = full_path.into_os_string();
781    if dir_str.is_empty() {
782        return Ok(dialog);
783    }
784    let dir_vec = dir_str.encode_wide().collect_vec();
785    let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
786        .inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
787    if ret.is_ok() {
788        let dir_shell_item: IShellItem = ret.unwrap();
789        let _ = dialog
790            .SetFolder(&dir_shell_item)
791            .inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
792    }
793
794    Ok(dialog)
795}
796
797fn begin_vsync_timer(vsync_event: HANDLE, timer_stop_event: OwnedHandle) {
798    let vsync_fn = select_vsync_fn();
799    std::thread::spawn(move || {
800        while vsync_fn(timer_stop_event.to_raw()) {
801            if unsafe { SetEvent(vsync_event) }.log_err().is_none() {
802                break;
803            }
804        }
805    });
806}
807
808fn end_vsync_timer(timer_stop_event: HANDLE) {
809    unsafe { SetEvent(timer_stop_event) }.log_err();
810}
811
812fn select_vsync_fn() -> Box<dyn Fn(HANDLE) -> bool + Send> {
813    if let Some(dcomp_fn) = load_dcomp_vsync_fn() {
814        log::info!("use DCompositionWaitForCompositorClock for vsync");
815        return Box::new(move |timer_stop_event| {
816            // will be 0 if woken up by timer_stop_event or 1 if the compositor clock ticked
817            // SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock
818            (unsafe { dcomp_fn(1, &timer_stop_event, INFINITE) }) == 1
819        });
820    }
821    log::info!("use fallback vsync function");
822    Box::new(fallback_vsync_fn())
823}
824
825fn load_dcomp_vsync_fn() -> Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32> {
826    static FN: OnceLock<Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32>> =
827        OnceLock::new();
828    *FN.get_or_init(|| {
829        let hmodule = unsafe { LoadLibraryW(windows::core::w!("dcomp.dll")) }.ok()?;
830        let address = unsafe {
831            GetProcAddress(
832                hmodule,
833                windows::core::s!("DCompositionWaitForCompositorClock"),
834            )
835        }?;
836        Some(unsafe { transmute(address) })
837    })
838}
839
840fn fallback_vsync_fn() -> impl Fn(HANDLE) -> bool + Send {
841    let freq = WindowsDisplay::primary_monitor()
842        .and_then(|monitor| monitor.frequency())
843        .unwrap_or(60);
844    log::info!("primaly refresh rate is {freq}Hz");
845
846    let interval = (1000 / freq).max(1);
847    log::info!("expected interval is {interval}ms");
848
849    unsafe { timeBeginPeriod(1) };
850
851    struct TimePeriod;
852    impl Drop for TimePeriod {
853        fn drop(&mut self) {
854            unsafe { timeEndPeriod(1) };
855        }
856    }
857    let period = TimePeriod;
858
859    move |timer_stop_event| {
860        let _ = (&period,);
861        (unsafe { WaitForSingleObject(timer_stop_event, interval) }) == WAIT_TIMEOUT
862    }
863}
864
865fn load_icon() -> Result<HICON> {
866    let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
867    let handle = unsafe {
868        LoadImageW(
869            module,
870            IDI_APPLICATION,
871            IMAGE_ICON,
872            0,
873            0,
874            LR_DEFAULTSIZE | LR_SHARED,
875        )
876        .context("unable to load icon file")?
877    };
878    Ok(HICON(handle.0))
879}