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