tray

tape
import tray from "tray";
import ui from "ui";
import gfx from "gfx";

fn on_tray(ev: tray.TrayEvent) {
    if (ev.kind == tray.TrayEventKind.Click) { ui.show_window(); }
    else if (ev.index == 0) { ui.show_window(); }      // "Open"
    else if (ev.index == 2) { ui.quit(); }             // "Quit"
}

component App { view { ui.Column {} } }

fn main() -> i32 {
    var small = gfx.new_surface(16, 16, gfx.Format.Argb) or { return 1; };
    small.fill_rect(0, 0, 16, 16, #2A7FFFFF);
    var icon: tray.Tray = tray.add([small], "Chat", on_tray) or { return 1; };
    let items: [3]tray.MenuItem = [
        tray.MenuItem { label: "Open"; },
        tray.MenuItem { separator: true; },
        tray.MenuItem { label: "Quit"; },
    ];
    icon.set_menu(items[0..3]) or {};
    // closing the window leaves the program running with its icon
    ui.run_window(App {}, ui.Window { title: "Chat"; keep_running: true; }) or { return 1; };
    icon.remove();
    small.free();
    return 0;
}

A status icon keeps a program reachable with no window open: a messenger, a sync tool. Pair it with ui.Window’s keep_running (Running with no window) so closing the window doesn’t end the program, and ui.show_window() brings the window back.

Errors

add fails with a TrayError: Unsupported (no status icons on this platform), NoArea (there are, but this session has nowhere to show one: no taskbar on Windows, no StatusNotifierWatcher on Linux; go on without the icon), Failed (the system refused it) or OutOfMemory.

Functions

FunctionDoes
add(icon, tooltip, on_event) -> Tray or TrayErrorPuts an icon in the status area; its events go to on_event
t.set_icon(icon) -> void or TrayErrorAnother picture (an unread count drawn on it, say)
t.set_tooltip(text) -> void or TrayErrorThe text shown when the pointer rests on it
t.set_menu(items) -> void or TrayErrorThe menu, replacing the last; no items: none
t.poll() -> i64The icon’s waiting events given to the handler now (the ui loop does this for you)
t.remove()Takes the icon away; removing twice does nothing

icon is one picture in several sizes (16, 20, 24, 32 pixels…), each a gfx.Surface: the platform shows the one that fits. The pixels are copied. From 1 to 16 images, each side 1 to 256 pixels; more traps, as does more than 8 icons at once.

A MenuItem has a label, enabled (default true), checked (a check mark, default false) and separator (a line; the rest ignored). At most 64 items.

Events

The handler gets a TrayEvent { tray, kind, index } in the ui loop, on ui.run’s thread, between other events:

kindWhen
ClickThe icon was clicked with the left button (or Enter pressed on it)
ChooseMenu item index (its place in set_menu’s list) was chosen

The menu opens on the right button by itself; on macOS also on a click with Control held.

Platforms

WindowsThe notification area (Shell_NotifyIconW); NoArea when the session has no taskbar (a service’s). The icon comes back by itself when Explorer restarts. A tooltip longer than 127 UTF-16 units is cut
macOSA status item in the menu bar (NSStatusItem)
LinuxA StatusNotifierItem with a dbusmenu menu, over dbus’s shared connection, registered with the StatusNotifierWatcher of KDE Plasma, the GNOME AppIndicator extension, waybar or xfce4-panel; NoArea where none runs (plain GNOME, a bare window manager), Unsupported with no session bus. A removed icon is hidden (its status Passive) until the program ends or adds another icon; the host comes back by itself if the panel restarts
UusiUnsupported

Last modified: