From e70b980aa19de4cf0cf7c587bbb4e71ace282b56 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Thu, 13 Apr 2023 11:25:48 +0200 Subject: [PATCH 1/3] Replace windows with windows-sys --- .github/workflows/rust.yml | 4 +- Cargo.toml | 5 +- build.rs | 23 +- src/backend/win_cid/file_dialog.rs | 4 +- src/backend/win_cid/file_dialog/dialog_ffi.rs | 501 +++++++++++++----- .../win_cid/file_dialog/dialog_future.rs | 10 +- src/backend/win_cid/message_dialog.rs | 122 ++--- src/backend/win_cid/utils.rs | 30 +- 8 files changed, 480 insertions(+), 219 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8b38323..cd21fcd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -36,14 +36,14 @@ jobs: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: "[Ubuntu] install dependencies" if: matrix.name == 'Ubuntu GTK' run: sudo apt update && sudo apt install libgtk-3-dev - name: "[WASM] rustup" if: matrix.name == 'WASM32' run: rustup target add wasm32-unknown-unknown - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: Build run: cargo build --target ${{ matrix.target }} ${{ matrix.flags }} - name: Test diff --git a/Cargo.toml b/Cargo.toml index aad16c4..c14474f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ default = ["gtk3"] file-handle-inner = [] gtk3 = ["gtk-sys", "glib-sys", "gobject-sys"] xdg-portal = ["ashpd", "urlencoding", "pollster"] -common-controls-v6 = ["windows/Win32_UI_Controls"] +common-controls-v6 = ["windows-sys/Win32_UI_Controls"] [dev-dependencies] futures = "0.3.12" @@ -32,7 +32,7 @@ block = "0.1.6" objc-foundation = "0.1.1" [target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.44", features = [ +windows-sys = { version = "0.48", features = [ "Win32_Foundation", "Win32_System_Com", "Win32_UI_Shell_Common", @@ -76,4 +76,3 @@ name = "async" [package.metadata.docs.rs] features = ["file-handle-inner"] - diff --git a/build.rs b/build.rs index cf8219a..6ec72c3 100644 --- a/build.rs +++ b/build.rs @@ -1,13 +1,18 @@ fn main() { - let target = std::env::var("TARGET").unwrap(); + let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("target OS not detected"); - if target.contains("darwin") { - println!("cargo:rustc-link-lib=framework=AppKit"); - } - - #[cfg(all(feature = "gtk3", feature = "xdg-portal"))] - compile_error!("You can't enable both GTK3 & XDG Portal features at once"); + match target_os.as_str() { + "macos" => println!("cargo:rustc-link-lib=framework=AppKit"), + "windows" => {} + _ => { + let gtk = std::env::var_os("CARGO_FEATURE_GTK3").is_some(); + let xdg = std::env::var_os("CARGO_FEATURE_XDG_PORTAL").is_some(); - #[cfg(not(any(feature = "gtk3", feature = "xdg-portal")))] - compile_error!("You need to choose at least one backend: `gtk3` or `xdg-portal` features"); + if gtk && xdg { + panic!("You can't enable both `gtk3` and `xdg-portal` features at once"); + } else if !gtk && !xdg { + panic!("You need to choose at least one backend: `gtk3` or `xdg-portal` features"); + } + } + } } diff --git a/src/backend/win_cid/file_dialog.rs b/src/backend/win_cid/file_dialog.rs index ae4481d..7979350 100644 --- a/src/backend/win_cid/file_dialog.rs +++ b/src/backend/win_cid/file_dialog.rs @@ -1,7 +1,7 @@ pub mod dialog_ffi; mod dialog_future; -use dialog_ffi::IDialog; +use dialog_ffi::{IDialog, Result}; use dialog_future::{multiple_return_future, single_return_future}; use crate::backend::DialogFutureType; @@ -10,8 +10,6 @@ use crate::FileHandle; use std::path::PathBuf; -use windows::core::Result; - use super::utils::init_com; // diff --git a/src/backend/win_cid/file_dialog/dialog_ffi.rs b/src/backend/win_cid/file_dialog/dialog_ffi.rs index 5a87ed9..ccce753 100644 --- a/src/backend/win_cid/file_dialog/dialog_ffi.rs +++ b/src/backend/win_cid/file_dialog/dialog_ffi.rs @@ -1,161 +1,452 @@ +use super::super::utils::str_to_vec_u16; use crate::FileDialog; -use std::{ffi::OsStr, iter::once, os::windows::ffi::OsStrExt, path::PathBuf}; +use std::{ffi::c_void, path::PathBuf}; -use windows::core::{Result, PCWSTR, PWSTR}; -use windows::Win32::{ +use windows_sys::core::{GUID, HRESULT, PCWSTR, PWSTR}; +use windows_sys::Win32::{ Foundation::HWND, System::Com::{CoCreateInstance, CoTaskMemFree, CLSCTX_INPROC_SERVER}, UI::Shell::{ - Common::COMDLG_FILTERSPEC, FileOpenDialog, FileSaveDialog, IFileDialog, IFileOpenDialog, - IFileSaveDialog, IShellItem, SHCreateItemFromParsingName, FILEOPENDIALOGOPTIONS, - FOS_ALLOWMULTISELECT, FOS_PICKFOLDERS, SIGDN_FILESYSPATH, + Common::COMDLG_FILTERSPEC, FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, + FILEOPENDIALOGOPTIONS, FOS_ALLOWMULTISELECT, FOS_PICKFOLDERS, SIGDN, SIGDN_FILESYSPATH, }, }; use raw_window_handle::RawWindowHandle; -unsafe fn read_to_string(ptr: PWSTR) -> String { - let mut len = 0usize; +#[inline] +unsafe fn read_to_string(ptr: *const u16) -> String { let mut cursor = ptr; + loop { - let val = cursor.0.read(); - if val == 0 { + if *cursor == 0 { break; } - len += 1; - cursor = PWSTR(cursor.0.add(1)); + + cursor = cursor.add(1); } - let slice = std::slice::from_raw_parts(ptr.0, len); + let slice = std::slice::from_raw_parts(ptr, cursor.offset_from(ptr) as usize); String::from_utf16(slice).unwrap() } -pub enum DialogKind { +pub type Result = std::result::Result; + +#[inline] +fn wrap_err(hresult: HRESULT) -> Result<()> { + if hresult >= 0 { + Ok(()) + } else { + Err(hresult) + } +} + +#[repr(C)] +struct Interface { + vtable: *mut T, +} + +impl Interface { + #[inline] + fn vtbl(&self) -> &T { + unsafe { &*self.vtable } + } +} + +#[repr(C)] +struct IUnknownV { + __query_interface: usize, + __add_ref: usize, + release: unsafe extern "system" fn(this: *mut c_void) -> u32, +} + +type IUnknown = Interface; + +#[inline] +fn drop_impl(ptr: *mut std::ffi::c_void) { + unsafe { + ((*ptr.cast::()).vtbl().release)(ptr); + } +} + +#[repr(C)] +struct IShellItemV { + base: IUnknownV, + __bind_to_handler: usize, + __get_parent: usize, + get_display_name: + unsafe extern "system" fn(this: *mut c_void, name_look: SIGDN, name: *mut PWSTR) -> HRESULT, + __get_attributes: usize, + __compare: usize, +} + +#[repr(C)] +struct IShellItem(*mut Interface); + +impl IShellItem { + fn get_path(&self) -> Result { + let filename = unsafe { + let mut dname = std::mem::MaybeUninit::uninit(); + wrap_err(((*self.0).vtbl().get_display_name)( + self.0.cast(), + SIGDN_FILESYSPATH, + dname.as_mut_ptr(), + ))?; + + let dname = dname.assume_init(); + let fname = read_to_string(dname); + CoTaskMemFree(dname.cast()); + fname + }; + + Ok(filename.into()) + } +} + +impl Drop for IShellItem { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +struct IShellItemArrayV { + base: IUnknownV, + __bind_to_handler: usize, + __get_property_store: usize, + __get_property_description_list: usize, + __get_attributes: usize, + get_count: unsafe extern "system" fn(this: *mut c_void, num_items: *mut u32) -> HRESULT, + get_item_at: unsafe extern "system" fn( + this: *mut c_void, + dwindex: u32, + ppsi: *mut IShellItem, + ) -> HRESULT, + __enum_items: usize, +} + +#[repr(C)] +struct IShellItemArray(*mut Interface); + +impl Drop for IShellItemArray { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +struct IModalWindowV { + base: IUnknownV, + show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, +} + +/// +#[repr(C)] +struct IFileDialogV { + base: IModalWindowV, + set_file_types: unsafe extern "system" fn( + this: *mut c_void, + count_filetypes: u32, + filter_spec: *const COMDLG_FILTERSPEC, + ) -> HRESULT, + __set_file_type_index: usize, + __get_file_type_index: usize, + __advise: usize, + __unadvise: usize, + set_options: + unsafe extern "system" fn(this: *mut c_void, options: FILEOPENDIALOGOPTIONS) -> HRESULT, + __get_options: usize, + __set_default_folder: usize, + set_folder: unsafe extern "system" fn(this: *mut c_void, shell_item: *mut c_void) -> HRESULT, + __get_folder: usize, + __get_current_selection: usize, + set_file_name: unsafe extern "system" fn(this: *mut c_void, name: PCWSTR) -> HRESULT, + __get_file_name: usize, + set_title: unsafe extern "system" fn(this: *mut c_void, title: PCWSTR) -> HRESULT, + __set_ok_button_label: usize, + __set_file_name_label: usize, + get_result: + unsafe extern "system" fn(this: *mut c_void, shell_item: *mut IShellItem) -> HRESULT, + __add_place: usize, + set_default_extension: + unsafe extern "system" fn(this: *mut c_void, default_ext: PCWSTR) -> HRESULT, + __close: usize, + __set_client_guid: usize, + __clear_client_data: usize, + __set_filter: usize, +} + +#[repr(C)] +struct IFileDialog(*mut Interface); + +impl Drop for IFileDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +/// +#[repr(C)] +struct IFileOpenDialogV { + base: IFileDialogV, + /// + get_results: + unsafe extern "system" fn(this: *mut c_void, results: *mut IShellItemArray) -> HRESULT, + __get_selected_items: usize, +} + +struct IFileOpenDialog(*mut Interface); + +impl Drop for IFileOpenDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +enum DialogInner { Open(IFileOpenDialog), - Save(IFileSaveDialog), + Save(IFileDialog), } -impl DialogKind { - fn as_dialog(&self) -> IFileDialog { +impl DialogInner { + unsafe fn new(open: bool) -> Result { + const FILE_OPEN_DIALOG_IID: GUID = GUID::from_u128(0xd57c7288_d4ad_4768_be02_9d969532d960); + const FILE_SAVE_DIALOG_IID: GUID = GUID::from_u128(0x84bccd23_5fde_4cdb_aea4_af64b83d78ab); + + unsafe { + let (cls_id, iid) = if open { + (&FileOpenDialog, &FILE_OPEN_DIALOG_IID) + } else { + (&FileSaveDialog, &FILE_SAVE_DIALOG_IID) + }; + + let mut iptr = std::mem::MaybeUninit::uninit(); + wrap_err(CoCreateInstance( + cls_id, + std::ptr::null_mut(), + CLSCTX_INPROC_SERVER, + iid, + iptr.as_mut_ptr(), + ))?; + + let iptr = iptr.assume_init(); + + Ok(if open { + Self::Open(IFileOpenDialog(iptr.cast())) + } else { + Self::Save(IFileDialog(iptr.cast())) + }) + } + } + + #[inline] + unsafe fn open() -> Result { + unsafe { Self::new(true) } + } + + #[inline] + unsafe fn save() -> Result { + unsafe { Self::new(false) } + } + + #[inline] + unsafe fn fd(&self) -> (*mut std::ffi::c_void, &IFileDialogV) { match self { - Self::Open(d) => d.into(), - Self::Save(d) => d.into(), + Self::Save(s) => unsafe { (s.0.cast(), (*s.0).vtbl()) }, + Self::Open(o) => unsafe { (o.0.cast(), &(*o.0).vtbl().base) }, + } + } + + #[inline] + unsafe fn set_options(&self, opts: FILEOPENDIALOGOPTIONS) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_options)(d, opts)) + } + + #[inline] + unsafe fn set_title(&self, title: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_title)(d, title.as_ptr())) + } + + #[inline] + unsafe fn set_default_extension(&self, extension: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_default_extension)(d, extension.as_ptr())) + } + + #[inline] + unsafe fn set_file_types(&self, specs: &[COMDLG_FILTERSPEC]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_file_types)(d, specs.len() as _, specs.as_ptr())) + } + + #[inline] + unsafe fn set_filename(&self, fname: &[u16]) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_file_name)(d, fname.as_ptr())) + } + + #[inline] + unsafe fn set_folder(&self, folder: &IShellItem) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.set_folder)(d, folder.0.cast())) + } + + #[inline] + unsafe fn show(&self, parent: Option) -> Result<()> { + let (d, v) = self.fd(); + wrap_err((v.base.show)(d, parent.unwrap_or_default())) + } + + #[inline] + unsafe fn get_result(&self) -> Result { + let (d, v) = self.fd(); + let mut res = std::mem::MaybeUninit::uninit(); + wrap_err((v.get_result)(d, res.as_mut_ptr()))?; + let res = res.assume_init(); + res.get_path() + } + + #[inline] + unsafe fn get_results(&self) -> Result> { + let Self::Open(od) = self else { unreachable!() }; + + let mut res = std::mem::MaybeUninit::uninit(); + wrap_err(((*(*od.0).vtable).get_results)( + od.0.cast(), + res.as_mut_ptr(), + ))?; + let items = res.assume_init(); + + let sia = items.0.cast(); + let svt = &*(*items.0).vtable; + + let mut count = 0; + wrap_err((svt.get_count)(sia, &mut count))?; + + let mut paths = Vec::with_capacity(count as usize); + for index in 0..count { + let mut item = std::mem::MaybeUninit::uninit(); + wrap_err((svt.get_item_at)(sia, index, item.as_mut_ptr()))?; + let item = item.assume_init(); + + let path = item.get_path()?; + paths.push(path); } + + Ok(paths) } } -pub struct IDialog(pub DialogKind, Option); +pub struct IDialog(DialogInner, Option); impl IDialog { fn new_open_dialog(opt: &FileDialog) -> Result { - let dialog: IFileOpenDialog = - unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_INPROC_SERVER)? }; + let dialog = unsafe { DialogInner::open()? }; let parent = match opt.parent { - Some(RawWindowHandle::Win32(handle)) => Some(HWND(handle.hwnd as _)), + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd as _), None => None, _ => unreachable!("unsupported window handle, expected: Windows"), }; - Ok(Self(DialogKind::Open(dialog), parent)) + Ok(Self(dialog, parent)) } fn new_save_dialog(opt: &FileDialog) -> Result { - let dialog: IFileSaveDialog = - unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_INPROC_SERVER)? }; + let dialog = unsafe { DialogInner::save()? }; let parent = match opt.parent { - Some(RawWindowHandle::Win32(handle)) => Some(HWND(handle.hwnd as _)), + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd as _), None => None, _ => unreachable!("unsupported window handle, expected: Windows"), }; - Ok(Self(DialogKind::Save(dialog), parent)) + Ok(Self(dialog, parent)) } fn add_filters(&self, filters: &[crate::file_dialog::Filter]) -> Result<()> { - if let Some(first_filter) = filters.first() { + { + let Some(first_filter) = filters.first() else { return Ok(()) }; if let Some(first_extension) = first_filter.extensions.first() { - let mut extension: Vec = - first_extension.encode_utf16().chain(Some(0)).collect(); - unsafe { - self.0 - .as_dialog() - .SetDefaultExtension(PCWSTR(extension.as_mut_ptr()))?; - } + let extension = str_to_vec_u16(first_extension); + unsafe { self.0.set_default_extension(&extension)? } } } - let mut f_list = { + let f_list = { let mut f_list = Vec::new(); + let mut ext_string = String::new(); for f in filters.iter() { - let name: Vec = OsStr::new(&f.name).encode_wide().chain(once(0)).collect(); - let ext_string = f - .extensions - .iter() - .map(|item| format!("*.{}", item)) - .collect::>() - .join(";"); - - let ext: Vec = OsStr::new(&ext_string) - .encode_wide() - .chain(once(0)) - .collect(); - - f_list.push((name, ext)); + let name = str_to_vec_u16(&f.name); + ext_string.clear(); + + for ext in &f.extensions { + use std::fmt::Write; + // This is infallible for String (barring OOM) + let _ = write!(&mut ext_string, "*.{ext};"); + } + + // pop trailing ; + ext_string.pop(); + + f_list.push((name, str_to_vec_u16(&ext_string))); } f_list }; let spec: Vec<_> = f_list - .iter_mut() + .iter() .map(|(name, ext)| COMDLG_FILTERSPEC { - pszName: PCWSTR(name.as_mut_ptr()), - pszSpec: PCWSTR(ext.as_mut_ptr()), + pszName: name.as_ptr(), + pszSpec: ext.as_ptr(), }) .collect(); unsafe { - if !spec.is_empty() { - self.0.as_dialog().SetFileTypes(&spec)?; - } + self.0.set_file_types(&spec)?; } Ok(()) } fn set_path(&self, path: &Option) -> Result<()> { - if let Some(path) = path { - if let Some(path) = path.to_str() { - // Strip Win32 namespace prefix from the path - let path = path.strip_prefix(r"\\?\").unwrap_or(path); - - let mut wide_path: Vec = - OsStr::new(path).encode_wide().chain(once(0)).collect(); - - unsafe { - let item: Option = - SHCreateItemFromParsingName(PCWSTR(wide_path.as_mut_ptr()), None).ok(); - - if let Some(item) = item { - // For some reason SetDefaultFolder(), does not guarantees default path, so we use SetFolder - self.0.as_dialog().SetFolder(&item)?; - } - } + const SHELL_ITEM_IID: GUID = GUID::from_u128(0x43826d1e_e718_42ee_bc55_a1e261c37bfe); + + let Some(path) = path.as_ref().and_then(|p| p.to_str()) else { return Ok(()) }; + + // Strip Win32 namespace prefix from the path + let path = path.strip_prefix(r"\\?\").unwrap_or(path); + + let wide_path = str_to_vec_u16(path); + + unsafe { + let mut item = std::mem::MaybeUninit::uninit(); + if wrap_err(SHCreateItemFromParsingName( + wide_path.as_ptr(), + std::ptr::null_mut(), + &SHELL_ITEM_IID, + item.as_mut_ptr(), + )) + .is_ok() + { + let item = IShellItem(item.assume_init().cast()); + // For some reason SetDefaultFolder(), does not guarantees default path, so we use SetFolder + self.0.set_folder(&item)?; } } + Ok(()) } fn set_file_name(&self, file_name: &Option) -> Result<()> { if let Some(path) = file_name { - let mut wide_path: Vec = OsStr::new(path).encode_wide().chain(once(0)).collect(); + let wide_path = str_to_vec_u16(path); unsafe { - self.0 - .as_dialog() - .SetFileName(PCWSTR(wide_path.as_mut_ptr()))?; + self.0.set_filename(&wide_path)?; } } Ok(()) @@ -163,61 +454,25 @@ impl IDialog { fn set_title(&self, title: &Option) -> Result<()> { if let Some(title) = title { - let mut wide_title: Vec = OsStr::new(title).encode_wide().chain(once(0)).collect(); + let wide_title = str_to_vec_u16(title); unsafe { - self.0 - .as_dialog() - .SetTitle(PCWSTR(wide_title.as_mut_ptr()))?; + self.0.set_title(&wide_title)?; } } Ok(()) } pub fn get_results(&self) -> Result> { - unsafe { - let dialog = if let DialogKind::Open(ref d) = self.0 { - d - } else { - unreachable!() - }; - - let items = dialog.GetResults()?; - - let count = items.GetCount()?; - - let mut paths = Vec::new(); - for id in 0..count { - let res_item = items.GetItemAt(id)?; - - let display_name = res_item.GetDisplayName(SIGDN_FILESYSPATH)?; - - let filename = read_to_string(display_name); - - CoTaskMemFree(Some(display_name.0 as _)); - - let path = PathBuf::from(filename); - paths.push(path); - } - - Ok(paths) - } + unsafe { self.0.get_results() } } pub fn get_result(&self) -> Result { - unsafe { - let res_item = self.0.as_dialog().GetResult()?; - let display_name = res_item.GetDisplayName(SIGDN_FILESYSPATH)?; - - let filename = read_to_string(display_name); - CoTaskMemFree(Some(display_name.0 as _)); - - Ok(PathBuf::from(filename)) - } + unsafe { self.0.get_result() } } pub fn show(&self) -> Result<()> { - unsafe { self.0.as_dialog().Show(self.1) } + unsafe { self.0.show(self.1) } } } @@ -251,7 +506,7 @@ impl IDialog { dialog.set_title(&opt.title)?; unsafe { - dialog.0.as_dialog().SetOptions(FOS_PICKFOLDERS as _)?; + dialog.0.set_options(FOS_PICKFOLDERS)?; } Ok(dialog) @@ -262,10 +517,10 @@ impl IDialog { dialog.set_path(&opt.starting_directory)?; dialog.set_title(&opt.title)?; - let opts = FILEOPENDIALOGOPTIONS(FOS_PICKFOLDERS.0 | FOS_ALLOWMULTISELECT.0); + let opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT; unsafe { - dialog.0.as_dialog().SetOptions(opts)?; + dialog.0.set_options(opts)?; } Ok(dialog) @@ -280,7 +535,7 @@ impl IDialog { dialog.set_title(&opt.title)?; unsafe { - dialog.0.as_dialog().SetOptions(FOS_ALLOWMULTISELECT as _)?; + dialog.0.set_options(FOS_ALLOWMULTISELECT)?; } Ok(dialog) diff --git a/src/backend/win_cid/file_dialog/dialog_future.rs b/src/backend/win_cid/file_dialog/dialog_future.rs index 2ab5f14..2ebf72c 100644 --- a/src/backend/win_cid/file_dialog/dialog_future.rs +++ b/src/backend/win_cid/file_dialog/dialog_future.rs @@ -2,15 +2,13 @@ use super::super::thread_future::ThreadFuture; use super::super::utils::init_com; use super::dialog_ffi::IDialog; -use windows::core::Result; - use crate::file_handle::FileHandle; -pub fn single_return_future Result + Send + 'static>( +pub fn single_return_future Result + Send + 'static>( build: F, ) -> ThreadFuture> { ThreadFuture::new(move |data| { - let ret: Result<()> = (|| { + let ret: Result<(), i32> = (|| { init_com(|| { let dialog = build()?; dialog.show()?; @@ -28,11 +26,11 @@ pub fn single_return_future Result + Send + 'static>( }) } -pub fn multiple_return_future Result + Send + 'static>( +pub fn multiple_return_future Result + Send + 'static>( build: F, ) -> ThreadFuture>> { ThreadFuture::new(move |data| { - let ret: Result<()> = (|| { + let ret: Result<(), i32> = (|| { init_com(|| { let dialog = build()?; dialog.show()?; diff --git a/src/backend/win_cid/message_dialog.rs b/src/backend/win_cid/message_dialog.rs index 0f33c97..a9f6300 100644 --- a/src/backend/win_cid/message_dialog.rs +++ b/src/backend/win_cid/message_dialog.rs @@ -1,24 +1,20 @@ use super::thread_future::ThreadFuture; +use super::utils::str_to_vec_u16; use crate::message_dialog::{MessageButtons, MessageDialog, MessageLevel}; -use windows::{ - core::PCWSTR, - Win32::{ - Foundation::HWND, - UI::WindowsAndMessaging::{IDOK, IDYES}, - }, +use windows_sys::Win32::{ + Foundation::HWND, + UI::WindowsAndMessaging::{IDOK, IDYES}, }; #[cfg(not(feature = "common-controls-v6"))] -use windows::Win32::UI::WindowsAndMessaging::{ +use windows_sys::Win32::UI::WindowsAndMessaging::{ MessageBoxW, MB_ICONERROR, MB_ICONINFORMATION, MB_ICONWARNING, MB_OK, MB_OKCANCEL, MB_YESNO, MESSAGEBOX_STYLE, }; use raw_window_handle::RawWindowHandle; -use std::{ffi::OsStr, iter::once, os::windows::ffi::OsStrExt}; - pub struct WinMessageDialog { parent: Option, text: Vec, @@ -33,10 +29,6 @@ pub struct WinMessageDialog { // fingers crossed unsafe impl Send for WinMessageDialog {} -fn str_to_vec_u16(str: &str) -> Vec { - OsStr::new(str).encode_wide().chain(once(0)).collect() -} - impl WinMessageDialog { pub fn new(opt: MessageDialog) -> Self { let text: Vec = str_to_vec_u16(&opt.description); @@ -57,7 +49,7 @@ impl WinMessageDialog { }; let parent = match opt.parent { - Some(RawWindowHandle::Win32(handle)) => Some(HWND(handle.hwnd as _)), + Some(RawWindowHandle::Win32(handle)) => Some(handle.hwnd as _), None => None, _ => unreachable!("unsupported window handle, expected: Windows"), }; @@ -74,57 +66,31 @@ impl WinMessageDialog { } #[cfg(feature = "common-controls-v6")] - pub fn run(mut self) -> bool { - use windows::Win32::{ - Foundation::BOOL, - UI::Controls::{ - TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, - TASKDIALOG_COMMON_BUTTON_FLAGS, TDCBF_CANCEL_BUTTON, TDCBF_NO_BUTTON, - TDCBF_OK_BUTTON, TDCBF_YES_BUTTON, TDF_ALLOW_DIALOG_CANCELLATION, - }, + pub fn run(self) -> bool { + use windows_sys::Win32::UI::Controls::{ + TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1, + TASKDIALOG_BUTTON, TDCBF_CANCEL_BUTTON, TDCBF_NO_BUTTON, TDCBF_OK_BUTTON, + TDCBF_YES_BUTTON, TDF_ALLOW_DIALOG_CANCELLATION, TD_ERROR_ICON, TD_INFORMATION_ICON, + TD_WARNING_ICON, }; - let mut pf_verification_flag_checked = BOOL(0); + let mut pf_verification_flag_checked = 0; let mut pn_button = 0; let mut pn_radio_button = 0; let id_custom_ok = 1000; let id_custom_cancel = 1001; - let mut task_dialog_config = TASKDIALOGCONFIG { - cbSize: core::mem::size_of::() as u32, - hwndParent: self.parent.unwrap_or_default(), - dwFlags: TDF_ALLOW_DIALOG_CANCELLATION, - cButtons: 0, - pszWindowTitle: PCWSTR(self.caption.as_mut_ptr()), - pszContent: PCWSTR(self.text.as_mut_ptr()), - ..Default::default() - }; - let main_icon_ptr = match self.opt.level { - // `TD_WARNING_ICON` / `TD_ERROR_ICON` / `TD_INFORMATION_ICON` are missing in windows-rs - // https://github.com/microsoft/win32metadata/issues/968 - // Workaround via hard code: - // TD_WARNING_ICON - MessageLevel::Warning => -1 as i16 as u16, - // TD_ERROR_ICON - MessageLevel::Error => -2 as i16 as u16, - // TD_INFORMATION_ICON - MessageLevel::Info => -3 as i16 as u16, + MessageLevel::Warning => TD_WARNING_ICON, + MessageLevel::Error => TD_ERROR_ICON, + MessageLevel::Info => TD_INFORMATION_ICON, }; - task_dialog_config.Anonymous1.pszMainIcon = PCWSTR(main_icon_ptr as *const u16); - let (system_buttons, custom_buttons) = match self.opt.buttons { MessageButtons::Ok => (TDCBF_OK_BUTTON, vec![]), - MessageButtons::OkCancel => ( - TASKDIALOG_COMMON_BUTTON_FLAGS(TDCBF_OK_BUTTON.0 | TDCBF_CANCEL_BUTTON.0), - vec![], - ), - MessageButtons::YesNo => ( - TASKDIALOG_COMMON_BUTTON_FLAGS(TDCBF_YES_BUTTON.0 | TDCBF_NO_BUTTON.0), - vec![], - ), + MessageButtons::OkCancel => (TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON, vec![]), + MessageButtons::YesNo => (TDCBF_YES_BUTTON | TDCBF_NO_BUTTON, vec![]), MessageButtons::OkCustom(ok_text) => ( Default::default(), vec![(id_custom_ok, str_to_vec_u16(&ok_text))], @@ -142,32 +108,60 @@ impl WinMessageDialog { .iter() .map(|(id, text)| TASKDIALOG_BUTTON { nButtonID: *id, - pszButtonText: PCWSTR(text.as_ptr()), + pszButtonText: text.as_ptr(), }) .collect::>(); - task_dialog_config.dwCommonButtons = system_buttons; - task_dialog_config.pButtons = p_buttons.as_ptr(); - task_dialog_config.cButtons = custom_buttons.len() as u32; + + let task_dialog_config = TASKDIALOGCONFIG { + cbSize: core::mem::size_of::() as u32, + hwndParent: self.parent.unwrap_or_default(), + dwFlags: TDF_ALLOW_DIALOG_CANCELLATION, + pszWindowTitle: self.caption.as_ptr(), + pszContent: self.text.as_ptr(), + Anonymous1: TASKDIALOGCONFIG_0 { + pszMainIcon: main_icon_ptr, + }, + Anonymous2: TASKDIALOGCONFIG_1 { + pszFooterIcon: std::ptr::null(), + }, + dwCommonButtons: system_buttons, + pButtons: p_buttons.as_ptr(), + cButtons: custom_buttons.len() as u32, + pRadioButtons: std::ptr::null(), + cRadioButtons: 0, + cxWidth: 0, + hInstance: 0, + pfCallback: None, + lpCallbackData: 0, + nDefaultButton: 0, + nDefaultRadioButton: 0, + pszCollapsedControlText: std::ptr::null(), + pszExpandedControlText: std::ptr::null(), + pszExpandedInformation: std::ptr::null(), + pszMainInstruction: std::ptr::null(), + pszVerificationText: std::ptr::null(), + pszFooter: std::ptr::null(), + }; let ret = unsafe { TaskDialogIndirect( &task_dialog_config, - Some(&mut pn_button), - Some(&mut pn_radio_button), - Some(&mut pf_verification_flag_checked), + &mut pn_button, + &mut pn_radio_button, + &mut pf_verification_flag_checked, ) }; - ret.is_ok() && (pn_button == id_custom_ok || pn_button == IDYES.0 || pn_button == IDOK.0) + ret == 0 && (pn_button == id_custom_ok || pn_button == IDYES || pn_button == IDOK) } #[cfg(not(feature = "common-controls-v6"))] - pub fn run(mut self) -> bool { + pub fn run(self) -> bool { let ret = unsafe { MessageBoxW( - self.parent, - PCWSTR(self.text.as_mut_ptr()), - PCWSTR(self.caption.as_mut_ptr()), + self.parent.unwrap_or_default(), + self.text.as_ptr(), + self.caption.as_ptr(), self.flags, ) }; diff --git a/src/backend/win_cid/utils.rs b/src/backend/win_cid/utils.rs index d31b7cd..dd7b6a1 100644 --- a/src/backend/win_cid/utils.rs +++ b/src/backend/win_cid/utils.rs @@ -1,18 +1,30 @@ -use windows::core::Result; - -use windows::Win32::System::Com::{ - CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, +use windows_sys::{ + core::HRESULT, + Win32::System::Com::{ + CoInitializeEx, CoUninitialize, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, + }, }; -/// Makes sure that COM lib is initialized long enought -pub fn init_com T>(f: F) -> Result { - unsafe { +#[inline] +pub(crate) fn str_to_vec_u16(s: &str) -> Vec { + let mut v: Vec<_> = s.encode_utf16().collect(); + v.push(0); + v +} + +/// Makes sure that COM lib is initialized long enough +pub fn init_com T>(f: F) -> Result { + let res = unsafe { CoInitializeEx( - None, + std::ptr::null(), COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE, - )? + ) }; + if res < 0 { + return Err(res); + } + let out = f(); unsafe { From 5c9b2b63b1368879e5f82dade98a4b769feb27be Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Tue, 25 Apr 2023 14:48:55 +0200 Subject: [PATCH 2/3] Move COM stuff to separate module --- src/backend/win_cid/file_dialog.rs | 1 + src/backend/win_cid/file_dialog/com.rs | 232 +++++++++++++++++ src/backend/win_cid/file_dialog/dialog_ffi.rs | 233 ++---------------- 3 files changed, 252 insertions(+), 214 deletions(-) create mode 100644 src/backend/win_cid/file_dialog/com.rs diff --git a/src/backend/win_cid/file_dialog.rs b/src/backend/win_cid/file_dialog.rs index 7979350..d5c414a 100644 --- a/src/backend/win_cid/file_dialog.rs +++ b/src/backend/win_cid/file_dialog.rs @@ -1,3 +1,4 @@ +mod com; pub mod dialog_ffi; mod dialog_future; diff --git a/src/backend/win_cid/file_dialog/com.rs b/src/backend/win_cid/file_dialog/com.rs new file mode 100644 index 0000000..c8c7065 --- /dev/null +++ b/src/backend/win_cid/file_dialog/com.rs @@ -0,0 +1,232 @@ +use std::ffi::c_void; +use windows_sys::core::{HRESULT, PCWSTR, PWSTR}; +pub use windows_sys::Win32::{ + Foundation::HWND, + UI::Shell::{Common::COMDLG_FILTERSPEC, FILEOPENDIALOGOPTIONS, SIGDN, SIGDN_FILESYSPATH}, +}; + +pub(crate) type Result = std::result::Result; + +#[inline] +pub(super) fn wrap_err(hresult: HRESULT) -> Result<()> { + if hresult >= 0 { + Ok(()) + } else { + Err(hresult) + } +} + +#[inline] +pub(super) unsafe fn read_to_string(ptr: *const u16) -> String { + let mut cursor = ptr; + + while *cursor != 0 { + cursor = cursor.add(1); + } + + let slice = std::slice::from_raw_parts(ptr, cursor.offset_from(ptr) as usize); + String::from_utf16(slice).unwrap() +} + +#[repr(C)] +pub(super) struct Interface { + vtable: *mut T, +} + +impl Interface { + #[inline] + pub(super) fn vtbl(&self) -> &T { + unsafe { &*self.vtable } + } +} + +#[repr(C)] +pub(super) struct IUnknownV { + __query_interface: usize, + __add_ref: usize, + pub(super) release: unsafe extern "system" fn(this: *mut c_void) -> u32, +} + +pub(super) type IUnknown = Interface; + +#[inline] +fn drop_impl(ptr: *mut c_void) { + unsafe { + ((*ptr.cast::()).vtbl().release)(ptr); + } +} + +#[repr(C)] +pub(super) struct IShellItemV { + base: IUnknownV, + __bind_to_handler: usize, + __get_parent: usize, + pub(super) get_display_name: + unsafe extern "system" fn(this: *mut c_void, name_look: SIGDN, name: *mut PWSTR) -> HRESULT, + __get_attributes: usize, + __compare: usize, +} + +#[repr(transparent)] +pub(super) struct IShellItem(pub(super) *mut Interface); + +impl IShellItem { + pub(super) fn get_path(&self) -> Result { + let filename = unsafe { + let mut dname = std::mem::MaybeUninit::uninit(); + wrap_err(((*self.0).vtbl().get_display_name)( + self.0.cast(), + SIGDN_FILESYSPATH, + dname.as_mut_ptr(), + ))?; + + let dname = dname.assume_init(); + let fname = read_to_string(dname); + windows_sys::Win32::System::Com::CoTaskMemFree(dname.cast()); + fname + }; + + Ok(filename.into()) + } +} + +impl Drop for IShellItem { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +struct IShellItemArrayV { + base: IUnknownV, + __bind_to_handler: usize, + __get_property_store: usize, + __get_property_description_list: usize, + __get_attributes: usize, + pub(super) get_count: + unsafe extern "system" fn(this: *mut c_void, num_items: *mut u32) -> HRESULT, + pub(super) get_item_at: unsafe extern "system" fn( + this: *mut c_void, + dwindex: u32, + ppsi: *mut IShellItem, + ) -> HRESULT, + __enum_items: usize, +} + +#[repr(transparent)] +pub(super) struct IShellItemArray(*mut Interface); + +impl IShellItemArray { + #[inline] + pub(super) fn get_count(&self) -> Result { + let mut count = 0; + unsafe { + wrap_err(((*self.0).vtbl().get_count)(self.0.cast(), &mut count))?; + } + Ok(count) + } + + #[inline] + pub(super) fn get_item_at(&self, index: u32) -> Result { + let mut item = std::mem::MaybeUninit::uninit(); + unsafe { + wrap_err(((*self.0).vtbl().get_item_at)( + self.0.cast(), + index, + item.as_mut_ptr(), + ))?; + Ok(item.assume_init()) + } + } +} + +impl Drop for IShellItemArray { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +#[repr(C)] +pub(super) struct IModalWindowV { + base: IUnknownV, + pub(super) show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, +} + +/// +#[repr(C)] +pub(super) struct IFileDialogV { + pub(super) base: IModalWindowV, + pub(super) set_file_types: unsafe extern "system" fn( + this: *mut c_void, + count_filetypes: u32, + filter_spec: *const COMDLG_FILTERSPEC, + ) -> HRESULT, + __set_file_type_index: usize, + __get_file_type_index: usize, + __advise: usize, + __unadvise: usize, + pub(super) set_options: + unsafe extern "system" fn(this: *mut c_void, options: FILEOPENDIALOGOPTIONS) -> HRESULT, + __get_options: usize, + __set_default_folder: usize, + pub(super) set_folder: + unsafe extern "system" fn(this: *mut c_void, shell_item: *mut c_void) -> HRESULT, + __get_folder: usize, + __get_current_selection: usize, + pub(super) set_file_name: unsafe extern "system" fn(this: *mut c_void, name: PCWSTR) -> HRESULT, + __get_file_name: usize, + pub(super) set_title: unsafe extern "system" fn(this: *mut c_void, title: PCWSTR) -> HRESULT, + __set_ok_button_label: usize, + __set_file_name_label: usize, + pub(super) get_result: + unsafe extern "system" fn(this: *mut c_void, shell_item: *mut IShellItem) -> HRESULT, + __add_place: usize, + pub(super) set_default_extension: + unsafe extern "system" fn(this: *mut c_void, default_ext: PCWSTR) -> HRESULT, + __close: usize, + __set_client_guid: usize, + __clear_client_data: usize, + __set_filter: usize, +} + +#[repr(transparent)] +pub(super) struct IFileDialog(pub(super) *mut Interface); + +impl Drop for IFileDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} + +/// +#[repr(C)] +pub(super) struct IFileOpenDialogV { + pub(super) base: IFileDialogV, + /// + pub(super) get_results: + unsafe extern "system" fn(this: *mut c_void, results: *mut IShellItemArray) -> HRESULT, + __get_selected_items: usize, +} + +#[repr(transparent)] +pub(super) struct IFileOpenDialog(pub(super) *mut Interface); + +impl IFileOpenDialog { + #[inline] + pub(super) fn get_results(&self) -> Result { + let mut res = std::mem::MaybeUninit::uninit(); + unsafe { + wrap_err((((*self.0).vtbl()).get_results)( + self.0.cast(), + res.as_mut_ptr(), + ))?; + Ok(res.assume_init()) + } + } +} + +impl Drop for IFileOpenDialog { + fn drop(&mut self) { + drop_impl(self.0.cast()); + } +} diff --git a/src/backend/win_cid/file_dialog/dialog_ffi.rs b/src/backend/win_cid/file_dialog/dialog_ffi.rs index ccce753..a9407b8 100644 --- a/src/backend/win_cid/file_dialog/dialog_ffi.rs +++ b/src/backend/win_cid/file_dialog/dialog_ffi.rs @@ -1,208 +1,25 @@ use super::super::utils::str_to_vec_u16; +pub(crate) use super::com::Result; +use super::com::{ + wrap_err, IFileDialog, IFileDialogV, IFileOpenDialog, IShellItem, COMDLG_FILTERSPEC, + FILEOPENDIALOGOPTIONS, HWND, +}; use crate::FileDialog; -use std::{ffi::c_void, path::PathBuf}; - -use windows_sys::core::{GUID, HRESULT, PCWSTR, PWSTR}; -use windows_sys::Win32::{ - Foundation::HWND, - System::Com::{CoCreateInstance, CoTaskMemFree, CLSCTX_INPROC_SERVER}, - UI::Shell::{ - Common::COMDLG_FILTERSPEC, FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, - FILEOPENDIALOGOPTIONS, FOS_ALLOWMULTISELECT, FOS_PICKFOLDERS, SIGDN, SIGDN_FILESYSPATH, +use windows_sys::{ + core::GUID, + Win32::{ + System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}, + UI::Shell::{ + FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, FOS_ALLOWMULTISELECT, + FOS_PICKFOLDERS, + }, }, }; -use raw_window_handle::RawWindowHandle; - -#[inline] -unsafe fn read_to_string(ptr: *const u16) -> String { - let mut cursor = ptr; - - loop { - if *cursor == 0 { - break; - } - - cursor = cursor.add(1); - } - - let slice = std::slice::from_raw_parts(ptr, cursor.offset_from(ptr) as usize); - String::from_utf16(slice).unwrap() -} - -pub type Result = std::result::Result; - -#[inline] -fn wrap_err(hresult: HRESULT) -> Result<()> { - if hresult >= 0 { - Ok(()) - } else { - Err(hresult) - } -} - -#[repr(C)] -struct Interface { - vtable: *mut T, -} - -impl Interface { - #[inline] - fn vtbl(&self) -> &T { - unsafe { &*self.vtable } - } -} - -#[repr(C)] -struct IUnknownV { - __query_interface: usize, - __add_ref: usize, - release: unsafe extern "system" fn(this: *mut c_void) -> u32, -} - -type IUnknown = Interface; - -#[inline] -fn drop_impl(ptr: *mut std::ffi::c_void) { - unsafe { - ((*ptr.cast::()).vtbl().release)(ptr); - } -} - -#[repr(C)] -struct IShellItemV { - base: IUnknownV, - __bind_to_handler: usize, - __get_parent: usize, - get_display_name: - unsafe extern "system" fn(this: *mut c_void, name_look: SIGDN, name: *mut PWSTR) -> HRESULT, - __get_attributes: usize, - __compare: usize, -} - -#[repr(C)] -struct IShellItem(*mut Interface); - -impl IShellItem { - fn get_path(&self) -> Result { - let filename = unsafe { - let mut dname = std::mem::MaybeUninit::uninit(); - wrap_err(((*self.0).vtbl().get_display_name)( - self.0.cast(), - SIGDN_FILESYSPATH, - dname.as_mut_ptr(), - ))?; - - let dname = dname.assume_init(); - let fname = read_to_string(dname); - CoTaskMemFree(dname.cast()); - fname - }; - - Ok(filename.into()) - } -} - -impl Drop for IShellItem { - fn drop(&mut self) { - drop_impl(self.0.cast()); - } -} - -#[repr(C)] -struct IShellItemArrayV { - base: IUnknownV, - __bind_to_handler: usize, - __get_property_store: usize, - __get_property_description_list: usize, - __get_attributes: usize, - get_count: unsafe extern "system" fn(this: *mut c_void, num_items: *mut u32) -> HRESULT, - get_item_at: unsafe extern "system" fn( - this: *mut c_void, - dwindex: u32, - ppsi: *mut IShellItem, - ) -> HRESULT, - __enum_items: usize, -} - -#[repr(C)] -struct IShellItemArray(*mut Interface); - -impl Drop for IShellItemArray { - fn drop(&mut self) { - drop_impl(self.0.cast()); - } -} - -#[repr(C)] -struct IModalWindowV { - base: IUnknownV, - show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, -} - -/// -#[repr(C)] -struct IFileDialogV { - base: IModalWindowV, - set_file_types: unsafe extern "system" fn( - this: *mut c_void, - count_filetypes: u32, - filter_spec: *const COMDLG_FILTERSPEC, - ) -> HRESULT, - __set_file_type_index: usize, - __get_file_type_index: usize, - __advise: usize, - __unadvise: usize, - set_options: - unsafe extern "system" fn(this: *mut c_void, options: FILEOPENDIALOGOPTIONS) -> HRESULT, - __get_options: usize, - __set_default_folder: usize, - set_folder: unsafe extern "system" fn(this: *mut c_void, shell_item: *mut c_void) -> HRESULT, - __get_folder: usize, - __get_current_selection: usize, - set_file_name: unsafe extern "system" fn(this: *mut c_void, name: PCWSTR) -> HRESULT, - __get_file_name: usize, - set_title: unsafe extern "system" fn(this: *mut c_void, title: PCWSTR) -> HRESULT, - __set_ok_button_label: usize, - __set_file_name_label: usize, - get_result: - unsafe extern "system" fn(this: *mut c_void, shell_item: *mut IShellItem) -> HRESULT, - __add_place: usize, - set_default_extension: - unsafe extern "system" fn(this: *mut c_void, default_ext: PCWSTR) -> HRESULT, - __close: usize, - __set_client_guid: usize, - __clear_client_data: usize, - __set_filter: usize, -} - -#[repr(C)] -struct IFileDialog(*mut Interface); - -impl Drop for IFileDialog { - fn drop(&mut self) { - drop_impl(self.0.cast()); - } -} - -/// -#[repr(C)] -struct IFileOpenDialogV { - base: IFileDialogV, - /// - get_results: - unsafe extern "system" fn(this: *mut c_void, results: *mut IShellItemArray) -> HRESULT, - __get_selected_items: usize, -} - -struct IFileOpenDialog(*mut Interface); +use std::{ffi::c_void, path::PathBuf}; -impl Drop for IFileOpenDialog { - fn drop(&mut self) { - drop_impl(self.0.cast()); - } -} +use raw_window_handle::RawWindowHandle; enum DialogInner { Open(IFileOpenDialog), @@ -251,7 +68,7 @@ impl DialogInner { } #[inline] - unsafe fn fd(&self) -> (*mut std::ffi::c_void, &IFileDialogV) { + unsafe fn fd(&self) -> (*mut c_void, &IFileDialogV) { match self { Self::Save(s) => unsafe { (s.0.cast(), (*s.0).vtbl()) }, Self::Open(o) => unsafe { (o.0.cast(), &(*o.0).vtbl().base) }, @@ -313,24 +130,12 @@ impl DialogInner { unsafe fn get_results(&self) -> Result> { let Self::Open(od) = self else { unreachable!() }; - let mut res = std::mem::MaybeUninit::uninit(); - wrap_err(((*(*od.0).vtable).get_results)( - od.0.cast(), - res.as_mut_ptr(), - ))?; - let items = res.assume_init(); - - let sia = items.0.cast(); - let svt = &*(*items.0).vtable; - - let mut count = 0; - wrap_err((svt.get_count)(sia, &mut count))?; + let items = od.get_results()?; + let count = items.get_count()?; let mut paths = Vec::with_capacity(count as usize); for index in 0..count { - let mut item = std::mem::MaybeUninit::uninit(); - wrap_err((svt.get_item_at)(sia, index, item.as_mut_ptr()))?; - let item = item.assume_init(); + let item = items.get_item_at(index)?; let path = item.get_path()?; paths.push(path); From 36a09737efbd3a00480c17cfe4d5a14eee245978 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Tue, 25 Apr 2023 15:22:38 +0200 Subject: [PATCH 3/3] Add signatures for unused methods as well --- src/backend/win_cid/file_dialog/com.rs | 146 +++++++++++------- src/backend/win_cid/file_dialog/dialog_ffi.rs | 16 +- 2 files changed, 99 insertions(+), 63 deletions(-) diff --git a/src/backend/win_cid/file_dialog/com.rs b/src/backend/win_cid/file_dialog/com.rs index c8c7065..acb192c 100644 --- a/src/backend/win_cid/file_dialog/com.rs +++ b/src/backend/win_cid/file_dialog/com.rs @@ -1,8 +1,13 @@ +#![allow(non_snake_case)] + use std::ffi::c_void; use windows_sys::core::{HRESULT, PCWSTR, PWSTR}; -pub use windows_sys::Win32::{ - Foundation::HWND, - UI::Shell::{Common::COMDLG_FILTERSPEC, FILEOPENDIALOGOPTIONS, SIGDN, SIGDN_FILESYSPATH}, +pub use windows_sys::{ + core::GUID, + Win32::{ + Foundation::HWND, + UI::Shell::{Common::COMDLG_FILTERSPEC, FILEOPENDIALOGOPTIONS, SIGDN, SIGDN_FILESYSPATH}, + }, }; pub(crate) type Result = std::result::Result; @@ -17,7 +22,7 @@ pub(super) fn wrap_err(hresult: HRESULT) -> Result<()> { } #[inline] -pub(super) unsafe fn read_to_string(ptr: *const u16) -> String { +unsafe fn read_to_string(ptr: *const u16) -> String { let mut cursor = ptr; while *cursor != 0 { @@ -59,12 +64,26 @@ fn drop_impl(ptr: *mut c_void) { #[repr(C)] pub(super) struct IShellItemV { base: IUnknownV, - __bind_to_handler: usize, - __get_parent: usize, - pub(super) get_display_name: - unsafe extern "system" fn(this: *mut c_void, name_look: SIGDN, name: *mut PWSTR) -> HRESULT, - __get_attributes: usize, - __compare: usize, + BindToHandler: unsafe extern "system" fn( + this: *mut c_void, + pbc: *mut c_void, + bhid: *const GUID, + riid: *const GUID, + ppv: *mut *mut c_void, + ) -> HRESULT, + GetParent: unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + GetDisplayName: unsafe extern "system" fn( + this: *mut c_void, + sigdnname: SIGDN, + ppszname: *mut PWSTR, + ) -> HRESULT, + GetAttributes: usize, + Compare: unsafe extern "system" fn( + this: *mut c_void, + psi: *mut c_void, + hint: u32, + piorder: *mut i32, + ) -> HRESULT, } #[repr(transparent)] @@ -74,7 +93,7 @@ impl IShellItem { pub(super) fn get_path(&self) -> Result { let filename = unsafe { let mut dname = std::mem::MaybeUninit::uninit(); - wrap_err(((*self.0).vtbl().get_display_name)( + wrap_err(((*self.0).vtbl().GetDisplayName)( self.0.cast(), SIGDN_FILESYSPATH, dname.as_mut_ptr(), @@ -99,18 +118,24 @@ impl Drop for IShellItem { #[repr(C)] struct IShellItemArrayV { base: IUnknownV, - __bind_to_handler: usize, - __get_property_store: usize, - __get_property_description_list: usize, - __get_attributes: usize, - pub(super) get_count: - unsafe extern "system" fn(this: *mut c_void, num_items: *mut u32) -> HRESULT, - pub(super) get_item_at: unsafe extern "system" fn( + BindToHandler: unsafe extern "system" fn( + this: *mut c_void, + pbc: *mut c_void, + bhid: *const GUID, + riid: *const GUID, + ppvout: *mut *mut c_void, + ) -> HRESULT, + GetPropertyStore: usize, + GetPropertyDescriptionList: usize, + GetAttributes: usize, + GetCount: unsafe extern "system" fn(this: *mut c_void, pdwnumitems: *mut u32) -> HRESULT, + GetItemAt: unsafe extern "system" fn( this: *mut c_void, dwindex: u32, ppsi: *mut IShellItem, ) -> HRESULT, - __enum_items: usize, + EnumItems: + unsafe extern "system" fn(this: *mut c_void, ppenumshellitems: *mut *mut c_void) -> HRESULT, } #[repr(transparent)] @@ -121,7 +146,7 @@ impl IShellItemArray { pub(super) fn get_count(&self) -> Result { let mut count = 0; unsafe { - wrap_err(((*self.0).vtbl().get_count)(self.0.cast(), &mut count))?; + wrap_err(((*self.0).vtbl().GetCount)(self.0.cast(), &mut count))?; } Ok(count) } @@ -130,7 +155,7 @@ impl IShellItemArray { pub(super) fn get_item_at(&self, index: u32) -> Result { let mut item = std::mem::MaybeUninit::uninit(); unsafe { - wrap_err(((*self.0).vtbl().get_item_at)( + wrap_err(((*self.0).vtbl().GetItemAt)( self.0.cast(), index, item.as_mut_ptr(), @@ -149,44 +174,54 @@ impl Drop for IShellItemArray { #[repr(C)] pub(super) struct IModalWindowV { base: IUnknownV, - pub(super) show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, + pub(super) Show: unsafe extern "system" fn(this: *mut c_void, owner: HWND) -> HRESULT, } /// #[repr(C)] pub(super) struct IFileDialogV { pub(super) base: IModalWindowV, - pub(super) set_file_types: unsafe extern "system" fn( + pub(super) SetFileTypes: unsafe extern "system" fn( + this: *mut c_void, + cfiletypes: u32, + rgfilterspec: *const COMDLG_FILTERSPEC, + ) -> HRESULT, + SetFileTypeIndex: unsafe extern "system" fn(this: *mut c_void, ifiletype: u32) -> HRESULT, + GetFileTypeIndex: unsafe extern "system" fn(this: *mut c_void, pifiletype: *mut u32) -> HRESULT, + Advise: unsafe extern "system" fn( + this: *mut c_void, + pfde: *mut c_void, + pdwcookie: *mut u32, + ) -> HRESULT, + Unadvise: unsafe extern "system" fn(this: *mut c_void, dwcookie: u32) -> HRESULT, + pub(super) SetOptions: + unsafe extern "system" fn(this: *mut c_void, fos: FILEOPENDIALOGOPTIONS) -> HRESULT, + GetOptions: + unsafe extern "system" fn(this: *mut c_void, pfos: *mut FILEOPENDIALOGOPTIONS) -> HRESULT, + SetDefaultFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT, + pub(super) SetFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT, + GetFolder: unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + GetCurrentSelection: + unsafe extern "system" fn(this: *mut c_void, ppsi: *mut *mut c_void) -> HRESULT, + pub(super) SetFileName: + unsafe extern "system" fn(this: *mut c_void, pszname: PCWSTR) -> HRESULT, + GetFileName: unsafe extern "system" fn(this: *mut c_void, pszname: *mut PWSTR) -> HRESULT, + pub(super) SetTitle: unsafe extern "system" fn(this: *mut c_void, psztitle: PCWSTR) -> HRESULT, + SetOkButtonLabel: unsafe extern "system" fn(this: *mut c_void, psztext: PCWSTR) -> HRESULT, + SetFileNameLabel: unsafe extern "system" fn(this: *mut c_void, pszlabel: PCWSTR) -> HRESULT, + pub(super) GetResult: + unsafe extern "system" fn(this: *mut c_void, ppsi: *mut IShellItem) -> HRESULT, + AddPlace: unsafe extern "system" fn( this: *mut c_void, - count_filetypes: u32, - filter_spec: *const COMDLG_FILTERSPEC, + psi: *mut c_void, + fdap: windows_sys::Win32::UI::Shell::FDAP, ) -> HRESULT, - __set_file_type_index: usize, - __get_file_type_index: usize, - __advise: usize, - __unadvise: usize, - pub(super) set_options: - unsafe extern "system" fn(this: *mut c_void, options: FILEOPENDIALOGOPTIONS) -> HRESULT, - __get_options: usize, - __set_default_folder: usize, - pub(super) set_folder: - unsafe extern "system" fn(this: *mut c_void, shell_item: *mut c_void) -> HRESULT, - __get_folder: usize, - __get_current_selection: usize, - pub(super) set_file_name: unsafe extern "system" fn(this: *mut c_void, name: PCWSTR) -> HRESULT, - __get_file_name: usize, - pub(super) set_title: unsafe extern "system" fn(this: *mut c_void, title: PCWSTR) -> HRESULT, - __set_ok_button_label: usize, - __set_file_name_label: usize, - pub(super) get_result: - unsafe extern "system" fn(this: *mut c_void, shell_item: *mut IShellItem) -> HRESULT, - __add_place: usize, - pub(super) set_default_extension: - unsafe extern "system" fn(this: *mut c_void, default_ext: PCWSTR) -> HRESULT, - __close: usize, - __set_client_guid: usize, - __clear_client_data: usize, - __set_filter: usize, + pub(super) SetDefaultExtension: + unsafe extern "system" fn(this: *mut c_void, pszdefaultextension: PCWSTR) -> HRESULT, + Close: unsafe extern "system" fn(this: *mut c_void, hr: HRESULT) -> HRESULT, + SetClientGuid: unsafe extern "system" fn(this: *mut c_void, guid: *const GUID) -> HRESULT, + ClearClientData: unsafe extern "system" fn(this: *mut c_void) -> HRESULT, + SetFilter: unsafe extern "system" fn(this: *mut c_void, pfilter: *mut c_void) -> HRESULT, } #[repr(transparent)] @@ -203,9 +238,10 @@ impl Drop for IFileDialog { pub(super) struct IFileOpenDialogV { pub(super) base: IFileDialogV, /// - pub(super) get_results: - unsafe extern "system" fn(this: *mut c_void, results: *mut IShellItemArray) -> HRESULT, - __get_selected_items: usize, + GetResults: + unsafe extern "system" fn(this: *mut c_void, ppenum: *mut IShellItemArray) -> HRESULT, + GetSelectedItems: + unsafe extern "system" fn(this: *mut c_void, ppsai: *mut *mut c_void) -> HRESULT, } #[repr(transparent)] @@ -216,7 +252,7 @@ impl IFileOpenDialog { pub(super) fn get_results(&self) -> Result { let mut res = std::mem::MaybeUninit::uninit(); unsafe { - wrap_err((((*self.0).vtbl()).get_results)( + wrap_err((((*self.0).vtbl()).GetResults)( self.0.cast(), res.as_mut_ptr(), ))?; diff --git a/src/backend/win_cid/file_dialog/dialog_ffi.rs b/src/backend/win_cid/file_dialog/dialog_ffi.rs index a9407b8..c0f73a4 100644 --- a/src/backend/win_cid/file_dialog/dialog_ffi.rs +++ b/src/backend/win_cid/file_dialog/dialog_ffi.rs @@ -78,50 +78,50 @@ impl DialogInner { #[inline] unsafe fn set_options(&self, opts: FILEOPENDIALOGOPTIONS) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_options)(d, opts)) + wrap_err((v.SetOptions)(d, opts)) } #[inline] unsafe fn set_title(&self, title: &[u16]) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_title)(d, title.as_ptr())) + wrap_err((v.SetTitle)(d, title.as_ptr())) } #[inline] unsafe fn set_default_extension(&self, extension: &[u16]) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_default_extension)(d, extension.as_ptr())) + wrap_err((v.SetDefaultExtension)(d, extension.as_ptr())) } #[inline] unsafe fn set_file_types(&self, specs: &[COMDLG_FILTERSPEC]) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_file_types)(d, specs.len() as _, specs.as_ptr())) + wrap_err((v.SetFileTypes)(d, specs.len() as _, specs.as_ptr())) } #[inline] unsafe fn set_filename(&self, fname: &[u16]) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_file_name)(d, fname.as_ptr())) + wrap_err((v.SetFileName)(d, fname.as_ptr())) } #[inline] unsafe fn set_folder(&self, folder: &IShellItem) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.set_folder)(d, folder.0.cast())) + wrap_err((v.SetFolder)(d, folder.0.cast())) } #[inline] unsafe fn show(&self, parent: Option) -> Result<()> { let (d, v) = self.fd(); - wrap_err((v.base.show)(d, parent.unwrap_or_default())) + wrap_err((v.base.Show)(d, parent.unwrap_or_default())) } #[inline] unsafe fn get_result(&self) -> Result { let (d, v) = self.fd(); let mut res = std::mem::MaybeUninit::uninit(); - wrap_err((v.get_result)(d, res.as_mut_ptr()))?; + wrap_err((v.GetResult)(d, res.as_mut_ptr()))?; let res = res.assume_init(); res.get_path() }