Skip to content

tbidne/navi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Navi

GitHub release (latest SemVer) ci MIT

Table of Contents

Introduction

Navi is an application for defining desktop notifications in terms of a running notification system. That is, Navi does not implement desktop notifications from the ground up. Rather, given a running compatible notifications system, Navi provides a simple interface for defining notifications that hook into the running system.

There are built-in services for sending specific notifications, along with functionality to send custom notifications.

Motivation

Navi is useful for when we have a running notification server and want to define custom notification events. For example, we may want a notification for cpu temperature. We can define a "service" that includes:

  • The command to run.
  • The command output that should trigger a notification.
  • The notification to send.
# requires lm-sensors
[[single]]
command = """
  temp_res=$(sensors | head -n 3 | tail -n 1)
  regex="temp1:\\s*\\+([0-9]+)\\.[0-9]{0,2}°[C|F]"

  if [[ $temp_res =~ $regex ]]; then
    temp="${BASH_REMATCH[1]}"
    # not actually that hot...
    if [[ $temp -gt 20 ]]; then
      echo "true"
    else
      echo "false"
    fi
  else
    echo "couldn't parse: ${temp_res}"
    exit 1
  fi
"""
trigger = "true"

[single.note]
summary = "Temperature"
body = "We're hot!"
urgency = "critical"
timeout = 10

This allows us to define arbitrary notification services based on the current system. In other words, as long as we can query for a particular bit of information (e.g. bash code), then navi will take care of the rest: running this query every N seconds, sending notifications, caching previous values to avoid repeats, and error handling.

Requirements

Navi currently supports:

  • DBus

    If there is a DBus-compatible notification server running, navi can hook in directly. This has been tested with:

  • Libnotify

    Navi can also use the notify-send tool directly. This is largely redundant since notify-send itself requires a running DBus notification server, but this option is provided as an alternative.

Command-Line Args

Navi has the following usage:

Navi: A program for monitoring system status via desktop notifications.

Usage: navi [-c|--config-file PATH] [-v|--version]

  Navi allows one to easily define custom notification 'services' that hook into
  a running notification server. For example, one can provide a bash script
  that, say, queries the connection status of a given network device. Navi will
  periodically run this query and send a desktop notification if the status has
  changed. See github.com/tbidne/navi#README for full documentation.

Available options:
  -c,--config-file PATH    Path to config file. Defaults to
                           <xdg-config>/navi/config.toml.

  -h,--help                Show this help text

Version: 0.1

Config File

This argument overrides where Navi searches for the configuration file.

The default path to the config file is based on the XDG config directory. Given xdg-config, by default, Navi will look for <xdg-config>/navi/config.toml e.g. ~/.config/navi/config.toml.

Configuration

Navi is configured via a toml file, by default located at <xdg-config>/navi/config.toml. Full examples can be found in examples.

General Options

  • note-system: Optional. One of ["dbus"|"notify-send"]. Defaults to "dbus".
  • logging.severity: Optional. One of ["debug"|"info"|"error"]. Controls the logging level. Defaults to error.
  • logging.location: Optional. Either "default", "stdout" or "<filename>". No option or default uses <xdg-state>/navi/<timestamp>.log e.g. ~/.local/state/navi/<timestamp>.log.
  • logging.size-mode: Optional. Sets a size threshold for the file log directory, upon which we either print a warning or delete all prior logs, if the threshold is exceeded. The SIZE should include the value and units e.g. warn 10 mb, warn 5 gigabytes, delete 20.5B. Defaults to delete 50 mb. This only affects the default log path e.g. ~/.local/state/navi.
Example
note-system = "dbus"

[logging]
severity = "debug"
location = "stdout"
size-mode = "warn 10 mb"

Notification Options

The full list of notification options are:

  • summary: Text summary.
  • body: (Optional). Text body.
  • urgency: (Optional). One of ["low"|"normal"|"critical"].
  • timeout: (Optional). One of ["never"|<seconds>]. Determines how long notifications persist. Defaults to 10 seconds.

Service Options

Individual services have their own options, but there are a few that are common to most.

  • poll-interval: Optional. One of [NATURAL | STRING]. The provided interval be either a raw natural (interpreted as seconds), or a "time string" e.g. 1d2m3h4s, 3h20s. Determines how often a service is polled.
  • repeat-events: One of [true|false]. Determines if we send off the same notification twice in a row. Defaults to false (i.e. no repeats) unless stated otherwise.
  • error-events: One of ["none"|"repeats"|"no-repeats"]. Determines if we send off notifications for errors, and how we handle repeats. Defaults to "no-repeats" unless stated otherwise i.e. we send error notifications but no repeats.

Predefined

These are services that are built-in, in the sense that no custom script is required.

Battery Status

This service sends notifications based on the current battery status. Options include:

Specific Options
  • battery-status.app: One of ["sysfs" | "acpi" | "upower"].
    • sysfs reads /sys or /sysfs directly.
    • acpi requires the acpi utility.
    • upower requires the upower utility.
General Options
  • battery-status.poll-interval: Defaults to 30 seconds.
  • battery-status.repeat-events
  • battery-status.error-events
  • battery-status.timeout
