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

Notifications makes your shell the notification daemon. Programs send their notifications to Amane, the same way they’d send them to mako or dunst, and you decide how to show them.

Before you start

Only one notification daemon can run at a time. Stop the one you have (mako, dunst, swaync, or a notification feature in another bar) before starting your shell. If another daemon is running, running() returns false and Amane receives nothing.

Amane starts being the daemon the first time something reads Notifications, so read it in a view that’s open when the shell starts.

A notification list

use amane::{
    App, Column, Horizontal, Layer, LayerWindow, Notification, Notifications, Parent, Rectangle,
    Service, Text, Urgency, Vertical, Widget,
};

fn main() {
    App::new().window(view).run();
}

fn view() -> LayerWindow {
    let notifications = Notifications::read();

    let mut cards: Vec<Box<dyn Widget>> = Vec::new();

    for notification in notifications.list() {
        cards.push(Box::new(card(notification)));
    }

    LayerWindow::new()
        .width(400.0)
        .height(600.0)
        .anchor_vertical(Vertical::Top)
        .anchor_horizontal(Horizontal::Right)
        .layer(Layer::Overlay)
        .visible(!notifications.list().is_empty())
        .child(Column::new(cards).gap(8.0))
}

// click a notification to run its default action, or to dismiss it
fn card(notification: &Notification) -> Rectangle {
    let fill = match notification.urgency() {
        Urgency::Critical => "#f38ba8",
        _ => "#313244",
    };

    let id = notification.id();

    let label = format!("{}: {}", notification.summary(), notification.body());

    Rectangle::new()
        .width(Parent)
        .height(60.0)
        .radius(12.0)
        .fill(fill)
        .padding(12.0)
        .on_click(move |_| Notifications::click(id))
        .child(Text::new(label).elide())
}

The window hides itself while there are no notifications, so its empty space doesn’t block clicks on the windows underneath.

Try it with:

notify-send "hello" "from amane"

Reading notifications

list() gives every notification, oldest first. Each Notification has:

FunctionGives
id()its number, which the control functions take
app_name()the sending program’s name
summary()the one-line title
body()the text, which may be empty and may hold simple markup like <b>
icon()an icon name like "firefox", a file path, or empty
image()a picture for this notification, like an avatar: a file path, a file:// link, or empty
urgency()Urgency::Low, Normal, or Critical
actions()the buttons the sender asked for, in order
has_default_action()whether clicking the notification itself does something
received()when it arrived, or when the sender last replaced it, as a SystemTime

Each Action has key(), to pass to invoke, and label(), the text to show on the button.

Clicking, dismissing, and actions

FunctionDoes
Notifications::click(id)runs the default action, or dismisses it when there’s none. Use it when the notification itself is clicked.
Notifications::invoke(id, key)runs one of its actions. Use it for action buttons.
Notifications::dismiss(id)closes it
Notifications::clear()closes all of them

After an action runs, the notification closes, unless the sender asked for it to stay.

Action buttons

for action in notification.actions() {
    let id = notification.id();
    let key = String::from(action.key());

    buttons.push(Box::new(
        Rectangle::new()
            .width(100.0)
            .height(30.0)
            .on_click(move |_| Notifications::invoke(id, &key))
            .child(Text::new(action.label())),
    ));
}

Try it with:

notify-send -A yes=Yes -A no=No "question" "pick one"

Timeouts

Notifications stay until they’re dismissed. Amane ignores the timeout the sender asks for.

To hide popups after a few seconds, compare received() with the current time in a Service that polls every second, and only show the recent ones in your popup window. Keep the full list for a notification center.