diff --git a/CHANGELOG.md b/CHANGELOG.md index c70c0a65..83ad6bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add `FileDialog::set_show_hidden_files` and `AsyncFileDialog::set_show_hidden_files` to control hidden file visibility. Supported on macOS, Windows, and Linux (GTK3). + ## 0.17.2 - Lower MSRV back to 1.88 by @PolyMeilex in https://github.com/PolyMeilex/rfd/pull/303 diff --git a/examples/show_hidden.rs b/examples/show_hidden.rs new file mode 100644 index 00000000..fde82d92 --- /dev/null +++ b/examples/show_hidden.rs @@ -0,0 +1,18 @@ +//! Example demonstrating the show_hidden_files option. +//! +//! Run with: cargo run --example show_hidden + +use rfd::FileDialog; + +fn main() { + // Open a file dialog with hidden files shown + let file = FileDialog::new() + .set_title("Select a file (hidden files visible)") + .set_show_hidden_files(true) + .pick_file(); + + match file { + Some(path) => println!("Selected: {}", path.display()), + None => println!("No file selected"), + } +} diff --git a/src/backend/gtk3/file_dialog/dialog_ffi.rs b/src/backend/gtk3/file_dialog/dialog_ffi.rs index 17f7aab0..bdcc4c1f 100644 --- a/src/backend/gtk3/file_dialog/dialog_ffi.rs +++ b/src/backend/gtk3/file_dialog/dialog_ffi.rs @@ -96,6 +96,14 @@ impl GtkFileDialog { } } + fn set_show_hidden(&self, show: Option) { + if let Some(show) = show { + unsafe { + gtk_sys::gtk_file_chooser_set_show_hidden(self.ptr as _, show as i32); + } + } + } + pub fn get_result(&self) -> Option { let cstr = unsafe { let chosen_filename = gtk_sys::gtk_file_chooser_get_filename(self.ptr as _); @@ -162,6 +170,7 @@ impl GtkFileDialog { dialog.add_filters(&opt.filters); dialog.set_path(opt.starting_directory.as_deref()); + dialog.set_show_hidden(opt.show_hidden_files); if let (Some(mut path), Some(file_name)) = (opt.starting_directory.to_owned(), opt.file_name.as_deref()) @@ -185,6 +194,7 @@ impl GtkFileDialog { dialog.add_filters(&opt.filters); dialog.set_path(opt.starting_directory.as_deref()); + dialog.set_show_hidden(opt.show_hidden_files); if let (Some(mut path), Some(file_name)) = (opt.starting_directory.to_owned(), opt.file_name.as_deref()) @@ -211,6 +221,7 @@ impl GtkFileDialog { GtkFileChooserAction::SelectFolder, ); dialog.set_path(opt.starting_directory.as_deref()); + dialog.set_show_hidden(opt.show_hidden_files); if let (Some(mut path), Some(file_name)) = (opt.starting_directory.to_owned(), opt.file_name.as_deref()) @@ -231,6 +242,7 @@ impl GtkFileDialog { ); unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; dialog.set_path(opt.starting_directory.as_deref()); + dialog.set_show_hidden(opt.show_hidden_files); if let (Some(mut path), Some(file_name)) = (opt.starting_directory.to_owned(), opt.file_name.as_deref()) @@ -253,6 +265,7 @@ impl GtkFileDialog { unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) }; dialog.add_filters(&opt.filters); dialog.set_path(opt.starting_directory.as_deref()); + dialog.set_show_hidden(opt.show_hidden_files); if let (Some(mut path), Some(file_name)) = (opt.starting_directory.to_owned(), opt.file_name.as_deref()) diff --git a/src/backend/macos/file_dialog/panel_ffi.rs b/src/backend/macos/file_dialog/panel_ffi.rs index 76d1fb74..21a28c83 100755 --- a/src/backend/macos/file_dialog/panel_ffi.rs +++ b/src/backend/macos/file_dialog/panel_ffi.rs @@ -104,6 +104,10 @@ trait PanelExt { unsafe { self.panel().setCanCreateDirectories(can) } } + fn set_shows_hidden_files(&self, show: bool) { + unsafe { self.panel().setShowsHiddenFiles(show) } + } + fn add_filters(&self, opt: &FileDialog) { let mut exts: Vec = Vec::new(); @@ -187,6 +191,10 @@ impl Panel { panel.set_can_create_directories(can); } + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(false) }; unsafe { panel.setCanChooseFiles(true) }; @@ -216,6 +224,10 @@ impl Panel { panel.set_can_create_directories(can); } + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + Self::new(panel, opt.parent.as_ref()) } @@ -233,6 +245,10 @@ impl Panel { let can = opt.can_create_directories.unwrap_or(true); panel.set_can_create_directories(can); + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(true) }; unsafe { panel.setCanChooseFiles(false) }; @@ -253,6 +269,10 @@ impl Panel { let can = opt.can_create_directories.unwrap_or(true); panel.set_can_create_directories(can); + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(true) }; unsafe { panel.setCanChooseFiles(false) }; unsafe { panel.setAllowsMultipleSelection(true) }; @@ -279,6 +299,10 @@ impl Panel { panel.set_can_create_directories(can); } + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(false) }; unsafe { panel.setCanChooseFiles(true) }; unsafe { panel.setAllowsMultipleSelection(true) }; @@ -304,6 +328,10 @@ impl Panel { let can = opt.can_create_directories.unwrap_or(true); panel.set_can_create_directories(can); + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(true) }; unsafe { panel.setCanChooseFiles(true) }; unsafe { panel.setAllowsMultipleSelection(false) }; @@ -329,6 +357,10 @@ impl Panel { let can = opt.can_create_directories.unwrap_or(true); panel.set_can_create_directories(can); + if let Some(show) = opt.show_hidden_files { + panel.set_shows_hidden_files(show); + } + unsafe { panel.setCanChooseDirectories(true) }; unsafe { panel.setCanChooseFiles(true) }; unsafe { panel.setAllowsMultipleSelection(true) }; diff --git a/src/backend/win_cid/file_dialog/com.rs b/src/backend/win_cid/file_dialog/com.rs index acb192c4..dab40d1b 100644 --- a/src/backend/win_cid/file_dialog/com.rs +++ b/src/backend/win_cid/file_dialog/com.rs @@ -196,7 +196,7 @@ pub(super) struct IFileDialogV { 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: + pub(super) 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, diff --git a/src/backend/win_cid/file_dialog/dialog_ffi.rs b/src/backend/win_cid/file_dialog/dialog_ffi.rs index 29e9dd33..59208a9a 100644 --- a/src/backend/win_cid/file_dialog/dialog_ffi.rs +++ b/src/backend/win_cid/file_dialog/dialog_ffi.rs @@ -12,7 +12,7 @@ use windows_sys::{ System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER}, UI::Shell::{ FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, FOS_ALLOWMULTISELECT, - FOS_PICKFOLDERS, + FOS_FORCESHOWHIDDEN, FOS_PICKFOLDERS, }, }, }; @@ -81,6 +81,20 @@ impl DialogInner { wrap_err((v.SetOptions)(d, opts)) } + #[inline] + unsafe fn get_options(&self) -> Result { + let (d, v) = self.fd(); + let mut opts = 0; + wrap_err((v.GetOptions)(d, &mut opts))?; + Ok(opts) + } + + #[inline] + unsafe fn add_options(&self, opts: FILEOPENDIALOGOPTIONS) -> Result<()> { + let current = self.get_options()?; + self.set_options(current | opts) + } + #[inline] unsafe fn set_title(&self, title: &[u16]) -> Result<()> { let (d, v) = self.fd(); @@ -273,6 +287,15 @@ impl IDialog { Ok(()) } + fn set_show_hidden_files(&self, show: Option) -> Result<()> { + if let Some(true) = show { + unsafe { + self.0.add_options(FOS_FORCESHOWHIDDEN)?; + } + } + Ok(()) + } + pub fn get_results(&self) -> Result> { unsafe { self.0.get_results() } } @@ -294,6 +317,7 @@ impl IDialog { dialog.set_path(&opt.starting_directory)?; dialog.set_file_name(&opt.file_name)?; dialog.set_title(&opt.title)?; + dialog.set_show_hidden_files(opt.show_hidden_files)?; Ok(dialog) } @@ -305,6 +329,7 @@ impl IDialog { dialog.set_path(&opt.starting_directory)?; dialog.set_file_name(&opt.file_name)?; dialog.set_title(&opt.title)?; + dialog.set_show_hidden_files(opt.show_hidden_files)?; Ok(dialog) } @@ -315,8 +340,13 @@ impl IDialog { dialog.set_path(&opt.starting_directory)?; dialog.set_title(&opt.title)?; + let mut opts = FOS_PICKFOLDERS; + if let Some(true) = opt.show_hidden_files { + opts |= FOS_FORCESHOWHIDDEN; + } + unsafe { - dialog.0.set_options(FOS_PICKFOLDERS)?; + dialog.0.set_options(opts)?; } Ok(dialog) @@ -327,7 +357,11 @@ impl IDialog { dialog.set_path(&opt.starting_directory)?; dialog.set_title(&opt.title)?; - let opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT; + + let mut opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT; + if let Some(true) = opt.show_hidden_files { + opts |= FOS_FORCESHOWHIDDEN; + } unsafe { dialog.0.set_options(opts)?; @@ -344,8 +378,13 @@ impl IDialog { dialog.set_file_name(&opt.file_name)?; dialog.set_title(&opt.title)?; + let mut opts = FOS_ALLOWMULTISELECT; + if let Some(true) = opt.show_hidden_files { + opts |= FOS_FORCESHOWHIDDEN; + } + unsafe { - dialog.0.set_options(FOS_ALLOWMULTISELECT)?; + dialog.0.set_options(opts)?; } Ok(dialog) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 272fc245..291e98f5 100755 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -25,6 +25,7 @@ pub struct FileDialog { pub(crate) parent: Option, pub(crate) parent_display: Option, pub(crate) can_create_directories: Option, + pub(crate) show_hidden_files: Option, } // Oh god, I don't like sending RawWindowHandle between threads but here we go anyways... @@ -108,6 +109,16 @@ impl FileDialog { self.can_create_directories.replace(can); self } + + /// Show hidden files in the dialog. + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (GTK3 only, not XDG Portal) + pub fn set_show_hidden_files(mut self, show: bool) -> Self { + self.show_hidden_files = Some(show); + self + } } #[cfg(not(target_arch = "wasm32"))] @@ -252,6 +263,16 @@ impl AsyncFileDialog { self.file_dialog = self.file_dialog.set_can_create_directories(can); self } + + /// Show hidden files in the dialog. + /// Supported platforms: + /// * Windows + /// * Mac + /// * Linux (GTK3 only, not XDG Portal) + pub fn set_show_hidden_files(mut self, show: bool) -> Self { + self.file_dialog = self.file_dialog.set_show_hidden_files(show); + self + } } #[cfg(target_os = "macos")]