diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c20ede..2820cea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Added `FileDialog::update_with_right_panel_ui` to add a custom right panel to the file dialog [#170](https://github.com/fluxxcode/egui-file-dialog/pull/170) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!) - Added `FileDialog::active_selected_entries` and `FileDialog::active_entry` to get information about the current active item/s [#170](https://github.com/fluxxcode/egui-file-dialog/pull/170) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!) - Added option to show system files in the hamburger menu [#173](https://github.com/fluxxcode/egui-file-dialog/pull/173) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!) +- Support mapped network devices on Windows [#189](https://github.com/fluxxcode/egui-file-dialog/pull/189) ### 🐛 Bug Fixes - Fixed heading `Places` not being able to be updated with `FileDialogLabels` [#180](https://github.com/fluxxcode/egui-file-dialog/pull/180) diff --git a/Cargo.toml b/Cargo.toml index 24eca5eb..7f61dfe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ serde = ["dep:serde"] default_fonts = ["egui/default_fonts"] [lints.rust] -unsafe_code = "forbid" +unsafe_code = "warn" [lints.clippy] nursery = { level = "deny", priority = 0 } diff --git a/src/data/disks.rs b/src/data/disks.rs index c5769ab5..db1f177c 100644 --- a/src/data/disks.rs +++ b/src/data/disks.rs @@ -11,15 +11,32 @@ pub struct Disk { } impl Disk { - /// Create a new Disk object based on the data of a `sysinfo::Disk`. - pub fn from_sysinfo_disk(disk: &sysinfo::Disk, canonicalize_paths: bool) -> Self { + pub fn new( + name: Option<&str>, + mount_point: &Path, + is_removable: bool, + canonicalize_paths: bool, + ) -> Self { Self { - mount_point: Self::canonicalize(disk.mount_point(), canonicalize_paths), - display_name: gen_display_name(disk), - is_removable: disk.is_removable(), + mount_point: canonicalize(mount_point, canonicalize_paths), + display_name: gen_display_name( + name.unwrap_or_default(), + mount_point.to_str().unwrap_or_default(), + ), + is_removable, } } + /// Create a new Disk object based on the data of a `sysinfo::Disk`. + pub fn from_sysinfo_disk(disk: &sysinfo::Disk, canonicalize_paths: bool) -> Self { + Self::new( + disk.name().to_str(), + disk.mount_point(), + disk.is_removable(), + canonicalize_paths, + ) + } + /// Returns the mount point of the disk pub fn mount_point(&self) -> &Path { &self.mount_point @@ -33,16 +50,6 @@ impl Disk { pub const fn is_removable(&self) -> bool { self.is_removable } - - /// Canonicalizes the given path. - /// Returns the input path in case of an error. - fn canonicalize(path: &Path, canonicalize: bool) -> PathBuf { - if canonicalize { - dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) - } else { - path.to_path_buf() - } - } } /// Wrapper above the `sysinfo::Disks` struct @@ -54,14 +61,9 @@ pub struct Disks { impl Disks { /// Creates a new Disks object with a refreshed list of the system disks. pub fn new_with_refreshed_list(canonicalize_paths: bool) -> Self { - let disks = sysinfo::Disks::new_with_refreshed_list(); - - let mut result: Vec = Vec::new(); - for disk in &disks { - result.push(Disk::from_sysinfo_disk(disk, canonicalize_paths)); + Self { + disks: load_disks(canonicalize_paths), } - - Self { disks: result } } /// Very simple wrapper method of the disks `.iter()` method. @@ -71,18 +73,19 @@ impl Disks { } } +/// Canonicalizes the given path. +/// Returns the input path in case of an error. +fn canonicalize(path: &Path, canonicalize: bool) -> PathBuf { + if canonicalize { + dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) + } else { + path.to_path_buf() + } +} + #[cfg(windows)] -fn gen_display_name(disk: &sysinfo::Disk) -> String { - // TODO: Get display name of the devices. - // Currently on Windows it returns an empty string for the C:\\ drive. - - let mut name = disk.name().to_str().unwrap_or_default().to_string(); - let mount_point = disk - .mount_point() - .to_str() - .unwrap_or_default() - .to_string() - .replace('\\', ""); +fn gen_display_name(name: &str, mount_point: &str) -> String { + let mount_point = mount_point.replace('\\', ""); // Try using the mount point as the display name if the specified name // from sysinfo::Disk is empty or contains invalid characters @@ -90,20 +93,60 @@ fn gen_display_name(disk: &sysinfo::Disk) -> String { return mount_point; } - name.push_str(format!(" ({mount_point})").as_str()); - - name + format!("{name} ({mount_point})") } #[cfg(not(windows))] -fn gen_display_name(disk: &sysinfo::Disk) -> String { - let name = disk.name().to_str().unwrap_or_default().to_string(); - +fn gen_display_name(name: &str, mount_point: &str) -> String { // Try using the mount point as the display name if the specified name // from sysinfo::Disk is empty or contains invalid characters if name.is_empty() { - return disk.mount_point().to_str().unwrap_or_default().to_string(); + return mount_point.to_string(); } - name + name.to_string() +} + +#[cfg(windows)] +fn load_disks(canonicalize_paths: bool) -> Vec { + let mut disks: Vec = sysinfo::Disks::new_with_refreshed_list() + .iter() + .map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths)) + .collect(); + + // `sysinfo::Disks` currently do not include mapped network drives on Windows. + // We will load all other available drives using the Windows API. + // However, the sysinfo disks have priority, we are just adding to the list. + #[allow(unsafe_code)] + let mut drives = unsafe { GetLogicalDrives() }; + let mut letter = b'A'; + + while drives > 0 { + if drives & 1 != 0 { + let path = PathBuf::from(format!("{}:\\", letter as char)); + let mount_point = canonicalize(&path, canonicalize_paths); + + if !disks.iter().any(|d| d.mount_point == mount_point) { + disks.push(Disk::new(None, &path, false, canonicalize_paths)); + } + } + + drives >>= 1; + letter += 1; + } + + disks +} + +#[cfg(windows)] +extern "C" { + pub fn GetLogicalDrives() -> u32; +} + +#[cfg(not(windows))] +fn load_disks(canonicalize_paths: bool) -> Vec { + sysinfo::Disks::new_with_refreshed_list() + .iter() + .map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths)) + .collect() } diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 9a17939f..d6fc3ed8 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -237,6 +237,9 @@ impl FileDialog { pub fn with_config(config: FileDialogConfig) -> Self { let mut obj = Self::new(); *obj.config_mut() = config; + + obj.refresh(); + obj } @@ -610,8 +613,12 @@ impl FileDialog { /// you know what you are doing and have a reason for it. /// Disabling canonicalization can lead to unexpected behavior, for example if an /// already canonicalized path is then set as the initial directory. - pub const fn canonicalize_paths(mut self, canonicalize: bool) -> Self { + pub fn canonicalize_paths(mut self, canonicalize: bool) -> Self { self.config.canonicalize_paths = canonicalize; + + // Reload data like system disks and user directories with the updated canonicalization. + self.refresh(); + self }