platform.rs

  1use std::{
  2    cell::RefCell,
  3    mem::ManuallyDrop,
  4    path::{Path, PathBuf},
  5    rc::Rc,
  6    sync::Arc,
  7};
  8
  9use ::util::{ResultExt, paths::SanitizedPath};
 10use anyhow::{Context as _, Result, anyhow};
 11use async_task::Runnable;
 12use futures::channel::oneshot::{self, Receiver};
 13use itertools::Itertools;
 14use parking_lot::RwLock;
 15use smallvec::SmallVec;
 16use windows::{
 17    UI::ViewManagement::UISettings,
 18    Win32::{
 19        Foundation::*,
 20        Graphics::{
 21            Gdi::*,
 22            Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
 23        },
 24        Security::Credentials::*,
 25        System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*},
 26        UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
 27    },
 28    core::*,
 29};
 30
 31use crate::*;
 32
 33pub(crate) struct WindowsPlatform {
 34    state: RefCell<WindowsPlatformState>,
 35    raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
 36    // The below members will never change throughout the entire lifecycle of the app.
 37    // gpu_context: BladeContext,
 38    directx_devices: DirectXDevices,
 39    icon: HICON,
 40    main_receiver: flume::Receiver<Runnable>,
 41    background_executor: BackgroundExecutor,
 42    foreground_executor: ForegroundExecutor,
 43    text_system: Arc<DirectWriteTextSystem>,
 44    windows_version: WindowsVersion,
 45    bitmap_factory: ManuallyDrop<IWICImagingFactory>,
 46    drop_target_helper: IDropTargetHelper,
 47    validation_number: usize,
 48    main_thread_id_win32: u32,
 49}
 50
 51pub(crate) struct WindowsPlatformState {
 52    callbacks: PlatformCallbacks,
 53    menus: Vec<OwnedMenu>,
 54    jump_list: JumpList,
 55    // NOTE: standard cursor handles don't need to close.
 56    pub(crate) current_cursor: Option<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    keyboard_layout_change: Option<Box<dyn FnMut()>>,
 68}
 69
 70impl WindowsPlatformState {
 71    fn new() -> Self {
 72        let callbacks = PlatformCallbacks::default();
 73        let jump_list = JumpList::new();
 74        let current_cursor = load_cursor(CursorStyle::Arrow);
 75
 76        Self {
 77            callbacks,
 78            jump_list,
 79            current_cursor,
 80            menus: Vec::new(),
 81        }
 82    }
 83}
 84
 85impl WindowsPlatform {
 86    pub(crate) fn new() -> Result<Self> {
 87        unsafe {
 88            OleInitialize(None).context("unable to initialize Windows OLE")?;
 89        }
 90        let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
 91        let main_thread_id_win32 = unsafe { GetCurrentThreadId() };
 92        let validation_number = rand::random::<usize>();
 93        let dispatcher = Arc::new(WindowsDispatcher::new(
 94            main_sender,
 95            main_thread_id_win32,
 96            validation_number,
 97        ));
 98        let background_executor = BackgroundExecutor::new(dispatcher.clone());
 99        let foreground_executor = ForegroundExecutor::new(dispatcher);
100        let bitmap_factory = ManuallyDrop::new(unsafe {
101            CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
102                .context("Error creating bitmap factory.")?
103        });
104        let text_system = Arc::new(
105            DirectWriteTextSystem::new(&bitmap_factory)
106                .context("Error creating DirectWriteTextSystem")?,
107        );
108        let drop_target_helper: IDropTargetHelper = unsafe {
109            CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
110                .context("Error creating drop target helper.")?
111        };
112        let icon = load_icon().unwrap_or_default();
113        let state = RefCell::new(WindowsPlatformState::new());
114        let raw_window_handles = RwLock::new(SmallVec::new());
115        // let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
116        let directx_devices = DirectXDevices::new().context("Unable to init directx devices.")?;
117        let windows_version = WindowsVersion::new().context("Error retrieve windows version")?;
118
119        Ok(Self {
120            state,
121            raw_window_handles,
122            directx_devices,
123            icon,
124            main_receiver,
125            background_executor,
126            foreground_executor,
127            text_system,
128            windows_version,
129            bitmap_factory,
130            drop_target_helper,
131            validation_number,
132            main_thread_id_win32,
133        })
134    }
135
136    fn redraw_all(&self) {
137        for handle in self.raw_window_handles.read().iter() {
138            unsafe {
139                RedrawWindow(Some(*handle), None, None, RDW_INVALIDATE | RDW_UPDATENOW)
140                    .ok()
141                    .log_err();
142            }
143        }
144    }
145
146    pub fn try_get_windows_inner_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
147        self.raw_window_handles
148            .read()
149            .iter()
150            .find(|entry| *entry == &hwnd)
151            .and_then(|hwnd| try_get_window_inner(*hwnd))
152    }
153
154    #[inline]
155    fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {
156        self.raw_window_handles
157            .read()
158            .iter()
159            .for_each(|handle| unsafe {
160                PostMessageW(Some(*handle), message, wparam, lparam).log_err();
161            });
162    }
163
164    fn close_one_window(&self, target_window: HWND) -> bool {
165        let mut lock = self.raw_window_handles.write();
166        let index = lock
167            .iter()
168            .position(|handle| *handle == target_window)
169            .unwrap();
170        lock.remove(index);
171
172        lock.is_empty()
173    }
174
175    #[inline]
176    fn run_foreground_task(&self) {
177        for runnable in self.main_receiver.drain() {
178            runnable.run();
179        }
180    }
181
182    fn generate_creation_info(&self) -> WindowCreationInfo {
183        WindowCreationInfo {
184            icon: self.icon,
185            executor: self.foreground_executor.clone(),
186            current_cursor: self.state.borrow().current_cursor,
187            windows_version: self.windows_version,
188            drop_target_helper: self.drop_target_helper.clone(),
189            validation_number: self.validation_number,
190            main_receiver: self.main_receiver.clone(),
191            main_thread_id_win32: self.main_thread_id_win32,
192        }
193    }
194
195    fn handle_dock_action_event(&self, action_idx: usize) {
196        let mut lock = self.state.borrow_mut();
197        if let Some(mut callback) = lock.callbacks.app_menu_action.take() {
198            let Some(action) = lock
199                .jump_list
200                .dock_menus
201                .get(action_idx)
202                .map(|dock_menu| dock_menu.action.boxed_clone())
203            else {
204                lock.callbacks.app_menu_action = Some(callback);
205                log::error!("Dock menu for index {action_idx} not found");
206                return;
207            };
208            drop(lock);
209            callback(&*action);
210            self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
211        }
212    }
213
214    fn handle_input_lang_change(&self) {
215        let mut lock = self.state.borrow_mut();
216        if let Some(mut callback) = lock.callbacks.keyboard_layout_change.take() {
217            drop(lock);
218            callback();
219            self.state
220                .borrow_mut()
221                .callbacks
222                .keyboard_layout_change
223                .get_or_insert(callback);
224        }
225    }
226
227    // Returns true if the app should quit.
228    fn handle_events(&self) -> bool {
229        let mut msg = MSG::default();
230        unsafe {
231            while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
232                match msg.message {
233                    WM_QUIT => return true,
234                    WM_INPUTLANGCHANGE
235                    | WM_GPUI_CLOSE_ONE_WINDOW
236                    | WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD
237                    | WM_GPUI_DOCK_MENU_ACTION => {
238                        if self.handle_gpui_evnets(msg.message, msg.wParam, msg.lParam, &msg) {
239                            return true;
240                        }
241                    }
242                    _ => {
243                        DispatchMessageW(&msg);
244                    }
245                }
246            }
247        }
248        false
249    }
250
251    // Returns true if the app should quit.
252    fn handle_gpui_evnets(
253        &self,
254        message: u32,
255        wparam: WPARAM,
256        lparam: LPARAM,
257        msg: *const MSG,
258    ) -> bool {
259        if wparam.0 != self.validation_number {
260            unsafe { DispatchMessageW(msg) };
261            return false;
262        }
263        match message {
264            WM_GPUI_CLOSE_ONE_WINDOW => {
265                if self.close_one_window(HWND(lparam.0 as _)) {
266                    return true;
267                }
268            }
269            WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(),
270            WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _),
271            WM_INPUTLANGCHANGE => self.handle_input_lang_change(),
272            _ => unreachable!(),
273        }
274        false
275    }
276
277    fn set_dock_menus(&self, menus: Vec<MenuItem>) {
278        let mut actions = Vec::new();
279        menus.into_iter().for_each(|menu| {
280            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
281                actions.push(dock_menu);
282            }
283        });
284        let mut lock = self.state.borrow_mut();
285        lock.jump_list.dock_menus = actions;
286        update_jump_list(&lock.jump_list).log_err();
287    }
288
289    fn update_jump_list(
290        &self,
291        menus: Vec<MenuItem>,
292        entries: Vec<SmallVec<[PathBuf; 2]>>,
293    ) -> Vec<SmallVec<[PathBuf; 2]>> {
294        let mut actions = Vec::new();
295        menus.into_iter().for_each(|menu| {
296            if let Some(dock_menu) = DockMenuItem::new(menu).log_err() {
297                actions.push(dock_menu);
298            }
299        });
300        let mut lock = self.state.borrow_mut();
301        lock.jump_list.dock_menus = actions;
302        lock.jump_list.recent_workspaces = entries;
303        update_jump_list(&lock.jump_list)
304            .log_err()
305            .unwrap_or_default()
306    }
307
308    fn find_current_active_window(&self) -> Option<HWND> {
309        let active_window_hwnd = unsafe { GetActiveWindow() };
310        if active_window_hwnd.is_invalid() {
311            return None;
312        }
313        self.raw_window_handles
314            .read()
315            .iter()
316            .find(|&&hwnd| hwnd == active_window_hwnd)
317            .copied()
318    }
319}
320
321impl Platform for WindowsPlatform {
322    fn background_executor(&self) -> BackgroundExecutor {
323        self.background_executor.clone()
324    }
325
326    fn foreground_executor(&self) -> ForegroundExecutor {
327        self.foreground_executor.clone()
328    }
329
330    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
331        self.text_system.clone()
332    }
333
334    fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
335        Box::new(
336            WindowsKeyboardLayout::new()
337                .log_err()
338                .unwrap_or(WindowsKeyboardLayout::unknown()),
339        )
340    }
341
342    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
343        self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
344    }
345
346    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
347        on_finish_launching();
348        let vsync_event = unsafe { Owned::new(CreateEventW(None, false, false, None).unwrap()) };
349        begin_vsync(*vsync_event);
350        'a: loop {
351            let wait_result = unsafe {
352                MsgWaitForMultipleObjects(Some(&[*vsync_event]), false, INFINITE, QS_ALLINPUT)
353            };
354
355            match wait_result {
356                // compositor clock ticked so we should draw a frame
357                WAIT_EVENT(0) => self.redraw_all(),
358                // Windows thread messages are posted
359                WAIT_EVENT(1) => {
360                    if self.handle_events() {
361                        break 'a;
362                    }
363                }
364                _ => {
365                    log::error!("Something went wrong while waiting {:?}", wait_result);
366                    break;
367                }
368            }
369        }
370
371        if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
372            callback();
373        }
374    }
375
376    fn quit(&self) {
377        self.foreground_executor()
378            .spawn(async { unsafe { PostQuitMessage(0) } })
379            .detach();
380    }
381
382    fn restart(&self, _: Option<PathBuf>) {
383        let pid = std::process::id();
384        let Some(app_path) = self.app_path().log_err() else {
385            return;
386        };
387        let script = format!(
388            r#"
389            $pidToWaitFor = {}
390            $exePath = "{}"
391
392            while ($true) {{
393                $process = Get-Process -Id $pidToWaitFor -ErrorAction SilentlyContinue
394                if (-not $process) {{
395                    Start-Process -FilePath $exePath
396                    break
397                }}
398                Start-Sleep -Seconds 0.1
399            }}
400            "#,
401            pid,
402            app_path.display(),
403        );
404        let restart_process = util::command::new_std_command("powershell.exe")
405            .arg("-command")
406            .arg(script)
407            .spawn();
408
409        match restart_process {
410            Ok(_) => self.quit(),
411            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
412        }
413    }
414
415    fn activate(&self, _ignoring_other_apps: bool) {}
416
417    fn hide(&self) {}
418
419    // todo(windows)
420    fn hide_other_apps(&self) {
421        unimplemented!()
422    }
423
424    // todo(windows)
425    fn unhide_other_apps(&self) {
426        unimplemented!()
427    }
428
429    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
430        WindowsDisplay::displays()
431    }
432
433    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
434        WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
435    }
436
437    #[cfg(feature = "screen-capture")]
438    fn is_screen_capture_supported(&self) -> bool {
439        true
440    }
441
442    #[cfg(feature = "screen-capture")]
443    fn screen_capture_sources(
444        &self,
445    ) -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {
446        crate::platform::scap_screen_capture::scap_screen_sources(&self.foreground_executor)
447    }
448
449    fn active_window(&self) -> Option<AnyWindowHandle> {
450        let active_window_hwnd = unsafe { GetActiveWindow() };
451        self.try_get_windows_inner_from_hwnd(active_window_hwnd)
452            .map(|inner| inner.handle)
453    }
454
455    fn open_window(
456        &self,
457        handle: AnyWindowHandle,
458        options: WindowParams,
459    ) -> Result<Box<dyn PlatformWindow>> {
460        let window = WindowsWindow::new(
461            handle,
462            options,
463            self.generate_creation_info(),
464            &self.directx_devices,
465        )
466        .inspect_err(|err| show_error("Failed to open new window", err.to_string()))?;
467        let handle = window.get_raw_handle();
468        self.raw_window_handles.write().push(handle);
469
470        Ok(Box::new(window))
471    }
472
473    fn window_appearance(&self) -> WindowAppearance {
474        system_appearance().log_err().unwrap_or_default()
475    }
476
477    fn open_url(&self, url: &str) {
478        let url_string = url.to_string();
479        self.background_executor()
480            .spawn(async move {
481                if url_string.is_empty() {
482                    return;
483                }
484                open_target(url_string.as_str());
485            })
486            .detach();
487    }
488
489    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
490        self.state.borrow_mut().callbacks.open_urls = Some(callback);
491    }
492
493    fn prompt_for_paths(
494        &self,
495        options: PathPromptOptions,
496    ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
497        let (tx, rx) = oneshot::channel();
498        let window = self.find_current_active_window();
499        self.foreground_executor()
500            .spawn(async move {
501                let _ = tx.send(file_open_dialog(options, window));
502            })
503            .detach();
504
505        rx
506    }
507
508    fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
509        let directory = directory.to_owned();
510        let (tx, rx) = oneshot::channel();
511        let window = self.find_current_active_window();
512        self.foreground_executor()
513            .spawn(async move {
514                let _ = tx.send(file_save_dialog(directory, window));
515            })
516            .detach();
517
518        rx
519    }
520
521    fn can_select_mixed_files_and_dirs(&self) -> bool {
522        // The FOS_PICKFOLDERS flag toggles between "only files" and "only folders".
523        false
524    }
525
526    fn reveal_path(&self, path: &Path) {
527        let Ok(file_full_path) = path.canonicalize() else {
528            log::error!("unable to parse file path");
529            return;
530        };
531        self.background_executor()
532            .spawn(async move {
533                let Some(path) = file_full_path.to_str() else {
534                    return;
535                };
536                if path.is_empty() {
537                    return;
538                }
539                open_target_in_explorer(path);
540            })
541            .detach();
542    }
543
544    fn open_with_system(&self, path: &Path) {
545        let Ok(full_path) = path.canonicalize() else {
546            log::error!("unable to parse file full path: {}", path.display());
547            return;
548        };
549        self.background_executor()
550            .spawn(async move {
551                let Some(full_path_str) = full_path.to_str() else {
552                    return;
553                };
554                if full_path_str.is_empty() {
555                    return;
556                };
557                open_target(full_path_str);
558            })
559            .detach();
560    }
561
562    fn on_quit(&self, callback: Box<dyn FnMut()>) {
563        self.state.borrow_mut().callbacks.quit = Some(callback);
564    }
565
566    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
567        self.state.borrow_mut().callbacks.reopen = Some(callback);
568    }
569
570    fn set_menus(&self, menus: Vec<Menu>, _keymap: &Keymap) {
571        self.state.borrow_mut().menus = menus.into_iter().map(|menu| menu.owned()).collect();
572    }
573
574    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
575        Some(self.state.borrow().menus.clone())
576    }
577
578    fn set_dock_menu(&self, menus: Vec<MenuItem>, _keymap: &Keymap) {
579        self.set_dock_menus(menus);
580    }
581
582    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
583        self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
584    }
585
586    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
587        self.state.borrow_mut().callbacks.will_open_app_menu = Some(callback);
588    }
589
590    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
591        self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback);
592    }
593
594    fn app_path(&self) -> Result<PathBuf> {
595        Ok(std::env::current_exe()?)
596    }
597
598    // todo(windows)
599    fn path_for_auxiliary_executable(&self, _name: &str) -> Result<PathBuf> {
600        anyhow::bail!("not yet implemented");
601    }
602
603    fn set_cursor_style(&self, style: CursorStyle) {
604        let hcursor = load_cursor(style);
605        let mut lock = self.state.borrow_mut();
606        if lock.current_cursor.map(|c| c.0) != hcursor.map(|c| c.0) {
607            self.post_message(
608                WM_GPUI_CURSOR_STYLE_CHANGED,
609                WPARAM(0),
610                LPARAM(hcursor.map_or(0, |c| c.0 as isize)),
611            );
612            lock.current_cursor = hcursor;
613        }
614    }
615
616    fn should_auto_hide_scrollbars(&self) -> bool {
617        should_auto_hide_scrollbars().log_err().unwrap_or(false)
618    }
619
620    fn write_to_clipboard(&self, item: ClipboardItem) {
621        write_to_clipboard(item);
622    }
623
624    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
625        read_from_clipboard()
626    }
627
628    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
629        let mut password = password.to_vec();
630        let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
631        let mut target_name = windows_credentials_target_name(url)
632            .encode_utf16()
633            .chain(Some(0))
634            .collect_vec();
635        self.foreground_executor().spawn(async move {
636            let credentials = CREDENTIALW {
637                LastWritten: unsafe { GetSystemTimeAsFileTime() },
638                Flags: CRED_FLAGS(0),
639                Type: CRED_TYPE_GENERIC,
640                TargetName: PWSTR::from_raw(target_name.as_mut_ptr()),
641                CredentialBlobSize: password.len() as u32,
642                CredentialBlob: password.as_ptr() as *mut _,
643                Persist: CRED_PERSIST_LOCAL_MACHINE,
644                UserName: PWSTR::from_raw(username.as_mut_ptr()),
645                ..CREDENTIALW::default()
646            };
647            unsafe { CredWriteW(&credentials, 0) }?;
648            Ok(())
649        })
650    }
651
652    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
653        let mut target_name = windows_credentials_target_name(url)
654            .encode_utf16()
655            .chain(Some(0))
656            .collect_vec();
657        self.foreground_executor().spawn(async move {
658            let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
659            unsafe {
660                CredReadW(
661                    PCWSTR::from_raw(target_name.as_ptr()),
662                    CRED_TYPE_GENERIC,
663                    None,
664                    &mut credentials,
665                )?
666            };
667
668            if credentials.is_null() {
669                Ok(None)
670            } else {
671                let username: String = unsafe { (*credentials).UserName.to_string()? };
672                let credential_blob = unsafe {
673                    std::slice::from_raw_parts(
674                        (*credentials).CredentialBlob,
675                        (*credentials).CredentialBlobSize as usize,
676                    )
677                };
678                let password = credential_blob.to_vec();
679                unsafe { CredFree(credentials as *const _ as _) };
680                Ok(Some((username, password)))
681            }
682        })
683    }
684
685    fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
686        let mut target_name = windows_credentials_target_name(url)
687            .encode_utf16()
688            .chain(Some(0))
689            .collect_vec();
690        self.foreground_executor().spawn(async move {
691            unsafe {
692                CredDeleteW(
693                    PCWSTR::from_raw(target_name.as_ptr()),
694                    CRED_TYPE_GENERIC,
695                    None,
696                )?
697            };
698            Ok(())
699        })
700    }
701
702    fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
703        Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
704    }
705
706    fn perform_dock_menu_action(&self, action: usize) {
707        unsafe {
708            PostThreadMessageW(
709                self.main_thread_id_win32,
710                WM_GPUI_DOCK_MENU_ACTION,
711                WPARAM(self.validation_number),
712                LPARAM(action as isize),
713            )
714            .log_err();
715        }
716    }
717
718    fn update_jump_list(
719        &self,
720        menus: Vec<MenuItem>,
721        entries: Vec<SmallVec<[PathBuf; 2]>>,
722    ) -> Vec<SmallVec<[PathBuf; 2]>> {
723        self.update_jump_list(menus, entries)
724    }
725}
726
727impl Drop for WindowsPlatform {
728    fn drop(&mut self) {
729        unsafe {
730            ManuallyDrop::drop(&mut self.bitmap_factory);
731            OleUninitialize();
732        }
733    }
734}
735
736pub(crate) struct WindowCreationInfo {
737    pub(crate) icon: HICON,
738    pub(crate) executor: ForegroundExecutor,
739    pub(crate) current_cursor: Option<HCURSOR>,
740    pub(crate) windows_version: WindowsVersion,
741    pub(crate) drop_target_helper: IDropTargetHelper,
742    pub(crate) validation_number: usize,
743    pub(crate) main_receiver: flume::Receiver<Runnable>,
744    pub(crate) main_thread_id_win32: u32,
745}
746
747fn open_target(target: &str) {
748    unsafe {
749        let ret = ShellExecuteW(
750            None,
751            windows::core::w!("open"),
752            &HSTRING::from(target),
753            None,
754            None,
755            SW_SHOWDEFAULT,
756        );
757        if ret.0 as isize <= 32 {
758            log::error!("Unable to open target: {}", std::io::Error::last_os_error());
759        }
760    }
761}
762
763fn open_target_in_explorer(target: &str) {
764    unsafe {
765        let ret = ShellExecuteW(
766            None,
767            windows::core::w!("open"),
768            windows::core::w!("explorer.exe"),
769            &HSTRING::from(format!("/select,{}", target).as_str()),
770            None,
771            SW_SHOWDEFAULT,
772        );
773        if ret.0 as isize <= 32 {
774            log::error!(
775                "Unable to open target in explorer: {}",
776                std::io::Error::last_os_error()
777            );
778        }
779    }
780}
781
782fn file_open_dialog(
783    options: PathPromptOptions,
784    window: Option<HWND>,
785) -> Result<Option<Vec<PathBuf>>> {
786    let folder_dialog: IFileOpenDialog =
787        unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? };
788
789    let mut dialog_options = FOS_FILEMUSTEXIST;
790    if options.multiple {
791        dialog_options |= FOS_ALLOWMULTISELECT;
792    }
793    if options.directories {
794        dialog_options |= FOS_PICKFOLDERS;
795    }
796
797    unsafe {
798        folder_dialog.SetOptions(dialog_options)?;
799        if folder_dialog.Show(window).is_err() {
800            // User cancelled
801            return Ok(None);
802        }
803    }
804
805    let results = unsafe { folder_dialog.GetResults()? };
806    let file_count = unsafe { results.GetCount()? };
807    if file_count == 0 {
808        return Ok(None);
809    }
810
811    let mut paths = Vec::with_capacity(file_count as usize);
812    for i in 0..file_count {
813        let item = unsafe { results.GetItemAt(i)? };
814        let path = unsafe { item.GetDisplayName(SIGDN_FILESYSPATH)?.to_string()? };
815        paths.push(PathBuf::from(path));
816    }
817
818    Ok(Some(paths))
819}
820
821fn file_save_dialog(directory: PathBuf, window: Option<HWND>) -> Result<Option<PathBuf>> {
822    let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
823    if !directory.to_string_lossy().is_empty() {
824        if let Some(full_path) = directory.canonicalize().log_err() {
825            let full_path = SanitizedPath::from(full_path);
826            let full_path_string = full_path.to_string();
827            let path_item: IShellItem =
828                unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? };
829            unsafe { dialog.SetFolder(&path_item).log_err() };
830        }
831    }
832    unsafe {
833        dialog.SetFileTypes(&[Common::COMDLG_FILTERSPEC {
834            pszName: windows::core::w!("All files"),
835            pszSpec: windows::core::w!("*.*"),
836        }])?;
837        if dialog.Show(window).is_err() {
838            // User cancelled
839            return Ok(None);
840        }
841    }
842    let shell_item = unsafe { dialog.GetResult()? };
843    let file_path_string = unsafe {
844        let pwstr = shell_item.GetDisplayName(SIGDN_FILESYSPATH)?;
845        let string = pwstr.to_string()?;
846        CoTaskMemFree(Some(pwstr.0 as _));
847        string
848    };
849    Ok(Some(PathBuf::from(file_path_string)))
850}
851
852fn begin_vsync(vsync_event: HANDLE) {
853    let event: SafeHandle = vsync_event.into();
854    std::thread::spawn(move || unsafe {
855        loop {
856            windows::Win32::Graphics::Dwm::DwmFlush().log_err();
857            SetEvent(*event).log_err();
858        }
859    });
860}
861
862fn load_icon() -> Result<HICON> {
863    let module = unsafe { GetModuleHandleW(None).context("unable to get module handle")? };
864    let handle = unsafe {
865        LoadImageW(
866            Some(module.into()),
867            windows::core::PCWSTR(1 as _),
868            IMAGE_ICON,
869            0,
870            0,
871            LR_DEFAULTSIZE | LR_SHARED,
872        )
873        .context("unable to load icon file")?
874    };
875    Ok(HICON(handle.0))
876}
877
878#[inline]
879fn should_auto_hide_scrollbars() -> Result<bool> {
880    let ui_settings = UISettings::new()?;
881    Ok(ui_settings.AutoHideScrollBars()?)
882}
883
884#[cfg(test)]
885mod tests {
886    use crate::{ClipboardItem, read_from_clipboard, write_to_clipboard};
887
888    #[test]
889    fn test_clipboard() {
890        let item = ClipboardItem::new_string("你好,我是张小白".to_string());
891        write_to_clipboard(item.clone());
892        assert_eq!(read_from_clipboard(), Some(item));
893
894        let item = ClipboardItem::new_string("12345".to_string());
895        write_to_clipboard(item.clone());
896        assert_eq!(read_from_clipboard(), Some(item));
897
898        let item = ClipboardItem::new_string_with_json_metadata("abcdef".to_string(), vec![3, 4]);
899        write_to_clipboard(item.clone());
900        assert_eq!(read_from_clipboard(), Some(item));
901    }
902}