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.
- 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!
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")
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 displayedgeometry
- Where the notification appears and how large it should be by defaultmax_icon_size
- Constrain icon display since some icons will be larger than othersicon_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 notificationsmouse_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, usedunstctl
)
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")))
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!\"")
Now that we can display notifications, we can use them in our Emacs configuration too:
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
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.