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 _ => {
252 log::error!("Something went wrong while waiting {:?}", wait_result);
253 break;
254 }
255 }
256 }
257 end_vsync_timer(timer_stop_event);
258 unsafe { CloseHandle(dispatch_event) }.log_err();
259
260 let mut callbacks = self.inner.callbacks.lock();
261 if let Some(callback) = callbacks.quit.as_mut() {
262 callback()
263 }
264 }
265
266 fn quit(&self) {
267 self.foreground_executor()
268 .spawn(async { unsafe { PostQuitMessage(0) } })
269 .detach();
270 }
271
272 // todo(windows)
273 fn restart(&self) {
274 unimplemented!()
275 }
276
277 // todo(windows)
278 fn activate(&self, ignoring_other_apps: bool) {}
279
280 // todo(windows)
281 fn hide(&self) {
282 unimplemented!()
283 }
284
285 // todo(windows)
286 fn hide_other_apps(&self) {
287 unimplemented!()
288 }
289
290 // todo(windows)
291 fn unhide_other_apps(&self) {
292 unimplemented!()
293 }
294
295 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
296 WindowsDisplay::displays()
297 }
298
299 fn display(&self, id: crate::DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
300 if let Some(display) = WindowsDisplay::new(id) {
301 Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
302 } else {
303 None
304 }
305 }
306
307 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
308 if let Some(display) = WindowsDisplay::primary_monitor() {
309 Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
310 } else {
311 None
312 }
313 }
314
315 fn active_window(&self) -> Option<AnyWindowHandle> {
316 let active_window_hwnd = unsafe { GetActiveWindow() };
317 self.inner
318 .try_get_windows_inner_from_hwnd(active_window_hwnd)
319 .map(|inner| inner.handle)
320 }
321
322 fn open_window(
323 &self,
324 handle: AnyWindowHandle,
325 options: WindowParams,
326 ) -> Box<dyn PlatformWindow> {
327 Box::new(WindowsWindow::new(self.inner.clone(), handle, options))
328 }
329
330 // todo(windows)
331 fn window_appearance(&self) -> WindowAppearance {
332 WindowAppearance::Dark
333 }
334
335 fn open_url(&self, url: &str) {
336 let url_string = url.to_string();
337 self.background_executor()
338 .spawn(async move {
339 if url_string.is_empty() {
340 return;
341 }
342 open_target(url_string.as_str());
343 })
344 .detach();
345 }
346
347 // todo(windows)
348 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
349 self.inner.callbacks.lock().open_urls = Some(callback);
350 }
351
352 fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
353 let (tx, rx) = oneshot::channel();
354
355 self.foreground_executor()
356 .spawn(async move {
357 let tx = Cell::new(Some(tx));
358
359 // create file open dialog
360 let folder_dialog: IFileOpenDialog = unsafe {
361 CoCreateInstance::<std::option::Option<&IUnknown>, IFileOpenDialog>(
362 &FileOpenDialog,
363 None,
364 CLSCTX_ALL,
365 )
366 .unwrap()
367 };
368
369 // dialog options
370 let mut dialog_options: FILEOPENDIALOGOPTIONS = FOS_FILEMUSTEXIST;
371 if options.multiple {
372 dialog_options |= FOS_ALLOWMULTISELECT;
373 }
374 if options.directories {
375 dialog_options |= FOS_PICKFOLDERS;
376 }
377
378 unsafe {
379 folder_dialog.SetOptions(dialog_options).unwrap();
380 folder_dialog
381 .SetTitle(&HSTRING::from(OsString::from("Select a folder")))
382 .unwrap();
383 }
384
385 let hr = unsafe { folder_dialog.Show(None) };
386
387 if hr.is_err() {
388 if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
389 // user canceled error
390 if let Some(tx) = tx.take() {
391 tx.send(None).unwrap();
392 }
393 return;
394 }
395 }
396
397 let mut results = unsafe { folder_dialog.GetResults().unwrap() };
398
399 let mut paths: Vec<PathBuf> = Vec::new();
400 for i in 0..unsafe { results.GetCount().unwrap() } {
401 let mut item: IShellItem = unsafe { results.GetItemAt(i).unwrap() };
402 let mut path: PWSTR =
403 unsafe { item.GetDisplayName(SIGDN_FILESYSPATH).unwrap() };
404 let mut path_os_string = OsString::from_wide(unsafe { path.as_wide() });
405
406 paths.push(PathBuf::from(path_os_string));
407 }
408
409 if let Some(tx) = tx.take() {
410 if paths.len() == 0 {
411 tx.send(None).unwrap();
412 } else {
413 tx.send(Some(paths)).unwrap();
414 }
415 }
416 })
417 .detach();
418
419 rx
420 }
421
422 fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
423 let directory = directory.to_owned();
424 let (tx, rx) = oneshot::channel();
425 self.foreground_executor()
426 .spawn(async move {
427 unsafe {
428 let Ok(dialog) = show_savefile_dialog(directory) else {
429 let _ = tx.send(None);
430 return;
431 };
432 let Ok(_) = dialog.Show(None) else {
433 let _ = tx.send(None); // user cancel
434 return;
435 };
436 if let Ok(shell_item) = dialog.GetResult() {
437 if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
438 let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap())));
439 return;
440 }
441 }
442 let _ = tx.send(None);
443 }
444 })
445 .detach();
446
447 rx
448 }
449
450 fn reveal_path(&self, path: &Path) {
451 let Ok(file_full_path) = path.canonicalize() else {
452 log::error!("unable to parse file path");
453 return;
454 };
455 self.background_executor()
456 .spawn(async move {
457 let Some(path) = file_full_path.to_str() else {
458 return;
459 };
460 if path.is_empty() {
461 return;
462 }
463 open_target(path);
464 })
465 .detach();
466 }
467
468 fn on_become_active(&self, callback: Box<dyn FnMut()>) {
469 self.inner.callbacks.lock().become_active = Some(callback);
470 }
471
472 fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
473 self.inner.callbacks.lock().resign_active = Some(callback);
474 }
475
476 fn on_quit(&self, callback: Box<dyn FnMut()>) {
477 self.inner.callbacks.lock().quit = Some(callback);
478 }
479
480 fn on_reopen(&self, callback: Box<dyn FnMut()>) {
481 self.inner.callbacks.lock().reopen = Some(callback);
482 }
483
484 fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
485 self.inner.callbacks.lock().event = Some(callback);
486 }
487
488 // todo(windows)
489 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
490
491 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
492 self.inner.callbacks.lock().app_menu_action = Some(callback);
493 }
494
495 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
496 self.inner.callbacks.lock().will_open_app_menu = Some(callback);
497 }
498
499 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
500 self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
501 }
502
503 fn os_name(&self) -> &'static str {
504 "Windows"
505 }
506
507 fn os_version(&self) -> Result<SemanticVersion> {
508 let mut info = unsafe { std::mem::zeroed() };
509 let status = unsafe { RtlGetVersion(&mut info) };
510 if status.is_ok() {
511 Ok(SemanticVersion {
512 major: info.dwMajorVersion as _,
513 minor: info.dwMinorVersion as _,
514 patch: info.dwBuildNumber as _,
515 })
516 } else {
517 Err(anyhow::anyhow!(
518 "unable to get Windows version: {}",
519 std::io::Error::last_os_error()
520 ))
521 }
522 }
523
524 fn app_version(&self) -> Result<SemanticVersion> {
525 Ok(SemanticVersion {
526 major: 1,
527 minor: 0,
528 patch: 0,
529 })
530 }
531
532 // todo(windows)
533 fn app_path(&self) -> Result<PathBuf> {
534 Err(anyhow!("not yet implemented"))
535 }
536
537 fn local_timezone(&self) -> UtcOffset {
538 let mut info = unsafe { std::mem::zeroed() };
539 let ret = unsafe { GetTimeZoneInformation(&mut info) };
540 if ret == TIME_ZONE_ID_INVALID {
541 log::error!(
542 "Unable to get local timezone: {}",
543 std::io::Error::last_os_error()
544 );
545 return UtcOffset::UTC;
546 }
547 // Windows treat offset as:
548 // UTC = localtime + offset
549 // so we add a minus here
550 let hours = -info.Bias / 60;
551 let minutes = -info.Bias % 60;
552
553 UtcOffset::from_hms(hours as _, minutes as _, 0).unwrap()
554 }
555
556 fn double_click_interval(&self) -> Duration {
557 let millis = unsafe { GetDoubleClickTime() };
558 Duration::from_millis(millis as _)
559 }
560
561 // todo(windows)
562 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
563 Err(anyhow!("not yet implemented"))
564 }
565
566 fn set_cursor_style(&self, style: CursorStyle) {
567 let handle = match style {
568 CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => unsafe {
569 load_cursor(IDC_IBEAM)
570 },
571 CursorStyle::Crosshair => unsafe { load_cursor(IDC_CROSS) },
572 CursorStyle::PointingHand | CursorStyle::DragLink => unsafe { load_cursor(IDC_HAND) },
573 CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => unsafe {
574 load_cursor(IDC_SIZEWE)
575 },
576 CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => unsafe {
577 load_cursor(IDC_SIZENS)
578 },
579 CursorStyle::OperationNotAllowed => unsafe { load_cursor(IDC_NO) },
580 _ => unsafe { load_cursor(IDC_ARROW) },
581 };
582 if handle.is_err() {
583 log::error!(
584 "Error loading cursor image: {}",
585 std::io::Error::last_os_error()
586 );
587 return;
588 }
589 let _ = unsafe { SetCursor(HCURSOR(handle.unwrap().0)) };
590 }
591
592 // todo(windows)
593 fn should_auto_hide_scrollbars(&self) -> bool {
594 false
595 }
596
597 fn write_to_clipboard(&self, item: ClipboardItem) {
598 let mut ctx = ClipboardContext::new().unwrap();
599 ctx.set_contents(item.text().to_owned()).unwrap();
600 }
601
602 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
603 let mut ctx = ClipboardContext::new().unwrap();
604 let content = ctx.get_contents().unwrap();
605 Some(ClipboardItem {
606 text: content,
607 metadata: None,
608 })
609 }
610
611 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
612 let mut password = password.to_vec();
613 let mut username = username.encode_utf16().chain(once(0)).collect_vec();
614 let mut target_name = windows_credentials_target_name(url)
615 .encode_utf16()
616 .chain(once(0))
617 .collect_vec();
618 self.foreground_executor().spawn(async move {
619 let credentials = CREDENTIALW {
620 LastWritten: unsafe { GetSystemTimeAsFileTime() },
621 Flags: CRED_FLAGS(0),
622 Type: CRED_TYPE_GENERIC,
623 TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
624 CredentialBlobSize: password.len() as u32,
625 CredentialBlob: password.as_ptr() as *mut _,
626 Persist: CRED_PERSIST_LOCAL_MACHINE,
627 UserName: PWSTR::from_raw(username.as_mut_ptr()),
628 ..CREDENTIALW::default()
629 };
630 unsafe { CredWriteW(&credentials, 0) }?;
631 Ok(())
632 })
633 }
634
635 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
636 let mut target_name = windows_credentials_target_name(url)
637 .encode_utf16()
638 .chain(once(0))
639 .collect_vec();
640 self.foreground_executor().spawn(async move {
641 let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
642 unsafe {
643 CredReadW(
644 PCWSTR::from_raw(target_name.as_ptr()),
645 CRED_TYPE_GENERIC,
646 0,
647 &mut credentials,
648 )?
649 };
650
651 if credentials.is_null() {
652 Ok(None)
653 } else {
654 let username: String = unsafe { (*credentials).UserName.to_string()? };
655 let credential_blob = unsafe {
656 std::slice::from_raw_parts(
657 (*credentials).CredentialBlob,
658 (*credentials).CredentialBlobSize as usize,
659 )
660 };
661 let mut password: Vec<u8> = Vec::with_capacity(credential_blob.len());
662 password.resize(password.capacity(), 0);
663 password.clone_from_slice(&credential_blob);
664 unsafe { CredFree(credentials as *const c_void) };
665 Ok(Some((username, password)))
666 }
667 })
668 }
669
670 fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
671 let mut target_name = windows_credentials_target_name(url)
672 .encode_utf16()
673 .chain(once(0))
674 .collect_vec();
675 self.foreground_executor().spawn(async move {
676 unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? };
677 Ok(())
678 })
679 }
680
681 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
682 Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
683 }
684}
685
686impl Drop for WindowsPlatform {
687 fn drop(&mut self) {
688 unsafe {
689 OleUninitialize();
690 }
691 }
692}
693
694unsafe fn load_cursor(name: PCWSTR) -> Result<HANDLE> {
695 LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED).map_err(|e| anyhow!(e))
696}
697
698fn open_target(target: &str) {
699 unsafe {
700 let ret = ShellExecuteW(
701 None,
702 windows::core::w!("open"),
703 &HSTRING::from(target),
704 None,
705 None,
706 SW_SHOWDEFAULT,
707 );
708 if ret.0 <= 32 {
709 log::error!("Unable to open target: {}", std::io::Error::last_os_error());
710 }
711 }
712}
713
714unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
715 let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
716 let bind_context = CreateBindCtx(0)?;
717 let Ok(full_path) = directory.canonicalize() else {
718 return Ok(dialog);
719 };
720 let dir_str = full_path.into_os_string();
721 if dir_str.is_empty() {
722 return Ok(dialog);
723 }
724 let dir_vec = dir_str.encode_wide().collect_vec();
725 let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
726 .inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
727 if ret.is_ok() {
728 let dir_shell_item: IShellItem = ret.unwrap();
729 let _ = dialog
730 .SetFolder(&dir_shell_item)
731 .inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
732 }
733
734 Ok(dialog)
735}
736
737fn begin_vsync_timer(vsync_event: HANDLE, timer_stop_event: HANDLE) {
738 let vsync_fn = select_vsync_fn();
739 std::thread::spawn(move || {
740 while vsync_fn(timer_stop_event) {
741 if unsafe { SetEvent(vsync_event) }.log_err().is_none() {
742 break;
743 }
744 }
745 unsafe { CloseHandle(timer_stop_event) }.log_err();
746 });
747}
748
749fn end_vsync_timer(timer_stop_event: HANDLE) {
750 unsafe { SetEvent(timer_stop_event) }.log_err();
751}
752
753fn select_vsync_fn() -> Box<dyn Fn(HANDLE) -> bool + Send> {
754 if let Some(dcomp_fn) = load_dcomp_vsync_fn() {
755 log::info!("use DCompositionWaitForCompositorClock for vsync");
756 return Box::new(move |timer_stop_event| {
757 // will be 0 if woken up by timer_stop_event or 1 if the compositor clock ticked
758 // SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock
759 (unsafe { dcomp_fn(1, &timer_stop_event, INFINITE) }) == 1
760 });
761 }
762 log::info!("use fallback vsync function");
763 Box::new(fallback_vsync_fn())
764}
765
766fn load_dcomp_vsync_fn() -> Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32> {
767 static FN: OnceLock<Option<unsafe extern "system" fn(u32, *const HANDLE, u32) -> u32>> =
768 OnceLock::new();
769 *FN.get_or_init(|| {
770 let hmodule = unsafe { LoadLibraryW(windows::core::w!("dcomp.dll")) }.ok()?;
771 let address = unsafe {
772 GetProcAddress(
773 hmodule,
774 windows::core::s!("DCompositionWaitForCompositorClock"),
775 )
776 }?;
777 Some(unsafe { transmute(address) })
778 })
779}
780
781fn fallback_vsync_fn() -> impl Fn(HANDLE) -> bool + Send {
782 let freq = WindowsDisplay::primary_monitor()
783 .and_then(|monitor| monitor.frequency())
784 .unwrap_or(60);
785 log::info!("primaly refresh rate is {freq}Hz");
786
787 let interval = (1000 / freq).max(1);
788 log::info!("expected interval is {interval}ms");
789
790 unsafe { timeBeginPeriod(1) };
791
792 struct TimePeriod;
793 impl Drop for TimePeriod {
794 fn drop(&mut self) {
795 unsafe { timeEndPeriod(1) };
796 }
797 }
798 let period = TimePeriod;
799
800 move |timer_stop_event| {
801 let _ = (&period,);
802 (unsafe { WaitForSingleObject(timer_stop_event, interval) }) == WAIT_TIMEOUT
803 }
804}