Skip to content

Commit 0fd2ab1

Browse files
authored
feat: Add set_show_hidden_files option for file dialogs (#306)
Add FileDialog::set_show_hidden_files() and AsyncFileDialog::set_show_hidden_files() methods to control whether hidden files (dotfiles) are displayed in file picker dialogs. Platform support: - macOS: Uses NSOpenPanel.setShowsHiddenFiles - Windows: Uses IFileDialog FOS_FORCESHOWHIDDEN flag - Linux (GTK3): Uses gtk_file_chooser_set_show_hidden - Linux (XDG Portal): Not supported (option is ignored) - WASM: Not applicable (browser controls this)
1 parent cca0cfa commit 0fd2ab1

File tree

7 files changed

+130
-5
lines changed

7 files changed

+130
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add `FileDialog::set_show_hidden_files` and `AsyncFileDialog::set_show_hidden_files` to control hidden file visibility. Supported on macOS, Windows, and Linux (GTK3).
6+
57
## 0.17.2
68

79
- Lower MSRV back to 1.88 by @PolyMeilex in https://github.com/PolyMeilex/rfd/pull/303

examples/show_hidden.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! Example demonstrating the show_hidden_files option.
2+
//!
3+
//! Run with: cargo run --example show_hidden
4+
5+
use rfd::FileDialog;
6+
7+
fn main() {
8+
// Open a file dialog with hidden files shown
9+
let file = FileDialog::new()
10+
.set_title("Select a file (hidden files visible)")
11+
.set_show_hidden_files(true)
12+
.pick_file();
13+
14+
match file {
15+
Some(path) => println!("Selected: {}", path.display()),
16+
None => println!("No file selected"),
17+
}
18+
}

src/backend/gtk3/file_dialog/dialog_ffi.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ impl GtkFileDialog {
9696
}
9797
}
9898

