Desktop platform APIs

Capability modules under src/desktop/. All follow the same pattern: explicit Error sets with error.Unsupported where a platform can't deliver, and no silent fallbacks.

Tray

var tray = try desktop.tray.init(allocator, &window, .{
    .tooltip = "My app",
    .icon_symbol = "bolt.fill",          // macOS SF Symbol; icon_path elsewhere
    .menu = &.{
        .{ .label = "Open", .id = 1 },
        .{},                              // separator
        .{ .label = "Quit", .id = 2 },
    },
    .on_menu_item = onMenuItem, .on_menu_item_ctx = ctx,
});

macOS NSStatusItem · Windows Shell_NotifyIcon · Linux libayatana-appindicator3 (dlopen'd; Unsupported without it; GTK4 unsupported).

Notifications

try desktop.notifications.show(allocator, .{ .title = "Done", .body = "Export finished." });

macOS UNUserNotificationCenter (needs a signed bundle) · Windows toast (balloon fallback) · Linux libnotify.

Global hotkeys

var hk = try desktop.hotkeys.init(allocator, onHotkey, ctx);
try hk.register(1, .{ .cmd = true, .shift = true }, keycode_k);

Carbon (macOS) · RegisterHotKey (Windows) · X11 XGrabKey (Linux; Wayland → Unsupported; fires from a worker thread).

Deep links & single instance

var lock = desktop.single_instance.acquire(allocator, "com.me.app") catch |err| switch (err) {
    error.AlreadyRunning => {
        try desktop.deep_link.forwardToRunningInstance(allocator, "com.me.app", url);
        return;
    },
    else => return err,
};
defer lock.release();
try desktop.deep_link.startListener(&window, "com.me.app");
// registerScheme(alloc, "myapp", bundle_id) registers the myapp:// handler

The second launch forwards its URL to the first and exits; the running window receives it via on_url_open. (macOS warm-launches are handled by the system; the forwarders matter on Windows/Linux.)

Clipboard & cookies

try window.clipboard().writeText("copied");
const text = try window.clipboard().readText(allocator);   // also Html/Image (PNG)

var cookies = window.cookies();
try cookies.set(.{ .name = "session", .value = token, .http_only = true });

Linux clipboard is text-only (Unsupported for HTML/image).

File watching

var watcher = try desktop.fswatch.init(allocator, path, onChange, ctx);

FSEvents (macOS, recursive, ~1s batches) · ReadDirectoryChangesW (Windows, recursive, worker thread) · inotify (Linux, single directory, non-recursive).

Autostart, updates, power, displays

try desktop.autostart.enable(allocator, io, environ, .{ .name = "com.me.app", .exe_path = exe });

if (try desktop.updates.checkForUpdate(allocator, feed_url, current_version)) |info| {
    try desktop.updates.applyUpdate(allocator, io, info);   // sha256-verified
}
// feed: {"version":"1.2.3","download_url":"…","sha256":"…","notes":"…"}

const pct = desktop.power.batteryPercent();   // ?u32
const charging = desktop.power.isCharging();

const screens = try desktop.displays.list(allocator);  // x/y/w/h, scale, primary

Updates: macOS atomic bundle swap, Windows swap.cmd handoff, Linux Unsupported.

Dialogs, print, snapshot

FileDialogOptions (open/save, multiple, directories), AlertOptions (buttons + informational/warning/critical), window.printWithOptions(.{ .kind = .system, .copies = 2 }), window.snapshot(...) for window captures (GTK4 Unsupported).

Next: Build options.