Use `PathBuf` instead of `String` to represent paths
OpenMultipleFile, OpenSingleDir and OpenSingleFile use Strings to represent paths. This may make any program using this library unable to open certain (perfectly valid) paths since they'll get mangled by the Windows backend and return an error on the Linux backend.
While this shouldn't be a problem in most cases (that I can think of, anyway), this will eventually become a problem for someone.
The return type of file dialogs has been changed to PathBuf in master branch. Currently I haven't tested the code with paths containing special characters that may be invalid in Rust's String. Could you give me some examples?
BTW, I think the Windows and macOS implementations should be correct. The Windows implementation calls the wfd crate, and it calls COM API IShellItem::GetDisplayName under the hood, whose result should always be valid Unicode strings according to the MSDN. The macOS implementation calls AppleScript, and the result is serialized to JSON, so it should be valid UTF-16 strings too.
On Windows, you can use the following program to create a file with a name which is invalid UTF-16 and test if native-dialog-rs and wfd are doing the right thing:
[dependencies]
native-dialog = { git = "https://github.com/balthild/native-dialog-rs", rev = "eea0b02" }
use std::{env, ffi::OsString, fs, os::windows::ffi::OsStringExt};
use native_dialog::{Dialog, OpenSingleFile};
fn main() {
let mut string: Vec<u16> = "I am a very naughty file name".encode_utf16().collect();
// Insert unpaired (low) surrogate.
string.insert(1, 0xDC00);
assert!(String::from_utf16(&string).is_err());
let string = OsString::from_wide(&string);
assert!(matches!(fs::write(&string, &[]), Ok(())));
let dir_path = env::current_dir().unwrap();
let expected_path = dir_path.join(string);
let dir = dir_path.to_str();
// You should select the file that was just created here:
let result = OpenSingleFile { dir, filter: None }.show();
assert!(matches!(result, Ok(_)));
assert_eq!(result.unwrap(), Some(expected_path));
}
Visual Studio Code refused to open the file that the program created, since VSCode seems to be doing what native-dialog-rs has been doing up until now (that is, replacing invalid code points with U+FFFD REPLACEMENT CHARACTER (�)).
This test unfortunately panicked. native-dialog-rs was, however, not the culprit. The panic originated inside wfd, which was caused by String::from_utf16(&utf16).unwrap().to_string() where utf16 is the path to the test file. I've submitted a PR to get this fixed, but I'm not sure how quickly it will be fixed since the owner and only contributor seems to be inactive, if the contributions shown on his GitHub profile is any indicator. I'll see if I can contact him by e-mail if he doesn't respond within two weeks.
I can't really test anything on macOS since I don't have a suitable machine, but I haven't heard of this being a real problem on macOS. Assuming that the only way to open file dialogs on macOS is through AppleScript, then we can only hope that AppleScript doesn't mishandle file paths. Given that the macOS backend transforms the output of the AppleScript script to a PathBuf and that PathBuf should treat its contents as a mostly-opaque sequence of bytes, it should be about as correct (or more correct, even) as most macOS programs that use native file dialogs. I'm also assuming that the AppleScript script is 100% correct since I have no knowledge of AppleScript.
PS
Windows paths are apparently not UTF-16, but rather UCS-2, according to RFC 474 (path-reform)
Not inactive, just not contributing to projects on GitHub much lately :) PR merged and wfd 0.1.4 released, thanks for the fix - nice to see wfd getting some use, I created it originally for myself when writing a Game Boy emulator in Rust and getting annoyed at dealing with COM APIs for just showing an open dialog - figured others might run across the same problem.
Great stuff!
While waiting for an answer from you, @ben-wallis, I had a look around on crates.io and it seems like wfd is the only "pure" Rust crate for file dialogs on Windows. Similarly, native-dialog seems to be the only cross-platform "pure" Rust crate for both file dialogs and message boxes.
I've put the result of my search onto the unofficial community wiki.
I have some further concerns about the public APIs and how both native-dialog and wdf use &str to set the default folder, which inhibits selecting a default directory programmatically for the same reasons I opened this issue. Exposing some platform-specific options is also something I've been thinking about. I should probably open separate issues for these concerns/ideas.
Hello, I've upgraded wfd dep to 0.1.4 and the crate should return special paths properly on Windows now.
However, the dir param for dialogs are still &strs. I think we should change them to Path too. This is an API change that is not backward compatiable. let's keep the issue open.
let's keep the issue open.
Alright.
Changing dir to take a Path requires changes to wfd, but I'm not sure how many of DialogParams's fields should take Path instead of str. native-dialog only uses default_dir, but wfd would benefit from supporting Path in more places. I'll probably end up testing every field properly some time in the next couple of weeeks and submit a PR to wfd.
In the interim, I'll be using my own forked versions of both wfd and native-dialog.
It should probably be noted that file extensions could potentially contain invalid UTF-16, but that's probably not something to be overly concerned about in the case of the filters parameter.