99+
fn set_show_hidden(&self, show: Option<bool>) {
100+
if let Some(show) = show {
101+
unsafe {
102+
gtk_sys::gtk_file_chooser_set_show_hidden(self.ptr as _, show as i32);
103+
}
104+
}
105+
}
106+
99107
pub fn get_result(&self) -> Option<PathBuf> {
100108
let cstr = unsafe {
101109
let chosen_filename = gtk_sys::gtk_file_chooser_get_filename(self.ptr as _);
@@ -162,6 +170,7 @@ impl GtkFileDialog {
162170

163171
dialog.add_filters(&opt.filters);
164172
dialog.set_path(opt.starting_directory.as_deref());
173+
dialog.set_show_hidden(opt.show_hidden_files);
165174

166175
if let (Some(mut path), Some(file_name)) =
167176
(opt.starting_directory.to_owned(), opt.file_name.as_deref())
@@ -185,6 +194,7 @@ impl GtkFileDialog {
185194

186195
dialog.add_filters(&opt.filters);
187196
dialog.set_path(opt.starting_directory.as_deref());
197+
dialog.set_show_hidden(opt.show_hidden_files);
188198

189199
if let (Some(mut path), Some(file_name)) =
190200
(opt.starting_directory.to_owned(), opt.file_name.as_deref())
@@ -211,6 +221,7 @@ impl GtkFileDialog {
211221
GtkFileChooserAction::SelectFolder,
212222
);
213223
dialog.set_path(opt.starting_directory.as_deref());
224+
dialog.set_show_hidden(opt.show_hidden_files);
214225

215226
if let (Some(mut path), Some(file_name)) =
216227
(opt.starting_directory.to_owned(), opt.file_name.as_deref())
@@ -231,6 +242,7 @@ impl GtkFileDialog {
231242
);
232243
unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) };
233244
dialog.set_path(opt.starting_directory.as_deref());
245+
dialog.set_show_hidden(opt.show_hidden_files);
234246

235247
if let (Some(mut path), Some(file_name)) =
236248
(opt.starting_directory.to_owned(), opt.file_name.as_deref())
@@ -253,6 +265,7 @@ impl GtkFileDialog {
253265
unsafe { gtk_sys::gtk_file_chooser_set_select_multiple(dialog.ptr as _, 1) };
254266
dialog.add_filters(&opt.filters);
255267
dialog.set_path(opt.starting_directory.as_deref());
268+
dialog.set_show_hidden(opt.show_hidden_files);
256269

257270
if let (Some(mut path), Some(file_name)) =
258271
(opt.starting_directory.to_owned(), opt.file_name.as_deref())

src/backend/macos/file_dialog/panel_ffi.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ trait PanelExt {
104104
unsafe { self.panel().setCanCreateDirectories(can) }
105105
}
106106

107+
fn set_shows_hidden_files(&self, show: bool) {
108+
unsafe { self.panel().setShowsHiddenFiles(show) }
109+
}
110+
107111
fn add_filters(&self, opt: &FileDialog) {
108112
let mut exts: Vec<String> = Vec::new();
109113

@@ -187,6 +191,10 @@ impl Panel {
187191
panel.set_can_create_directories(can);
188192
}
189193

194+
if let Some(show) = opt.show_hidden_files {
195+
panel.set_shows_hidden_files(show);
196+
}
197+
190198
unsafe { panel.setCanChooseDirectories(false) };
191199
unsafe { panel.setCanChooseFiles(true) };
192200

@@ -216,6 +224,10 @@ impl Panel {
216224
panel.set_can_create_directories(can);
217225
}
218226

227+
if let Some(show) = opt.show_hidden_files {
228+
panel.set_shows_hidden_files(show);
229+
}
230+
219231
Self::new(panel, opt.parent.as_ref())
220232
}
221233

@@ -233,6 +245,10 @@ impl Panel {
233245
let can = opt.can_create_directories.unwrap_or(true);
234246
panel.set_can_create_directories(can);
235247

248+
if let Some(show) = opt.show_hidden_files {
249+
panel.set_shows_hidden_files(show);
250+
}
251+
236252
unsafe { panel.setCanChooseDirectories(true) };
237253
unsafe { panel.setCanChooseFiles(false) };
238254

@@ -253,6 +269,10 @@ impl Panel {
253269
let can = opt.can_create_directories.unwrap_or(true);
254270
panel.set_can_create_directories(can);
255271

272+
if let Some(show) = opt.show_hidden_files {
273+
panel.set_shows_hidden_files(show);
274+
}
275+
256276
unsafe { panel.setCanChooseDirectories(true) };
257277
unsafe { panel.setCanChooseFiles(false) };
258278
unsafe { panel.setAllowsMultipleSelection(true) };
@@ -279,6 +299,10 @@ impl Panel {
279299
panel.set_can_create_directories(can);
280300
}
281301

302+
if let Some(show) = opt.show_hidden_files {
303+
panel.set_shows_hidden_files(show);
304+
}
305+
282306
unsafe { panel.setCanChooseDirectories(false) };
283307
unsafe { panel.setCanChooseFiles(true) };
284308
unsafe { panel.setAllowsMultipleSelection(true) };
@@ -304,6 +328,10 @@ impl Panel {
304328
let can = opt.can_create_directories.unwrap_or(true);
305329
panel.set_can_create_directories(can);
306330

331+
if let Some(show) = opt.show_hidden_files {
332+
panel.set_shows_hidden_files(show);
333+
}
334+
307335
unsafe { panel.setCanChooseDirectories(true) };
308336
unsafe { panel.setCanChooseFiles(true) };
309337
unsafe { panel.setAllowsMultipleSelection(false) };
@@ -329,6 +357,10 @@ impl Panel {
329357
let can = opt.can_create_directories.unwrap_or(true);
330358
panel.set_can_create_directories(can);
331359

360+
if let Some(show) = opt.show_hidden_files {
361+
panel.set_shows_hidden_files(show);
362+
}
363+
332364
unsafe { panel.setCanChooseDirectories(true) };
333365
unsafe { panel.setCanChooseFiles(true) };
334366
unsafe { panel.setAllowsMultipleSelection(true) };

src/backend/win_cid/file_dialog/com.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ pub(super) struct IFileDialogV {
196196
Unadvise: unsafe extern "system" fn(this: *mut c_void, dwcookie: u32) -> HRESULT,
197197
pub(super) SetOptions:
198198
unsafe extern "system" fn(this: *mut c_void, fos: FILEOPENDIALOGOPTIONS) -> HRESULT,
199-
GetOptions:
199+
pub(super) GetOptions:
200200
unsafe extern "system" fn(this: *mut c_void, pfos: *mut FILEOPENDIALOGOPTIONS) -> HRESULT,
201201
SetDefaultFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT,
202202
pub(super) SetFolder: unsafe extern "system" fn(this: *mut c_void, psi: *mut c_void) -> HRESULT,

src/backend/win_cid/file_dialog/dialog_ffi.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use windows_sys::{
1212
System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
1313
UI::Shell::{
1414
FileOpenDialog, FileSaveDialog, SHCreateItemFromParsingName, FOS_ALLOWMULTISELECT,
15-
FOS_PICKFOLDERS,
15+
FOS_FORCESHOWHIDDEN, FOS_PICKFOLDERS,
1616
},
1717
},
1818
};
@@ -81,6 +81,20 @@ impl DialogInner {
8181
wrap_err((v.SetOptions)(d, opts))
8282
}
8383

84+
#[inline]
85+
unsafe fn get_options(&self) -> Result<FILEOPENDIALOGOPTIONS> {
86+
let (d, v) = self.fd();
87+
let mut opts = 0;
88+
wrap_err((v.GetOptions)(d, &mut opts))?;
89+
Ok(opts)
90+
}
91+
92+
#[inline]
93+
unsafe fn add_options(&self, opts: FILEOPENDIALOGOPTIONS) -> Result<()> {
94+
let current = self.get_options()?;
95+
self.set_options(current | opts)
96+
}
97+
8498
#[inline]
8599
unsafe fn set_title(&self, title: &[u16]) -> Result<()> {
86100
let (d, v) = self.fd();
@@ -273,6 +287,15 @@ impl IDialog {
273287
Ok(())
274288
}
275289

290+
fn set_show_hidden_files(&self, show: Option<bool>) -> Result<()> {
291+
if let Some(true) = show {
292+
unsafe {
293+
self.0.add_options(FOS_FORCESHOWHIDDEN)?;
294+
}
295+
}
296+
Ok(())
297+
}
298+
276299
pub fn get_results(&self) -> Result<Vec<PathBuf>> {
277300
unsafe { self.0.get_results() }
278301
}
@@ -294,6 +317,7 @@ impl IDialog {
294317
dialog.set_path(&opt.starting_directory)?;
295318
dialog.set_file_name(&opt.file_name)?;
296319
dialog.set_title(&opt.title)?;
320+
dialog.set_show_hidden_files(opt.show_hidden_files)?;
297321

298322
Ok(dialog)
299323
}
@@ -305,6 +329,7 @@ impl IDialog {
305329
dialog.set_path(&opt.starting_directory)?;
306330
dialog.set_file_name(&opt.file_name)?;
307331
dialog.set_title(&opt.title)?;
332+
dialog.set_show_hidden_files(opt.show_hidden_files)?;
308333

309334
Ok(dialog)
310335
}
@@ -315,8 +340,13 @@ impl IDialog {
315340
dialog.set_path(&opt.starting_directory)?;
316341
dialog.set_title(&opt.title)?;
317342

343+
let mut opts = FOS_PICKFOLDERS;
344+
if let Some(true) = opt.show_hidden_files {
345+
opts |= FOS_FORCESHOWHIDDEN;
346+
}
347+
318348
unsafe {
319-
dialog.0.set_options(FOS_PICKFOLDERS)?;
349+
dialog.0.set_options(opts)?;
320350
}
321351

322352
Ok(dialog)
@@ -327,7 +357,11 @@ impl IDialog {
327357

328358
dialog.set_path(&opt.starting_directory)?;
329359
dialog.set_title(&opt.title)?;
330-
let opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT;
360+
361+
let mut opts = FOS_PICKFOLDERS | FOS_ALLOWMULTISELECT;
362+
if let Some(true) = opt.show_hidden_files {
363+
opts |= FOS_FORCESHOWHIDDEN;
364+
}
331365

332366
unsafe {
333367
dialog.0.set_options(opts)?;
@@ -344,8 +378,13 @@ impl IDialog {
344378
dialog.set_file_name(&opt.file_name)?;
345379
dialog.set_title(&opt.title)?;
346380

381+
let mut opts = FOS_ALLOWMULTISELECT;
382+
if let Some(true) = opt.show_hidden_files {
383+
opts |= FOS_FORCESHOWHIDDEN;
384+
}
385+
347386
unsafe {
348-
dialog.0.set_options(FOS_ALLOWMULTISELECT)?;
387+
dialog.0.set_options(opts)?;
349388
}
350389

351390
Ok(dialog)

src/file_dialog.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct FileDialog {
2525
pub(crate) parent: Option<RawWindowHandle>,
2626
pub(crate) parent_display: Option<RawDisplayHandle>,
2727
pub(crate) can_create_directories: Option<bool>,
28+
pub(crate) show_hidden_files: Option<bool>,
2829
}
2930

3031
// Oh god, I don't like sending RawWindowHandle between threads but here we go anyways...
@@ -108,6 +109,16 @@ impl FileDialog {
108109
self.can_create_directories.replace(can);
109110
self
110111
}
112+
113+
/// Show hidden files in the dialog.
114+
/// Supported platforms:
115+
/// * Windows
116+
/// * Mac
117+
/// * Linux (GTK3 only, not XDG Portal)
118+
pub fn set_show_hidden_files(mut self, show: bool) -> Self {
119+
self.show_hidden_files = Some(show);
120+
self
121+
}
111122
}
112123

113124
#[cfg(not(target_arch = "wasm32"))]
@@ -252,6 +263,16 @@ impl AsyncFileDialog {
252263
self.file_dialog = self.file_dialog.set_can_create_directories(can);
253264
self
254265
}
266+
267+
/// Show hidden files in the dialog.
268+
/// Supported platforms:
269+
/// * Windows
270+
/// * Mac
271+
/// * Linux (GTK3 only, not XDG Portal)
272+
pub fn set_show_hidden_files(mut self, show: bool) -> Self {
273+
self.file_dialog = self.file_dialog.set_show_hidden_files(show);
274+
self
275+
}
255276
}
256277

257278
#[cfg(target_os = "macos")]

0 commit comments

Comments
 (0)