Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

notifications

The notification server: the newest 20 notifications and do-not-disturb.

button {
    on_click = function()
        local notifications = mantle.notifications:get()
        if notifications then
            mantle.notifications:set_dnd(not notifications.dnd)
        end
    end,
    children = {
        text {
            content = mantle.notifications:map(function(notifications)
                return (notifications and notifications.dnd) and "DND on" or "DND off"
            end),
        },
    },
}

State

mantle.notifications:get() returns NotificationsState, nil before the first push. A field marked ? may be absent.

mantle.notifications’s payload.

FieldTypeDescription
dndbooleanDo-not-disturb: mutes non-critical sounds only. Hiding popups is the config’s call.
feedNotification[]The newest 20 of up to 100 queued notifications, newest first, expired ones included; a replacement keeps its place. An entry past 20 stays dismissable by id.

Notification

One notifications.feed entry.

FieldTypeDescription
actionsNotificationAction[]Buttons in sender order, at most 8, excluding default and inline-reply.
app_icon?stringApplication icon for icon { name = ... }: a theme name such as "firefox" or an absolute path, or nil.
app_namestringSending application, truncated to 64 bytes.
bodyNotificationSpan[]Parsed body markup; the raw body is truncated to 512 bytes first.
desktop_entry?stringSender’s desktop id, e.g. "org.telegram.desktop", for mantle.applications.by_app_id; nil when absent or containing /.
expiredbooleanThe timeout ran out: drop it from popups, keep it in history until dismissed. Never true for critical or expire_timeout = 0; a replacement resets it.
has_default_actionbooleanClicking the card may :invoke_action(id, "default").
has_replybooleanThe sender accepts mantle.notifications:reply(id, text).
idintegerServer id, from 1; a replacement keeps the id it replaces.
image_path?stringAttached picture (album art, avatar) as an existing absolute path, or nil. Never a theme name.
reply_placeholder?stringPlaceholder for an empty reply field, e.g. "Reply to Alice", capped at 64 bytes; nil when unset.
summarystringTitle as sent, truncated to 128 bytes. Not markup-parsed: the spec makes it plain text.
timestampintegerArrival time, Unix seconds; age is mantle.system.time - timestamp. A replacement restamps it.
transientbooleanPopup-only: removed on expiry instead of retired to history.
urgencyUrgency"normal" when the sender set none. "critical" never expires and plays sound through DND.

NotificationAction

One action button.

FieldTypeDescription
icon_name?stringTheme icon name (the key) when the sender set action-icons, else nil. Never a path.
keystringOpaque key for :invoke_action(id, key).
labelstringButton label, capped at 64 bytes. An empty label falls back to the key unless icon_name is set.

NotificationSpan

One body-markup run: styled text or an image.

FieldTypeDescription
bold?booleanWhether the run was inside <b>.
href?string<a href> target, or nil when not a link.
image_path?stringExisting absolute path under an icon root; images elsewhere are dropped.
italic?booleanWhether the run was inside <i>.
kind"text"|"image"Which variant this is; each other field belongs to one variant.
text?stringUnescaped text; empty runs are omitted.
underline?booleanWhether the run was inside <u>.

Urgency

Notification urgency, also the set_sound tier.

One of "low", "normal", "critical".

Actions

Call each as mantle.notifications:<action>(arguments...); ? marks an argument you may omit.

ActionArgumentsDescription
dismissid: integerRemoves a queued notification.
invoke_actionid: integer, key: stringInvokes an actions[].key, or "default"; removes the notification unless it is resident.
replyid: integer, text: stringSends reply text to a notification with has_reply; removes it unless it is resident.
set_soundurgency: Urgency, path: stringSets an urgency tier’s sound: an existing file under /usr/share, /usr/local/share, /opt or $XDG_DATA_HOME, else ignored. Only Ogg Vorbis and 16-bit PCM WAV play.
set_dndenabled: booleanGates non-critical notification sounds.
set_quietenabled: booleanMutes non-critical sounds like set_dnd, without changing dnd.
set_app_mutedapp: string, muted: booleanSilences every sound from an app, critical included, matched exactly on app_name or desktop_entry.
hold_expiryseconds: integerPauses every expiry countdown for seconds, capped at 300; 0 releases the hold.

Backend

Mantle is the org.freedesktop.Notifications server on the session bus.

ContractBehavior
NameRequested with DoNotQueue. If mako, dunst or another daemon owns it, the server stays off for the run and feed stays empty
Retention100-entry FIFO; feed shows the newest 20
ExpiryA negative expire_timeout means 5 s. Critical and 0 never expire
Close reasonsNotificationClosed sends 1 expired, 2 dismiss or reply, 3 CloseNotification or invoke_action, 4 evicted from the FIFO. reply also emits NotificationReplied(id, text)
Body markupKeeps <b>, <i>, <u>, <a href>, <img src>. Other tags lose their markup and keep their text; script and style lose both
ImagesA path in image-path or <img> must be a regular file under /usr/share/icons, /usr/share/pixmaps, $XDG_DATA_HOME/icons or ~/.icons; an image-path without / and every action icon are theme names. Raw image-data (8-bit RGB or RGBA, at most 128 px a side) is spooled as notifications/notif-<id>.png
SoundNothing plays for a tier until set_sound registers a file for it, except a client’s own sound-file. Order: suppress-sound or set_app_muted silences, critical included; else the client’s sound-file; else sound-name from the freedesktop theme, only for a registered tier; else the tier’s file. Every file sits under /usr/share, /usr/local/share, /opt or $XDG_DATA_HOME and is Ogg Vorbis or 16-bit WAV, at most 4 MiB and 30 s

How do I…

List notifications and dismiss one on click

list {
    spacing = 6,
    source = mantle.notifications:map(function(notifications)
        return notifications and notifications.feed or {}
    end),
    key = function(item) return tostring(item.id) end,
    itemfn = function(item)
        return button {
            width = 320,
            padding = 8,
            radius = 8,
            background = "#1E1E2E",
            on_click = function() mantle.notifications:dismiss(item.id) end,
            children = {
                column {
                    children = {
                        text { content = item.summary, font_size = 13, elide = "End", width = "Fill" },
                        text { content = item.app_name, font_size = 11, foreground = "#A6ADC8" },
                    },
                },
            },
        }
    end,
}

See also: Notification popups recipe.

Source: supervisor/src/capabilities/notifications/