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