Skip to main content

Module portal

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§

FileFilter
One entry in the chooser’s file-type dropdown.
LaunchRequest
Open a local file, or reveal its containing folder, through the XDG OpenURI portal. The portal receives an already-open descriptor rather than a string path, preserving the same capability boundary as file choosing.
OpenedFile
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.
PickRequest
A portal file-chooser request, built up and then send on a worker thread.
PickedContents
One chosen file’s contents, as PickRequest::send_contents delivers it.

Enums§

ContentsOutcome
The result of PickRequest::send_contents.
LaunchOutcome
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.
MultiOutcome
The result of a multi-select portal open.
OpenFileOutcome
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.
PickKind
Which chooser to open.
PortalOutcome
The result of a single-file portal interaction (open, or a Save destination), delivered back to the UI thread over a channel.
SaveOutcome
The result of PickRequest::send_save_contents.
WallpaperTarget
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§

EffectGate
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, a settings: pane) to the current host’s crate::UriOpener.
open_uri_detached
open_uri through the host’s tilo_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::UriOpener runs. Apps retain their own URL policy (for example, allowed link schemes).
open_uri_with
open_uri through an opener the caller resolved itself, for work that runs away from the thread whose host it belongs to: resolve ui::host::current().opener there, then call this on the worker.
replace_saved
Replace the Save destination destination with bytes atomically: write save_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. None for 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’s tilo_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 from PickRequest::send_open_file), for target. 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 None if it does not name a file on this machine.

Type Aliases§

ContentsChannel
A pending contents pick, drained by the UI thread.
LaunchChannel
A pending desktop-open request.
MultiChannel
A pending multi-select request: the receiver the UI thread drains each frame.
OpenFileChannel
A pending descriptor-retaining portal open, drained by the UI thread.
PortalChannel
A pending single-file request: the receiver the UI thread drains each frame. Some(rx) means a picker is in flight; None means idle.
SaveChannel
A pending contents save, drained by the UI thread.
WallpaperChannel
The outcome of asking the desktop to use a picture as the wallpaper. The desktop previews it and asks the user to confirm; Err carries why it did not happen, including the user declining.