Example
[battery-status]
app = "sysfs"
repeat-events = false
error-events = "repeats"

Battery Percentage

This service sends notifications based on the current battery percentage when it is discharging. Options include:

Specific Options
  • battery-percentage.alert.percent: integer in [0, 100]. Sends a notification once the battery level drops to this level.
  • battery-percentage.app: One of ["sysfs" | "acpi" | "upower"].
    • sysfs reads /sys or /sysfs directly.
    • acpi requires the acpi utility.
    • upower requires the upower utility.
General Options
  • battery-percentage.poll-interval: Defaults to 30 seconds.
  • battery-percentage.repeat-events
  • battery-percentage.error-events
  • battery-percentage.alert.urgency
  • battery-percentage.alert.timeout
Example
[battery-percentage]
app = "upower"
repeat-events = false
error-events = "repeats"

[[battery-percentage.alert]]
percent = 80

[[battery-percentage.alert]]
percent = 20
urgency = "critical"

Network Interface

This service sends notifications based on the network connectivity for given devices.

Specific Options
  • net-interface.device: The name of the network device to monitor (e.g. wlp0s20f3).
  • net-interface.app: One of ["nmcli" | "ip"].
    • nmcli requires the nmcli (NetworkManager cli) utility.
    • ip requires the ip utility.
General Options
  • net-interface.poll-interval: Defaults to 30 seconds.
  • net-interface.repeat-events
  • net-interface.error-events
  • net-interface.alert.urgency
  • net-interface.alert.timeout
Example
[[net-interface]]
app = "nmcli"
device = "wlp0s20f3"

[[net-interface]]
app = "ip"
device = "enp0s31f6"

Custom

Single

This service sends a single notification based on an arbitrary command.

Specific Options
  • single.command: Command literal or path to a script.
  • single.name: Optional name to be used in logging.
  • single.trigger: Result that triggers the notification.
General Options
  • single.poll-interval: Defaults to 30 seconds.
  • single.repeat-events
  • single.error-events
  • single.note.summary
  • single.note.body
  • single.note.urgency
  • single.note.timeout
Example
# Send alert when the current minute is even
[[single]]
poll-interval = 10
command = """
  min=`date +%M`;
  if [[ \"$min % 2\" -eq 0 ]]; then
    echo -n "true"
  else
    echo -n "false"
  fi
"""
trigger = "true"

[single.note]
summary = "Even/Odd"
body = "We're even, yay!"
timeout = 10

Multiple

This service sends multiple notifications based on an arbitrary command.

Specific Options
  • multiple.command: Command literal or path to a script.
  • multiple.name: Optional name to be used in logging.
  • multiple.trigger-note.trigger: Result that triggers the notification.
General Options
  • multiple.poll-interval: Defaults to 30 seconds.
  • multiple.repeat-events
  • multiple.error-events
  • multiple.trigger-note.summary
  • multiple.trigger-note.body
  • multiple.trigger-note.urgency
  • multiple.trigger-note.timeout
Example
# Manual battery level alerts
[[multiple]]
name = "battery-manual"
command = """
  regex="([0-9]{1,3})%"
  power=$(upower -i `upower -e | grep 'BAT'` | grep percentage | awk '{print $2}')

  if [[ $power =~ $regex ]]; then
      power_num="${BASH_REMATCH[1]}"

      if [[ $power_num -lt 5 ]]; then
          echo 5
      elif [[ $power_num -lt 40 ]]; then
          echo 40
      elif [[ $power_num -lt 80 ]]; then
          echo 80
      else
          echo 100
      fi
  else
      echo "Error reading battery: $power"
  fi
"""

[[multiple.trigger-note]]
trigger = "100"

[multiple.trigger-note.note]
summary = "Battery Percentage"
body = "Full"
timeout = 10

[[multiple.trigger-note]]
trigger = 80

[multiple.trigger-note.note]
summary = "Battery Percentage"
body = "< 80"
timeout = 10

[[multiple.trigger-note]]
trigger = "40"

[multiple.trigger-note.note]
summary = "Battery Percentage"
body = "< 40"
urgency = "critical"
timeout = 10

Building

If you have never built a haskell program before, Cabal is probably the best choice.

Cabal

Prerequisites

Using ghcup, install cabal 2.4+ and one of:

  • ghc 9.6
  • ghc 9.8
  • ghc 9.10

Build Navi

Once you have cabal and ghc, navi can be built with cabal build or installed globally (i.e. ~/.cabal/bin/) with cabal install.

Nix

Prerequisites

Manually

Building with nix uses flakes. navi can be built with nix build, which will compile and run the tests.

Nix expression

Because Navi is a flake, it can be built as part of a nix expression. For instance, if you want to add Navi to NixOS, your flake.nix might look something like:

# flake.nix
{
  inputs.navi.url = "github:tbidne/navi/main";
}

Then include this in the systemPackages:

# wherever your global packages are defined
{
  environment.systemPackages = [
    navi.packages."${system}".default
  ];
}

About

A program for defining custom desktop notifications.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published