notify

tape
@profile(t1);
import notify from "notify";
import io from "io";

pub fn main() -> i32 {
    notify.set_app("com.example.chat", "Example Chat");
    let id: notify.NoteId = notify.show(notify.Note {
        title: "Aino";
        body: "On my way";
        tag: "chat-7";
    }) or {
        io.println("no notification: {err}");
        return 1;
    };
    io.println("shown as {id.v}");
    return 0;
}

A note appears in the system’s notification area under your program’s name, and stays in the notification centre until the user or your program takes it away.

FunctionDoes
notify.set_app(id, name)Your program’s id ("com.example.chat") and the name its notes show. Call it once, at the start, before anything else; without it the id is tape.<program> and the name the program’s file name. On macOS the app bundle’s are used. On Windows it also gets your program ready for clicks on its notes, also the click that started it (below)
notify.show(note) -> NoteId or NotifyErrorShows the note
notify.withdraw(id) -> void or NotifyErrorTakes it off the screen and out of the notification centre (nothing if it is gone)
notify.withdraw_tag(tag) -> void or NotifyErrorThe same for every note with the tag
notify.badge(n) -> void or NotifyErrorThe unread count on your program: the Dock icon on macOS, the taskbar button of your ui window on Windows; 0 takes it off
notify.handle() -> i64What ui.Watch waits on for the user’s clicks and dismissals (-1 where there are none to wait for)
notify.next() -> ?NoteEventThe oldest click or dismissal not yet taken, or null

Note:

FieldIs
title: []const u8One line, not empty
body: []const u8 = ""The text under it
icon: *gfx.Surface = nullA picture beside the text; null: your program’s icon
tag: []const u8 = ""What the note is about ("chat-7"), up to notify.MAX_TAG (64) bytes: withdraw_tag uses it and the note’s events carry it
silent: bool = falseNo sound
sender: []const u8 = ""Who a message is from: a messenger’s note (below)
avatar: *gfx.Surface = nullThe sender’s picture; only with a sender

An empty title, a tag that is too long or a negative badge trap.

A note stays until the user closes it or your program withdraws it. To take a conversation’s notes away when its chat is read, or when the user closes one of them, call notify.withdraw_tag(tag). On macOS, don’t withdraw a conversation’s notes just before you show its next one: the new note then plays its sound but shows no banner. Withdraw them when the chat is read instead.

Who a message is from

A messenger’s note names its sender, with their picture:

tape
notify.show(notify.Note {
    title: "Aino";
    body: "Lunch at 12?";
    tag: "chat-aino";
    sender: "Aino";
    avatar: &aino_avatar;
}) or { io.println("no notification: {err}"); };

The avatar is shown the way the system shows a person:

  • Windows: in the place of your app’s logo, cropped round. An icon given as well is shown under the text.
  • macOS: on the left of the note, with your app’s icon as a small badge on it, as a communication notification. That needs the Communication Notifications entitlement in your app’s signature (a Developer ID build with a provisioning profile that has it). Without it the avatar is shown as a picture on the right, and your app’s icon stays on the left.
  • Linux: as the note’s image (an icon given as well is not shown).

Clicks and dismissals

When the user clicks a note, or closes it, your program gets a NoteEvent { action, id, tag } (action is NoteAction.Clicked or NoteAction.Dismissed). They arrive while ui.run runs: put a ui.Watch on notify.handle() and take them all in its event.

tape
@profile(t1);
import notify from "notify";
import ui from "ui";
import io from "io";

fn on_notes(h: i64) {
    while (true) {
        let e: ?notify.NoteEvent = notify.next();
        if (e == null) { break; }
        if (e!.action == notify.NoteAction.Clicked) {
            io.println("open {e!.tag}");
            notify.withdraw_tag(e!.tag) or { };
        }
    }
}

component App {
    view {
        ui.Watch { handle: notify.handle(); on_ready: on_notes; }
    }
}

A note that times out is not dismissed: it waits in the notification centre, and a click there later still comes, while your program runs.

A click lets your program come to the front: call ui.activate_window() in its handler to bring your window forward.

A click after your program exited

On Windows, a click on one of your notes in the notification centre after your program exited starts it again (a program built with tape build; under tape run it doesn’t). It starts as usual, with one more argument, -Embedding, which your program should ignore. Once it calls notify.set_app, the click is waiting as a Clicked event: its tag is the note’s, and its id.v is 0, since the note was the earlier run’s. So call set_app early, and watch notify.handle() as above.

If your program is still running, the click goes to it and no second copy starts, as with launch.claim. Elsewhere a click after exit does nothing yet.

Errors

NotifyError:

VariantWhen
PermissionDeniedThe user turned your program’s notifications off
UnsupportedThe system shows your program none: Linux without a notification server, macOS outside an app bundle (tape run, a bare executable)
NoWindowbadge on Windows with no ui window open
FailedThe system refused otherwise
OutOfMemoryOut of memory

Platforms

  • Windows: toasts. set_app’s id is your AppUserModelID; it is registered for the current user so a program without a Start-menu shortcut can show toasts, and it groups your taskbar button. A built program is also registered as its notes’ activator, for the current user: HKEY_CURRENT_USER\Software\Classes\CLSID\{...} names the executable’s path (the {...} is the same for every run of the same app id), so move or rebuild the program and its next run registers the new path. The badge is a red disc with the count over the taskbar button.
  • macOS: the Notification Centre, for a program in an app bundle. The first call asks the user to allow notifications; until they answer, notes are shown as the system decides.
  • Linux: the desktop’s notification server (org.freedesktop.Notifications) on the session bus, through dbus’s shared connection: a click on the note and a dismissal by the user are events, as elsewhere, and handle() is ready while they wait (also when one came while a file dialog or a keyring prompt had the program waiting). The badge is the Unity launcher signal, shown by Ubuntu’s dock, Dash to Dock and KDE Plasma on the launcher of the desktop file named after your app id (set_app); where no dock listens it shows nothing. Unsupported without a session bus or a notification server.

The library is shell100 (.dll, .dylib, .so), next to tape; tape build copies it beside your program.

Last modified: