Module portal
Expand description
The file-chooser and URI-opening vocabulary: requests, outcomes and the URI → path rule every backend shares.
§Why this is shared
Per the Tilo HIG an app must never draw its own file picker and never key open/save off an environment variable: it goes through the portal so the host desktop provides the chooser — on GNOME the user sees GNOME’s file dialog; on Tilo the compositor implements the portal backend and renders Tilo’s picker. Windows uses the host’s Open, Save, and folder dialogs. On Tilo, the picker is also where the per-workspace sandbox file-access prompt lives, which makes this module the file-access security boundary. A per-app copy of it is not merely duplicated code: it is a second, independently-drifting definition of which URIs are allowed to become paths the app then reads or writes.
One such drift was live: mapping URIs with uri.strip_prefix("file://")
instead of url::Url::to_file_path turns file://evil-host/etc/passwd
into the relative path evil-host/etc/passwd — silently resolved
against the process’s working directory, from a URI that explicitly named
a remote host. This module validates the host explicitly before converting
the URL; see file_uris_resolve_only_to_local_normalised_paths.
§Threading contract
The portal round-trip is asynchronous and opens a real host window, so it
must never run on the UI thread. PickRequest::send hands the request
to the current host’s crate::FilePicker, which answers over an
std::sync::mpsc channel — the Linux portal from the app runtime, the
macOS panels from the main run loop, the Windows dialogs from a COM
apartment on a worker, the browser from a promise. The app drains that
channel once per frame; the UI thread never blocks. Tests answer requests
through a recording host (ui::host::HostServices::recording).
§Backends
The crate selects the backend per target (see the crate docs). The pure
URI→path mapping (map_uris, map_uri) and the channel types are the
same everywhere, so the security rule stays under test on every target.
Structs§
- File
Filter - One entry in the chooser’s file-type dropdown.
- Launch
Request - Open a local file, or reveal its containing folder, through the XDG
OpenURIportal. The portal receives an already-open descriptor rather than a string path, preserving the same capability boundary as file choosing. - Opened
File - A file selected by the portal and opened by its worker before the result is handed to the UI. The descriptor, rather than the path used to obtain it, is the authority to read the selected file.
- Pick
Request - A portal file-chooser request, built up and then
sendon a worker thread. - Picked
Contents - One chosen file’s contents, as
PickRequest::send_contentsdelivers it.
Enums§
- Contents
Outcome - The result of
PickRequest::send_contents. - Launch
Outcome - Outcome of asking the desktop to open a path with an appropriate app, or reveal its containing directory. The request is fire-and-observe: callers drain this channel without ever blocking the UI thread.
- Multi
Outcome - The result of a multi-select portal open.
- Open
File Outcome - The result of a single-file portal open which retains the selected
descriptor. Unlike
PortalOutcome, this is specifically for consumers that must hand an exact, already-open file to another security boundary. - Pick
Kind - Which chooser to open.
- Portal
Outcome - The result of a single-file portal interaction (open, or a Save destination), delivered back to the UI thread over a channel.
- Save
Outcome - The result of
PickRequest::send_save_contents. - Wallpaper
Target - Where a wallpaper is shown.
Constants§
- SAVE_
DIRECTORY_ ENV - Names the directory a session nobody sits at (a provider’s guest) saves into: a Save chooser answers there under its suggested name, and an Open chooser is refused, since no one is present to choose. Whether the app may write there is still its file grants’.
Traits§
- Effect
Gate - Custody of a delayed host effect. A scoped chooser or URI request carries the caller that asked for it, and the worker rechecks that caller immediately before the host effect runs; an expired caller never reaches the effect.
Functions§
- map_uri
- Map the portal’s returned URIs for a single-file interaction: the first
local
file://path wins, an empty list is a cancel, and a URI that is not a local file is a failure. - map_
uris - Map the portal’s returned URIs for a multi-select open: every local
file://path is collected, an empty list is a cancel, and a selection with no local file at all is a failure. - open_
regular_ file - Open a chosen local path without following its final component and verify the resulting descriptor is a regular file. Checking metadata on the open descriptor, rather than the path, keeps the type check bound to exactly what is handed to the caller.
- open_
uri - Hand a URI (a web page, a
mailto:draft, asettings:pane) to the current host’scrate::UriOpener. - open_
uri_ detached open_urithrough the host’stilo_rt::background::Background. A launch failure is logged, not surfaced.- open_
uri_ scoped - Dispatch one URI effect without blocking the UI thread. The original
admitted caller is rechecked on the worker immediately before the host’s
crate::UriOpenerruns. Apps retain their own URL policy (for example, allowed link schemes). - open_
uri_ with open_urithrough an opener the caller resolved itself, for work that runs away from the thread whose host it belongs to: resolveui::host::current().openerthere, then call this on the worker.- replace_
saved - Replace the Save destination
destinationwithbytesatomically: writesave_sibling, flush it to disk, and rename it over the destination, so a crash leaves the old file or the new one. - save_
sibling - The one temporary file beside a Save destination an app may write and
then rename over it:
<folder>/.<name>.tilo-save. A Seatbelt-confined macOS app is granted exactly this name with the destination, and nothing else in the folder.Nonefor a path that names no file. - send_
open_ uri - Non-blocking
open_uri: the outcome arrives on the returned channel, which the UI thread drains like any other portal result. The request runs through the host’stilo_rt::background::Background; a host that refuses background work reports it as a failed launch. - send_
set_ wallpaper - Ask the desktop’s Wallpaper portal to use
file, an already-open picture (typically fromPickRequest::send_open_file), fortarget. Never blocks: the D-Bus round trip runs on the app runtime. - to_
local_ path - Map one portal-returned URI to a local path, or
Noneif it does not name a file on this machine.
Type Aliases§
- Contents
Channel - A pending contents pick, drained by the UI thread.
- Launch
Channel - A pending desktop-open request.
- Multi
Channel - A pending multi-select request: the receiver the UI thread drains each frame.
- Open
File Channel - A pending descriptor-retaining portal open, drained by the UI thread.
- Portal
Channel - A pending single-file request: the receiver the UI thread drains each
frame.
Some(rx)means a picker is in flight;Nonemeans idle. - Save
Channel - A pending contents save, drained by the UI thread.
- Wallpaper
Channel - The outcome of asking the desktop to use a picture as the wallpaper. The
desktop previews it and asks the user to confirm;
Errcarries why it did not happen, including the user declining.