#+titl the final code here.

The final piece

Desktop notifications are pretty common in applications, it’s good to support them in our desktop environment.

It’s also a useful tool for our own Emacs extensions and scripts!

Password sync script (View on GitHub)

We’ll use a program called Dunst to accomplish this.

Dunst

  • Minimal
  • Easy to customize
  • Keyboard controlled
  • Fits in well with Polybar

Homepage: https://dunst-project.org/

On Ubuntu you can install via apt:

sudo apt install dunst

It’s pretty commonly available in other Linux distributions as well!

Launching Dunst

It’s just as simple as running dunst! We can run it and see its output with async-shell-command (M-&)

Now to try it out with notify-send:

notify-send "Test!"
notify-send -u critical "Your computer asplode"
notify-send -i "emblem-synchronizing" "Passwords synced!"

We can also call up previous notifications using the global C-` keybinding! Useful if you saw a notification but didn’t have a chance to read it.

We can add it to our efs/exwm-init-hook function:

(efs/run-in-background "dunst")

Customizing Dunst

Much of this is boilerplate, you can read more about how to configure Dunst here:

https://dunst-project.org/documentation/

We’ll add this to our Desktop.org file:

:tangle ~/.config/dunst/dunstrc :mkdirp yes

[global]
    ### Display ###
    monitor = 0

    # The geometry of the window:
    #   [{width}]x{height}[+/-{x}+/-{y}]
    geometry = "500x10-10+50"

    # Show how many messages are currently hidden (because of geometry).
    indicate_hidden = yes

    # Shrink window if it's smaller than the width.  Will be ignored if
    # width is 0.
    shrink = no

    # The transparency of the window.  Range: [0; 100].
    transparency = 10

    # The height of the entire notification.  If the height is smaller
    # than the font height and padding combined, it will be raised
    # to the font height and padding.
    notification_height = 0

    # Draw a line of "separator_height" pixel height between two
    # notifications.
    # Set to 0 to disable.
    separator_height = 1
    separator_color = frame

    # Padding between text and separator.
    padding = 8

    # Horizontal padding.
    horizontal_padding = 8

    # Defines width in pixels of frame around the notification window.
    # Set to 0 to disable.
    frame_width = 2

    # Defines color of the frame around the notification window.
    frame_color = "#89AAEB"

    # Sort messages by urgency.
    sort = yes

    # Don't remove messages, if the user is idle (no mouse or keyboard input)
    # for longer than idle_threshold seconds.
    idle_threshold = 120

    ### Text ###

    font = Cantarell 20

    # The spacing between lines.  If the height is smaller than the
    # font height, it will get raised to the font height.
    line_height = 0
    markup = full

    # The format of the message.  Possible variables are:
    #   %a  appname
    #   %s  summary
    #   %b  body
    #   %i  iconname (including its path)
    #   %I  iconname (without its path)
    #   %p  progress value if set ([  0%] to [100%]) or nothing
    #   %n  progress value if set without any extra characters
    #   %%  Literal %
    # Markup is allowed
    format = "<b>%s</b>\n%b"

    # Alignment of message text.
    # Possible values are "left", "center" and "right".
    alignment = left

    # Show age of message if message is older than show_age_threshold
    # seconds.
    # Set to -1 to disable.
    show_age_threshold = 60

    # Split notifications into multiple lines if they don't fit into
    # geometry.
    word_wrap = yes

    # When word_wrap is set to no, specify where to make an ellipsis in long lines.
    # Possible values are "start", "middle" and "end".
    ellipsize = middle

    # Ignore newlines '\n' in notifications.
    ignore_newline = no

    # Stack together notifications with the same content
    stack_duplicates = true

    # Hide the count of stacked notifications with the same content
    hide_duplicate_count = false

    # Display indicators for URLs (U) and actions (A).
    show_indicators = yes

    ### Icons ###

    # Align icons left/right/off
    icon_position = left

    # Scale larger icons down to this size, set to 0 to disable
    max_icon_size = 88

    # Paths to default icons.
    icon_path = /home/daviwil/.guix-extra-profiles/desktop/desktop/share/icons/gnome/256x256/status/:/home/daviwil/.guix-extra-profiles/desktop/desktop/share/icons/gnome/256x256/devices/:/home/daviwil/.guix-extra-profiles/desktop/desktop/share/icons/gnome/256x256/emblems/

    ### History ###

    # Should a notification popped up from history be sticky or timeout
    # as if it would normally do.
    sticky_history = no

    # Maximum amount of notifications kept in history
    history_length = 20

    ### Misc/Advanced ###

    # Browser for opening urls in context menu.
    browser = qutebrowser

    # Always run rule-defined scripts, even if the notification is suppressed
    always_run_script = true

    # Define the title of the windows spawned by dunst
    title = Dunst

    # Define the class of the windows spawned by dunst
    class = Dunst

    startup_notification = false
    verbosity = mesg

    # Define the corner radius of the notification window
    # in pixel size. If the radius is 0, you have no rounded
    # corners.
    # The radius will be automatically lowered if it exceeds half of the
    # notification height to avoid clipping text and/or icons.
    corner_radius = 4

    mouse_left_click = close_current
    mouse_middle_click = do_action
    mouse_right_click = close_all

# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
    # Calculate the dpi to use on a per-monitor basis.
    # If this setting is enabled the Xft.dpi value will be ignored and instead
    # dunst will attempt to calculate an appropriate dpi value for each monitor
    # using the resolution and physical size. This might be useful in setups
    # where there are multiple screens with very different dpi values.
    per_monitor_dpi = false

[shortcuts]

    # Shortcuts are specified as [modifier+][modifier+]...key
    # Available modifiers are "ctrl", "mod1" (the alt-key), "mod2",
    # "mod3" and "mod4" (windows-key).
    # Xev might be helpful to find names for keys.

    # Close notification.
    #close = ctrl+space

    # Close all notifications.
    #close_all = ctrl+shift+space

    # Redisplay last message(s).
    # On the US keyboard layout "grave" is normally above TAB and left
    # of "1". Make sure this key actually exists on your keyboard layout,
    # e.g. check output of 'xmodmap -pke'
    history = ctrl+grave

    # Context menu.
    context = ctrl+shift+period

[urgency_low]
    # IMPORTANT: colors have to be defined in quotation marks.
    # Otherwise the "#" and following would be interpreted as a comment.
    background = "#222222"
    foreground = "#888888"
    timeout = 10
    # Icon for notifications with low urgency, uncomment to enable
    #icon = /path/to/icon

[urgency_normal]
    background = "#1c1f26"
    foreground = "#ffffff"
    timeout = 10
    # Icon for notifications with normal urgency, uncomment to enable
    #icon = /path/to/icon

[urgency_critical]
    background = "#900000"
    foreground = "#ffffff"
    frame_color = "#ff0000"
    timeout = 0
    # Icon for notifications with critical urgency, uncomment to enable
    #icon = /path/to/icon

To refresh the configuration you’ll need to kill and restart Dunst:

pkill dunst && dunst &

Some things you’ll want to consider setting:

  • format - Customize how notification text contents are displayed
  • geometry - Where the notification appears and how large it should be by default
  • max_icon_size - Constrain icon display since some icons will be larger than others
  • icon_path - Important if your icons are not in a common location (like when using GNU Guix)
  • idle_threshold - Wait for user to become active for this long before hiding notifications
  • mouse_left/right/middle_click - Action to take when clicking a notification
  • Any of the key bindings in the shortcuts section (though these are deprecated in 1.5.0, use dunstctl)

Control Dunst with dunstctl

Starting with Dunst 1.5.0 there is a new command line tool called dunstctl which enables you to set up key bindings in your desktop environment (Emacs!) which launch dunstctl to control the running Dunst instance:

λ dunstctl --help
Usage: dunstctl <command> [parameters]
Commands:
  action                         Perform the default action, or open the
                                 context menu of the notification at the
                                 given position
  close                          Close the last notification
  close-all                      Close the all notifications
  context                        Open context menu
  history-pop                    Pop one notification from history
  is-paused                      Check if dunst is running or paused
  set-paused [true|false|toggle] Set the pause status
  debug                          Print debugging information
  help                           Show this help
(defun efs/dunstctl (command)
  (start-process-shell-command "dunstctl" nil (concat "dunstctl " command)))

(exwm-input-set-key (kbd "s-n") (lambda () (interactive) (efs/dunstctl "history-pop")))
(exwm-input-set-key (kbd "s-N") (lambda () (interactive) (efs/dunstctl "close-all")))

Enabling and disabling notifications

You can use either of the following commands to disable desktop notifications temporarily:

notify-send "DUNST_COMMAND_PAUSE"

killall -SIGUSR1 dunst

dunstctl set-paused true  # Only available form 1.5.0 onward

You can resume notifications (and see all the notifications that occurred while disabled) by running any of these commands:

notify-send "DUNST_COMMAND_RESUME"

killall -SIGUSR2 dunst

dunstctl set-paused false  # Only available from 1.5.0 onward

It can be useful to create an interactive function to enable/disable notifications so that you can use it in your configuration!

(defun efs/disable-desktop-notifications ()
  (interactive)
  (start-process-shell-command "notify-send" nil "notify-send \"DUNST_COMMAND_PAUSE\""))

(defun efs/enable-desktop-notifications ()
  (interactive)
  (start-process-shell-command "notify-send" nil "notify-send \"DUNST_COMMAND_RESUME\""))

(defun efs/toggle-desktop-notifications ()
  (interactive)
  (start-process-shell-command "notify-send" nil "notify-send \"DUNST_COMMAND_TOGGLE\""))

  (start-process-shell-command "notify-send" nil "notify-send \"This is from Emacs!\"")

Sending notifications from Emacs

Now that we can display notifications, we can use them in our Emacs configuration too:

Built-in functions

Emacs has a built-in function for this:

(notifications-notify :title "Test!"
                      :body "This is just a test!")

Manual: https://www.gnu.org/software/emacs/manual/html_node/elisp/Desktop-Notifications.html

alert.el

An alternative is alert.el: https://github.com/jwiegley/alert

(alert "This is just a test!" :title "Test!")

However, this library is usually only preferrable if you are writing a package that needs to show notifications that the user might want to customize.

Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address