filepicker
The system's own file chooser.
[dependencies]
filepicker = "*"
Use cpc pm add . filepicker to write the platform-specific dependency
closure.
import "filepicker/filepicker" as fp;
fn picked(p: fp::Pick, ctx: *u8) {
if !p.chose() { return; } // they cancelled — an answer, not an error
open_it(p.path);
}
let _o: fp::Outcome = fp::open(picked, types: "png,jpg");
Asynchronous everywhere
Every platform hands the screen to another process and answers later, so there
is no blocking form — not even on macOS, where runModal exists. A modal run
loop inside a facet app is a reentrancy problem, not a convenience.
The return value is whether the picker opened. The choice arrives on the handler.
A cancel is an answer
The handler runs either way, with an empty path. chose() is the check.
path is not always a path
On Android it is a content:// URI from the Storage Access Framework — opaque,
provider-owned, and not something fs::open_read can take. Read it with
ContentResolver.openInputStream. Handing back an invented filesystem path
would be a lie.
Coverage
| macOS | iOS | Android | Linux | Windows | |
|---|---|---|---|---|---|
open |
✅ | ✅ | ✅ | ✅ XDG portal | ✅ GetOpenFileNameW |
save |
✅ | ❌ no such picker | ✅ CREATE_DOCUMENT |
✅ XDG portal | ✅ GetSaveFileNameW |
types filter |
✅ extensions | ❌ ignored | ✅ one MIME family | ✅ extension globs | ❌ not wired yet — every file shown |
| returned value | path | path | content:// URI |
decoded file path | path |
Tests
cd vendor/filepicker && cpc test
A visible picker cannot be automated in the unit suite; pure mapping and validation paths are tested.