- Zig 98.3%
- Nix 1.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
mdbus 624d9a3 can address a signal to one peer, which is how the real portal sends Request::Response. The fake portal now does the same, so the test for a portal that ignores handle_token no longer waits for the client's match rule before answering. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01XXfXdgZgdHNKx7ZjYvdtci |
||
| .forgejo/workflows | ||
| docs/adr | ||
| LICENSES | ||
| src | ||
| tests | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| build.zig.zon | ||
| CONTRIBUTING.md | ||
| flake.lock | ||
| flake.nix | ||
| GLOSSARY.md | ||
| README.md | ||
| REUSE.toml | ||
zig-openuri
Open a URI in the user's chosen application from Zig: a web page in their
browser, a mailto: link in their mail client, a local file in whatever opens
files of that type. It does what clicking a link in a document does.
const openuri = @import("openuri");
switch (try openuri.open(io, gpa, environ, "https://ziglang.org/", .{})) {
.opened => {},
.cancelled => {}, // the user dismissed the choice of application
}
The API reference is generated from the doc comments and published at https://jeff.jcollie.page/zig-openuri/.
How each platform opens a URI
| Platform | Mechanism |
|---|---|
| Linux | The org.freedesktop.portal.OpenURI desktop portal over the session bus, through mdbus. file: URIs go to the portal's OpenFile, which receives the file as an open descriptor. |
| Windows | ShellExecuteExW with the open verb. |
| macOS | LSOpenCFURLRef from Launch Services. |
The portal is what makes this work from inside a Flatpak or Snap sandbox. It
also lets the desktop ask the user which application to use, so on Linux the
outcome can be that they cancelled. When there is no portal, open
returns error.PortalUnavailable. It does not fall back to running
xdg-open (why). A program that wants
that fallback can catch the error and run xdg-open itself.
On Linux, open connects to the session bus for the length of the call. A
program that already holds an mdbus connection can pass it to
openWithConnection. Only the portal's own reply and signal are taken from
the connection, and everything else stays queued for the caller.
Options
| Option | Platforms | Meaning |
|---|---|---|
schemes |
all | The only schemes that may be opened, or null for any. |
parent_window |
Linux | The caller's X11 window ID, or xdg-foreign handle on Wayland, so that a dialog from the portal appears over it. |
activation_token |
Linux | Lets the opened application's window take focus. A program started from a launcher finds one in XDG_ACTIVATION_TOKEN. |
ask |
Linux | Always ask which application to use. |
writable |
Linux | Open a file: URI for an application that edits it. |
timeout_ms |
Linux | How long to wait for the portal (default two minutes, null for no limit). |
Opening URIs you did not write
A URI from a chat message, a web page or text in a terminal is untrusted.
Handing it to the operating system's opener can run a program. On Windows,
C:\x\evil.exe is a valid argument to ShellExecuteExW, and a file: URI
naming a program runs it on Windows and macOS. So zig-openuri:
- always refuses single-letter schemes, which are Windows drive letters rather than schemes;
- refuses URIs containing spaces, control characters, or invalid UTF-8;
- takes an allowlist in
Options.schemes, so that a program can say, for example, that it opens onlyhttp,httpsandmailto.
A program opening untrusted URIs should always pass an allowlist. The reasons are recorded in ADR 0001.
The openuri command
$ openuri [--ask] [--timeout=SECONDS] [--scheme=NAME]... URI
It exits 0 when the URI was opened, 1 when the choice of application was
cancelled, and 2 on any error. On Linux it passes on the activation token from
XDG_ACTIVATION_TOKEN.
Using it
Requires Zig 0.17.
$ zig fetch --save git+https://git.jcollie.dev/jeff/zig-openuri.git
const openuri = b.dependency("openuri", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("openuri", openuri.module("openuri"));
The module links what each platform needs: shell32 on Windows, and
CoreFoundation and CoreServices on macOS. Linking for macOS therefore needs
Apple's SDK, either by building on a Mac or by passing --sysroot. On Windows,
some protocol handlers need COM, so a program should initialize COM on the
calling thread itself if it opens arbitrary schemes.
Building
$ nix develop
$ zig build # the openuri command, in zig-out/bin
$ zig build test # unit tests, and the portal tests on Linux
$ zig build check # compile for Linux, Windows and macOS on x86_64 and aarch64
$ zig build docs-serve # read the API documentation at http://localhost:8000/
On Linux the portal tests start a private dbus-daemon with a fake portal on
it. They skip themselves when dbus-daemon is not on PATH, which the
devshell provides.
Where this lives
The canonical repository is on my Forgejo instance, and Tangled carries a mirror:
$ git clone https://git.jcollie.dev/jeff/zig-openuri.git
$ git clone https://tangled.org/jcollie.dev/zig-openuri
Issues and pull requests
Please report issues and send pull requests on the Tangled mirror: https://tangled.org/jcollie.dev/zig-openuri. See CONTRIBUTING.md for how to work on the code.
License
MIT. The project follows the REUSE specification.
References cited
- Apple Inc. (n.d.). LSOpenCFURLRef(_:_:). Apple Developer Documentation. https://developer.apple.com/documentation/coreservices/1442850-lsopencfurlref
- Berners-Lee, T., Fielding, R., & Masinter, L. (2005). Uniform Resource Identifier (URI): Generic Syntax (RFC 3986). RFC Editor. https://doi.org/10.17487/RFC3986
- freedesktop.org. (n.d.). D-Bus Specification. https://dbus.freedesktop.org/doc/dbus-specification.html
- Kerwin, M. (2017). The "file" URI Scheme (RFC 8089). RFC Editor. https://doi.org/10.17487/RFC8089
- Microsoft. (n.d.). ShellExecuteExW function (shellapi.h). Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecuteexw
- Microsoft. (n.d.). SHELLEXECUTEINFOW structure (shellapi.h). Microsoft Learn. https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfow
- Wayland Protocols contributors. (n.d.). XDG activation protocol (xdg-activation-v1). Wayland Explorer. https://wayland.app/protocols/xdg-activation-v1
- XDG Desktop Portal contributors. (n.d.). org.freedesktop.portal.OpenURI. XDG Desktop Portal documentation. https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html
- XDG Desktop Portal contributors. (n.d.). org.freedesktop.portal.Request. XDG Desktop Portal documentation. https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html
- XDG Desktop Portal contributors. (n.d.). Window Identifiers. XDG Desktop Portal documentation. https://flatpak.github.io/xdg-desktop-portal/docs/window-identifiers.html