6 releases

0.2.4 Aug 19, 2023
0.2.3 Jul 23, 2023
0.1.0 Jul 1, 2023

#1135 in Command line utilities

43 downloads per month

MIT license

49KB
880 lines

nofi

A Rofi-driven notification manager

GitHub Release Crate Release Continuous Integration Continuous Deployment Documentation

https://github.com/ellsclytn/nofi/assets/8725013/b3c5e53b-7ba9-44bd-a920-81a408b84cb9

nofi is a distraction-free notification center. While most notification daemons make immediate popups a key function, nofi is designed with such functionality as an anti-feature: notifications are intended to be viewed, but not to annoy. Notifications can be viewed at the user's discretion by launching nofi's Rofi-driven notification manager.

nofi is a server implementation of freedesktop.org - Desktop Notifications Specification and it can be used to receive notifications from applications via D-Bus.

The name?

A portmanteau of "notification" and Rofi.

Features

  • Template-powered (Jinja2/Django) notification text.
  • Run custom OS commands based on the matched notifications.

Installation

From crates.io

nofi can be installed from crates.io:

$ cargo install nofi

The minimum supported Rust version is 1.64.0.

Arch Linux

nofi can be installed from the AUR using an AUR helper. For example:

aura -A nofi-bin

Binary releases

See the available binaries for different operating systems/architectures from the releases page.

Build from source

Prerequisites

Instructions

  1. Clone the repository.
$ git clone https://github.com/ellsclytn/nofi && cd nofi/
  1. Build.
$ CARGO_TARGET_DIR=target cargo build --release

Binary will be located at target/release/nofi.

Usage

On Xorg startup

You can use xinitrc or xprofile for autostarting nofi.

xinitrc

If you are starting Xorg manually with xinit, you can nofi on X server startup via xinitrc:

$HOME/.xinitrc:

nofi &

Long-running programs such as notification daemons should be started before the window manager, so they should either fork themself or be run in the background via appending & sign. Otherwise, the script would halt and wait for each program to exit before executing the window manager or desktop environment.

In the case of nofi not being available since it's started at a faster manner than the window manager, you can add a delay as shown in the example below:

{ sleep 2; nofi; } &

xprofile

If you are using a display manager, you can utilize an xprofile file which allows you to execute commands at the beginning of the X user session.

The xprofile file, which is ~/.xprofile or /etc/xprofile, can be styled similarly to xinitrc.

As a D-Bus service

You can create a D-Bus service to launch nofi automatically on the first notification action. For example, you can create the following service configuration:

/usr/share/dbus-1/services/org.ellsclytn.nofi.service:

[D-BUS Service]
Name=org.freedesktop.Notifications
Exec=/usr/bin/nofi

Whenever an application sends a notification by sending a signal to org.freedesktop.Notifications, D-Bus activates nofi.

As a systemd service

~/.config/systemd/user/nofi.service:

[Unit]
Description=Nofi notification daemon
Documentation=man:nofi(1)
PartOf=graphical-session.target

[Service]
Type=dbus
BusName=org.freedesktop.Notifications
ExecStart=/usr/bin/nofi

You may then reload systemd and start/enable the service:

systemctl --user daemon-reload
systemctl --user start nofi.service

Usage

nofi uses dbus-send(1) to receive control instructions. There is currently only one instruction: viewing notification history.

# show the last notification
dbus-send --print-reply \
          --dest=org.freedesktop.Notifications \
          /org/freedesktop/Notifications/ctl \
          org.freedesktop.Notifications.History

An example use-case of this is to bind this to a key in your window manager, such as i3:

bindsym $mod+grave exec dbus-send --print-reply \
        --dest=org.freedesktop.Notifications /org/freedesktop/Notifications/ctl org.freedesktop.Notifications.History

Status Bar Integration

nofi broadcasts notification counts over a UNIX socket in the same format as Rofication. This means it can be integrated into status bars like i3status-rust via the Rofication block. The socket path follows the XDG Base Directory specification which usually exposes the socket at /run/user/<UID>/nofi/socket. This may vary between systems, so the socket path is output to stdout when nofi starts.

# Example i3status-rust integration

[[block]]
block = "rofication"
interval = 1
socket_path = "/run/user/1000/nofi/socket"

Configuration

nofi configuration file supports TOML format and the default configuration values can be found here.

Configuration overrides can be placed in $HOME/.config/nofi/nofi.toml, or at a path of your choosing by specifying a NOFI_CONFIG environment variable.

Global configuration

log_verbosity

Sets the logging verbosity. Possible values are error, warn, info, debug and trace.

template

Sets the template for the notification message. The syntax is based on Jinja2 and Django templates.

Simply, there are 3 kinds of delimiters:

  • {{ and }} for expressions
  • {% or {%- and %} or -%} for statements
  • {# and #} for comments

See Tera documentation for more information about control structures, built-in filters, etc.

Context

Context is the model that holds the required data for template rendering. The JSON format is used in the following example for the representation of a context.

{
  "app_name": "nofi",
  "summary": "example",
  "body": "this is a notification 🦡",
  "urgency": "normal",
  "unread_count": 1,
  "timestamp": 1672426610
}

Urgency configuration

There are 3 levels of urgency defined in the Freedesktop specification and they define the importance of the notification.

  1. low: e.g. "joe signed on"
  2. normal: e.g. "you got mail"
  3. critical: e.g. "your computer is on fire!"

You can configure nofi to act differently based on these urgency levels. For this, there need to be 3 different sections defined in the configuration file. Each of these sections has the following fields:

[urgency_{level}] # urgency_low, urgency_normal or urgency_critical
    custom_commands = []

custom_commands

With using this option, you can run custom OS commands based on urgency levels and the notification contents. The basic usage is the following:

custom_commands = [
    { command = 'echo "{{app_name}} {{summary}} {{body}}"' } # echoes the notification to stdout
]

As shown in the example above, you can specify an arbitrary command via command which is also processed through the template engine. This means that you can use the same template context.

The filtering is done by matching the fields in JSON via using filter along with the command. For example, if you want to play a custom notification sound for a certain application:

custom_commands = [
  { filter = '{ "app_name":"notify-send" }', command = 'aplay notification.wav' },
  { filter = '{ "app_name":"weechat" }', command = 'aplay irc.wav' }
]

The JSON filter can have the following fields:

  • app_name: Name of the application that sends the notification.
  • summary: Summary of the notification.
  • body: Body of the notification.

Each of these fields is matched using regex and you can combine them as follows:

custom_commands = [
  { filter = '{ "app_name":"telegram|discord|.*chat$","body":"^hello.*" }', command = 'gotify push -t "{{app_name}}" "someone said hi!"' }
]

In this hypothetical example, we are sending a Gotify notification when someone says hi to us in any chatting application matched by the regex.

License

Licensed under either of Apache License Version 2.0 or The MIT License at your option.

Copyright © 2023, Ellis Clayton

Dependencies

~20–32MB
~389K SLoC