## nofi
**A Rofi-driven notification manager**
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](https://www.freedesktop.org/wiki) - [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html) and it can be used to receive notifications from applications via [D-Bus](https://www.freedesktop.org/wiki/Software/dbus/).
### The name?
A portmanteau of "[notification](https://wiki.archlinux.org/title/Desktop_notifications)" and [Rofi](https://github.com/davatorium/rofi).
## Features
- Template-powered ([Jinja2](http://jinja.pocoo.org/)/[Django](https://docs.djangoproject.com/en/3.1/topics/templates/)) notification text.
- Run custom OS commands based on the matched notifications.
## Installation
### From crates.io
`nofi` can be installed from [crates.io](https://crates.io/crates/nofi):
```sh
$ cargo install nofi
```
The minimum supported Rust version is `1.64.0`.
### Arch Linux
`nofi` can be installed from the [AUR](https://aur.archlinux.org/packages/nofi-bin) using an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers). For example:
```sh
aura -A nofi-bin
```
### Binary releases
See the available binaries for different operating systems/architectures from the [releases page](https://github.com/ellsclytn/nofi/releases).
### Build from source
#### Prerequisites
- [D-Bus](https://www.freedesktop.org/wiki/Software/dbus)
#### Instructions
1. Clone the repository.
```sh
$ git clone https://github.com/ellsclytn/nofi && cd nofi/
```
2. Build.
```sh
$ CARGO_TARGET_DIR=target cargo build --release
```
Binary will be located at `target/release/nofi`.
## Usage
### On Xorg startup
You can use [xinitrc](#xinitrc) or [xprofile](#xprofile) for autostarting `nofi`.
#### xinitrc
If you are starting Xorg manually with [xinit](https://www.x.org/archive/X11R6.8.0/doc/xinit.1.html), you can `nofi` on X server startup via [xinitrc](https://wiki.archlinux.org/title/Xinit#xinitrc):
`$HOME/.xinitrc`:
```sh
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:
```sh
{ sleep 2; nofi; } &
```
#### xprofile
If you are using a [display manager](https://wiki.archlinux.org/title/Display_manager), you can utilize an [xprofile](https://wiki.archlinux.org/title/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](#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`:
```ini
[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`:
```ini
[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:
```sh
systemctl --user daemon-reload
systemctl --user start nofi.service
```
## Usage
`nofi` uses [`dbus-send(1)`](https://man.archlinux.org/man/dbus-send.1.en) to receive control instructions. There is currently only one instruction: viewing notification history.
```sh
# 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](https://i3wm.org/):
```sh
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](https://github.com/DaveDavenport/Rofication). This means it can be integrated into status bars like [i3status-rust](https://github.com/greshake/i3status-rust/) via the [Rofication block](https://docs.rs/i3status-rs/latest/i3status_rs/blocks/rofication/index.html). The socket path follows the [XDG Base Directory](https://wiki.archlinux.org/title/XDG_Base_Directory) specification which usually exposes the socket at `/run/user//nofi/socket`. This may vary between systems, so the socket path is output to `stdout` when `nofi` starts.
```ini
# Example i3status-rust integration
[[block]]
block = "rofication"
interval = 1
socket_path = "/run/user/1000/nofi/socket"
```
## Configuration
`nofi` configuration file supports [TOML](https://github.com/toml-lang/toml) format and the default configuration values can be found [here](./config/nofi.toml).
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](https://docs.rs/log/latest/log/enum.Level.html). Possible values are `error`, `warn`, `info`, `debug` and `trace`.
#### `template`
Sets the template for the notification message. The syntax is based on [Jinja2](http://jinja.pocoo.org/) and [Django](https://docs.djangoproject.com/en/3.1/topics/templates/) templates.
Simply, there are 3 kinds of delimiters:
- `{{` and `}}` for expressions
- `{%` or `{%-` and `%}` or `-%}` for statements
- `{#` and `#}` for comments
See [Tera documentation](https://tera.netlify.app/docs/#templates) for more information about [control structures](https://tera.netlify.app/docs/#control-structures), [built-in filters](https://tera.netlify.app/docs/#built-ins), etc.
##### Context
Context is the model that holds the required data for template rendering. The [JSON](https://en.wikipedia.org/wiki/JSON) format is used in the following example for the representation of a context.
```json
{
"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](https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html) 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:
```toml
[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:
```toml
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](#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:
```toml
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:
```toml
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](https://gotify.net/) notification when someone says hi to us in any chatting application matched by the regex.
## Related Projects
- [Rofication](https://github.com/DaveDavenport/Rofication)
- [runst](https://github.com/orhun/runst), which is what this project is a fork of.
## License
Licensed under either of [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) or [The MIT License](http://opensource.org/licenses/MIT) at your option.
## Copyright
Copyright © 2023, [Ellis Clayton](mailto:ellis@ellis.codes)