Notifications
Banners, alerts, action buttons and the Notification Centre — from a shell, from C, or from the toolkit.
Fasadas has a notification system modelled on macOS: banners that slide in at the top right and leave by themselves, alerts that wait for a decision, action buttons whose click reaches the application that posted, grouping by application with a count, Do Not Disturb with a schedule, and a history that outlives the compositor.
Clicking the menu-bar clock opens the Notification Centre: a sidebar with the history, a calendar, weather, system metrics and the Do Not Disturb switch.
Everything is drawn by the compositor, for the same reason the menu bar is: a notification must appear over every window, and it must outlive the process that posted it — a daemon posting one and exiting is the normal case, not an edge case.
From a shell or a daemon
anotify needs no window. Its flags, output and exit codes are a stable
interface.
anotify --app "Software Update" --id 1 --alert \
--title "Updates available" --body "3 packages can be updated." \
--action Install --action Later --wait
# posted 1 shown
# action 1 Install
| Flag | Meaning |
|---|---|
--app NAME |
Required. The name on the card and in Settings. |
--title, --subtitle, --body |
At least one of title or body. |
--action LABEL |
A button, up to four. Repeat for more. |
--alert |
Ask for alert style, which stays until answered. The user's setting wins. |
--id N |
Posting the same application and id again replaces it in place, including from a later run. |
--open PATH |
An absolute executable or .aop bundle the compositor runs on a click after this process has exited. |
--icon PNG |
An absolute image path. Defaults to the application's icon. |
--wait |
Stay connected and report the user's choice. |
--timeout S |
Give up waiting after S seconds. |
--withdraw N |
Remove it from the screen and the history. 0 means all of this application's. |
Output is always posted <id> <state>. With --wait you then get one of
action <n> <label>, dismissed, removed or timeout.
Exit codes: 0 ok, 1 usage, 2 no compositor or the connection was lost,
3 refused, 4 dismissed or removed, 5 timed out.
From C
#include "anotify.h" /* link -lfasadas */
anotify *n = anotify_open("Software Update", 5.0);
anotify_note note = { .title = "Updates available", .body = "3 packages",
.actions = { "Install", "Later" }, .nactions = 2,
.alert = 1 };
uint32_t id = anotify_post(n, 1, ¬e);
anotify_event ev;
if (anotify_wait(n, id, -1, &ev) == 1 &&
ev.kind == ANOTIFY_ACTION && ev.value == 1)
install();
anotify_close(n);
anotify_open() fails with ENOENT or ECONNREFUSED when there is no
compositor, ETIMEDOUT when one is listening but not answering, and ENOTSUP
against a compositor without notifications.
A daemon with its own loop polls anotify_fd() and calls anotify_dispatch();
anotify_set_callback() receives every event.
From a toolkit application
An application built on the AO toolkit uses
AO_UserNotificationCenter, modelled on NSUserNotificationCenter. It rides
the application's existing compositor connection — no second client — and the
answer comes back to a delegate on the run loop, like every other event.
static void on_note(AO_UserNotificationCenter *nc,
const AO_UserNotificationEvent *ev, void *user)
{
if (ev->kind == AO_USER_NOTIFICATION_EVENT_ACTIVATED && ev->action == 1)
install(); /* ev->action_title is "Install" */
}
AO_UserNotificationCenter *nc = AO_UserNotificationCenterDefault();
AO_UserNotificationCenterSetDelegate(nc, on_note, NULL);
AO_UserNotification *n = AO_UserNotificationCreate();
AO_UserNotificationSetIdentifier(n, "updates");
AO_UserNotificationSetTitle(n, "Updates available");
AO_UserNotificationSetInformativeText(n, "3 packages can be updated.");
AO_UserNotificationAddAction(n, "Install"); /* returns 1 */
AO_UserNotificationAddAction(n, "Later"); /* returns 2 */
AO_UserNotificationSetStyle(n, AO_USER_NOTIFICATION_STYLE_ALERT);
AO_UserNotificationCenterDeliver(nc, n);
AO_Release(n); /* the centre copied what it needs */
- The identifier is the key. Delivering the same identifier again replaces the notification in place, including from a later run. Leave it unset for a one-shot and one is made up for you.
- Actions are numbered the same way everywhere on this page: 0 is a click on the notification itself, 1 to n are your buttons in order. At most four; a fifth is refused at the call, not silently dropped at delivery.
- The delegate runs on the UI thread, on a run-loop turn — never inside some other call that happened to read the connection while waiting for its own reply. Delivering before the loop has started is fine; the events wait for its first turn.
- The style is a request. The user's per-application setting wins.
AO_UserNotificationCenterIsAvailable()says whether the compositor implements notifications at all, so delivering to one that does not fails loudly instead of vanishing.
What happens to a notification
Every post is answered with exactly one state:
| State | When |
|---|---|
shown |
On screen now. |
dnd |
Do Not Disturb: recorded in the history, not shown. |
quiet |
The application's style is None: recorded, never on screen. |
blocked |
The application is not allowed to notify: neither shown nor recorded. |
rejected |
Malformed — no title and no body, or an unterminated string. |
Then, as they happen: expired (a banner left by itself — it is still in the
history and still clickable there), dismissed (closed without a choice),
removed (cleared, withdrawn, or pushed out by age), and the action events.
Actions reach the connection that posted, and only while it lives. A click
after the poster has gone runs its --open target instead, if it has one.
Banners, alerts and grouping
The geometry is a contract. Nothing about a card is measured from its text.
| Card | 344 × 74 px, corner radius 12. Text is fitted and ellipsized, never grows the card. |
| Position | 12 px from the right edge, the first 10 px below the 22 px menu bar. |
| Stacking | Top to bottom, newest application first, 10 px apart, at most five on screen. |
| Grouping | One card per application, showing the newest. Up to two 6 px plates peek below it, and a red count badge sits on the icon. |
| Buttons | An 88 px column on the right, its buttons splitting the card's height evenly. |
| Close box | A circle on the card's top-left corner, shown while the pointer is on the card. It closes the whole stack. |
| Banner lifetime | 5 s, configurable from 2 to 60. A new banner restarts its group's clock; the pointer resting on a card holds it, and it lingers 2 s after the pointer leaves. |
| Animation | In from the right edge in 0.30 s, out in 0.25 s, reflow in 0.20 s. |
An alert always shows its button column; a banner shows it only under the pointer. With no actions an alert shows Close; one action shows it and Close; two show both; more than two show the first and Options, which drops down the rest.
A card never takes the keyboard, and an alert blocks nothing. Every window keeps its input and the compositor keeps compositing. Clicking the card body is the default action: the card leaves and the notification stays in the history, marked read.
Cards are a solid material rather than live blur. A blur behind a card that moves every frame of its slide would cost a blur per frame, and the slowest supported machine sets the frame budget.
Settings and Do Not Disturb
notifications.conf in the configuration directory; the compositor re-reads it
on change.
| Key | |
|---|---|
dnd |
1 = Do Not Disturb on, now. |
dnd_scheduled, dnd_from, dnd_to |
The schedule, in minutes after midnight, local time. to at or before from wraps midnight, so 22:00–07:00 is 1320 and 420. Equal values are an empty window. |
<App>_enabled |
0 blocks the application. |
<App>_style |
0 None (history only), 1 Banners, 2 Alerts. Overrides what the poster asked for. |
banner_secs |
Banner lifetime. |
<App> is the application name with every byte that is not a letter or digit
turned into an underscore, so "Software Update" becomes Software_Update.
Under Do Not Disturb nothing is put on screen, alerts included; everything is still recorded in the history.
More documentation
- Booting the live image
- Installing to disk
- Packages and the filesystem
- How it boots
- The syscall ABI
- Hardware support
- ACPI
- UEFI boot entries
- NetBooting a Mac
- The AO toolkit
- Grafika — the graphics API
- The display protocol
- Desktop effects
- The level bezel
- Rastinis — the file manager
- File operations
- Audio — Garsas
- Hardware video decode
- Networking — Tinklas
- Network shares — SMB and NFS
- Remote control
- The Win32 subsystem
- DirectX and OpenGL for Win32 programs
- Ports and packaging