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