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