launch
import launch from "launch";
import ui from "ui";
import io from "io";
fn handed_over(args: []const []const u8) {
// a second start, or a chat://... link clicked in the browser
for (i in 0..args.len) { io.println("handed: {args[i]}"); }
ui.activate_window(); // opened again if closed, brought to the front
}
component App { view { ui.Column {} } }
fn main() -> i32 {
let c: launch.Claim = launch.claim("com.example.chat", handed_over) or { return 1; };
if (c == launch.Claim.HandedOver) { return 0; } // the running one has our arguments
launch.register_scheme("chat") or { io.println("no scheme: {err}"); };
ui.run_window(App {}, ui.Window { title: "Chat"; keep_running: true; }) or { return 1; };
launch.release();
return 0;
}One running instance
claim(app_id, on_handover) makes the program the one instance of
app_id for this user’s session:
- the first program to claim the name gets
Claim.Owner, and from then on every later instance’s arguments come toon_handoverin the ui loop; - a later one gets
Claim.HandedOver: its arguments (without the program’s name) are already with the owner, and it should exit.
claim_args(app_id, args, on_handover) hands over args instead of the
program’s own. release() gives the name up (the next instance to start
owns it), owned() says whether the program owns one, and poll()
delivers waiting handovers now (the ui loop calls it for you).
An app id is 1 to 64 letters, digits, ., - and _ (a reverse domain
name is a good one); anything else traps, as does claiming a second name.
The arguments of one handover are at most MAX_HANDOVER bytes (60,000,
4 more per argument): TooLong past that. Under the hood it is a named
pipe on Windows and a Unix-domain socket on Linux and macOS, only this
user can write to it: in $XDG_RUNTIME_DIR, else in a directory of the
user’s own in the temporary directory (tape-launch-<uid>, made with
mode 0700). If that directory is not this user’s alone (another user made
it first, it is a link, or it is open to others), claim refuses with
NotPrivate rather than hand arguments to someone else.
A URL scheme of your own
register_scheme("chat") makes the program the handler of chat: URLs
for this user: clicking one starts it with the URL as its one argument,
and with claim the running instance gets it. unregister_scheme undoes
it. A scheme is a lowercase letter, then letters, digits, +, - or
.; anything else traps.
| Windows | HKEY_CURRENT_USER\Software\Classes\<scheme>, the command "<the program>" "%1" |
| Linux | ~/.local/share/applications/tape-scheme-<scheme>.desktop with MimeType=x-scheme-handler/<scheme>, made the default with xdg-mime |
| macOS | Unsupported: the scheme belongs in an app bundle’s Info.plist, which tape build does not make yet |
Opening links
open_url(url) opens a link in the user’s browser (or a mailto: in the
mail program). Only http:, https: and mailto: URLs with no spaces or
control characters are opened; anything else is InvalidUrl, because a
link from a message could otherwise start any handler the system has.
is_openable(url) asks first. ShellExecuteExW on Windows, NSWorkspace
on macOS; on Linux the desktop portal’s OpenURI through dbus
(which also works from a Flatpak sandbox), else xdg-open.
LaunchError | When |
|---|---|
Unsupported | The platform can’t (macOS schemes, Uusi) |
InvalidUrl | Not an http:, https: or mailto: URL |
TooLong | The arguments don’t fit one handover |
NotPrivate | The socket’s directory under the temporary directory is not this user’s own (Linux and macOS without $XDG_RUNTIME_DIR) |
Failed | The system refused |
Last modified: