Wayland Rice - part II
18 Aug 2024Thanks to all the brilliant upstream work from wayland/mesa/linux/nvidia devs, it looks like Wayland is fully usable for me now - time to get to work replicating the desktop experience I refined over many months in bspwm, sxhkd, polybar and others.
General Theming
I like and use the dracula dark theme palette, and with so many available configs ready to go for it, you can quickly and easily make a very consistent and integrated looking desktop environment with it, regardless of the mix of software tools you choose. This is the end result; I’ll break down all the main apps and configs with some additional sweetners on the way Note: windows are all set to floating here just to highlight the theming a bit better, normally they would all be tiled other than the rofi application launcher
River
Let’s start with riverwm - the wayland compositor and tiling window manager I chose. Initially I planned to look at river and hyprland after discovering that sway was opinionated about a couple of things that I had different opinions on. But I never got as far as hyprland because I loved the initial foray with river so much. Seems hyprland got more mind share than river though - I hope Isaac and the rest of the river community have enough momentum to keep it going!
Launching river
Once logged in on a tty you can just run river
to launch it. Graphical logins are so overkill. But I’m also too lazy even for that, so I have this in my fish
config (convert to bash and add to .profile
or similar)
if [ -z "$WAYLAND_DISPLAY" ] && [ $(tty) = "/dev/tty1" ]
exec river
end
.. which checks that a Wayland compositor isn’t already running and that I’m on tty1
, if so it starts river up.
River Init
Similar to bspwm, river is basically configured via an executable invoking a control program (riverctl
). Shell script is easiest for me, but you can use anything that can delegate to the control binary. The docs are pretty comprehensive and the supplied example init file sets good defaults and is well commented so it’s a great place to start. Apart from some changes to key mappings to make it more similar to what I was familiar with in sxhkd, here are the key things I changed from the defaults.
# ----------------------------------------------------------------------------
# Startup apps and services
# ----------------------------------------------------------------------------
import-gsettings &
dunst &
launch-waybar &
pgrep -x greenclip > /dev/null || /usr/bin/greenclip daemon &
squeezelite -n Office &
sway-rotate-wallpapers.sh &
swayidle -w timeout 1230 "notify-send -u low System 'Locking session in 30 seconds'" \
timeout 1260 "swaylock-desktop-image.sh" \
timeout 1800 "wlopm --off '*'" resume "wlopm --on '*'" &
brave --profile-directory=Default &
brave --profile-directory=Work &
signal-desktop &
Many of the above are covered in sections below, but I basically ensure a bunch of things run as soon as river starts.
The swayidle config runs a background process to handle idle time. In my case, it waits about 20 minutes before sending a low urgency notification to the desktop warning of imminent lock (so I can move the mouse or press a key if I’ve been sat watching a video and want to reset the idle timer). 30s later it will run another custom script to lock the desktop: this is a common idea where it takes a screenshot, fuzzes and blurs it for privacy and sets it as the background for the lock screen. If the machine remains idle for another 10 minutes, the display is turned off by wlopm. Swaylock theming is shown further below in the main theming section.
Tiler
The other key part of my river init
was replacing the default tiler with something that was a bit closer to bspwm
. You should play with the default one to understand the basics and see what you like and what you don’t. For me, the killer was having to have the same layout applied across all “workspaces” (tags). I need different layouts per tag. wideriver is what I’m currently using, but maybe something else will appear that works better for me :)
River init
code for the tiler, with border colors from the dracula scheme:
# ----------------------------------------------------------------------------
# Layout manager, window settings and launcher
# ----------------------------------------------------------------------------
riverctl map normal Super J focus-view next
riverctl map normal Super K focus-view previous
riverctl map normal Super+Shift J swap next
riverctl map normal Super+Shift K swap previous
riverctl map normal Super F toggle-float
riverctl map normal Super Z toggle-fullscreen
riverctl map normal Super N zoom
riverctl map normal Super Left send-layout-cmd wideriver "--layout left"
riverctl map normal Super Right send-layout-cmd wideriver "--layout right"
riverctl map normal Super H send-layout-cmd wideriver "--ratio -0.05"
riverctl map normal Super L send-layout-cmd wideriver "--ratio +0.05"
riverctl default-layout wideriver
wideriver \
--layout left \
--layout-alt monocle \
--stack dwindle \
--count-master 1 \
--ratio-master 0.50 \
--count-wide-left 0 \
--ratio-wide 0.35 \
--no-smart-gaps \
--inner-gaps 3 \
--outer-gaps 3 \
--border-width 2 \
--border-width-monocle 2 \
--border-width-smart-gaps 3 \
--border-color-focused "0x6272a4" \
--border-color-focused-monocle "0x586e75" \
--border-color-unfocused "0x282a36" \
--log-threshold info \
> "$HOME/wideriver.log" 2>&1 &
Waybar
Waybar is a highly configurable, extensible and themable bar for a number of wayland compositors. It’s well documented and easy enough to play with it to get the setup and the modules you want - I’ll just add the bits of config here relevant to this rice. To get the fonts and colors, I start the waybar style.css
with:
@import url("./colors.css");
* {
/* `otf-font-awesome` is required to be installed for icons */
font-family: FontAwesome, Roboto, sans-serif;
font-size: 20px;
}
window#waybar {
background-color: @background-alpha;
color: @foreground;
transition-property: background-color;
transition-duration: .5s;
}
relevant distro packages or other installs will be required to get the fonts
The colors.css
file in the same directory just defines the dracula palette
@define-color background-alpha rgba(0, 0, 0, 0.6);
@define-color background #282a36;
@define-color selection #44475a;
@define-color foreground #f8f8f2;
@define-color comment #6272a4;
@define-color cyan #8be9fd;
@define-color green #50fa7b;
@define-color orange #ffb86c;
@define-color pink #ff79c6;
@define-color purple #bd93f9;
@define-color red #ff5555;
@define-color yellow #f1fa8c;
Tags (virtual desktops) in river I have on the left of the bar, my music player in the centre and everything else to the right. Layout config is
"modules-left": ["river/tags"],
"modules-center": ["custom/squeezebox"],
"modules-right": ["custom/pacman", "pulseaudio", "disk#root", "disk#home", "cpu", "memory", "temperature", "keyboard-state", "clock", "tray"],
river/tags
are defined in the river init
file and are per the default from the example config. They are styled as:
button {
box-shadow: inset 0 -3px transparent;
border: none;
border-radius: 0;
padding: 0 10px;
color: @comment;
}
button:hover {
background: inherit;
color: @foreground;
}
button.focused {
color: @foreground;
box-shadow: inset 0 -3px @pink;
}
button.urgent {
background-color: @red;
}
There’s not much to the styling of the modules on the right, other than to set the appropriate foreground color
property in the style.css
for the relevant module.
Custom Waybar Modules
Code for my trivial custom waybar-modules is available on github.
I use one that just periodically checks which packages need upgrading and outputs the count of them on the bar with a package icon. When you hover over the tooltip shows the actual packages. This works with pacman
for Arch but could easily be adapted for any other package manager from another distro
The ~/.config/waybar/config
snippet to define it is:
"custom/pacman": {
"format": "{}",
"return-type": "json",
"escape": true,
"interval": 3600,
"exec": "$HOME/projects/waybar-modules/pacman/pacman-upgrades",
"on-click": "alacritty --hold -e 'yay -Syyu'"
}
and a bit of custom style.css
#custom-pacman {
color: @red;
}
The other module is for my multi-room audio system, based on squeezebox (read the posts on that to see why I chose it!)
Config for the waybar module:
"custom/squeezebox": {
"interval": 5,
"format": "🎜 {} {percentage}%",
"return-type": "json",
// IP address is the address of your squeezebox server, "Office" here is the name of the player to control
"exec": "$HOME/waybar-modules/.venv/bin/python $HOME/waybar-modules/squeezebox/squeezebox 10.1.2.3 Office",
"on-click": "$HOME/waybar-modules/.venv/bin/python $HOME/waybar-modules/squeezebox/squeezebox 10.1.2.3 Office toggle"
}
.. the styling simply uses a different foreground colour depending on whether the player is paused/stopped or playing.
#custom-squeezebox.pause, #custom-squeezebox.stop {
color: @comment;
}
#custom-squeezebox.play {
color: @green;
}
Additional Theming
I’m using dunst for desktop notification management, and rofi as an application launcher. Both are highly customisable and themable.
Dunst
Relevant parts of the dunstrc
config..
[global]
monitor = 0
follow = mouse
width = 800
height = 300
origin = bottom-center
gap_size = 0
separator_color = frame
sort = yes
font = "Roboto 14"
line_height = 0
markup = full
format = "<b>%s</b>\n%a\n\n%b"
alignment = left
vertical_alignment = center
show_age_threshold = 60
show_indicators = yes
enable_recursive_icon_lookup = true
icon_theme = Tela-purple
icon_position = left
min_icon_size = 32
max_icon_size = 128
icon_path = /usr/share/icons/Tela-purple/scalable/status/:/usr/share/icons/Tela-purple/scalable/devices/:/usr/share/icons/Tela-purple/scalable/places:/usr/share/icons/Tela-purple/scalable/mimetypes
[urgency_low]
background = "#282a36"
foreground = "#f8f8f2"
frame_color = "#6272a4"
timeout = 5
[urgency_normal]
background = "#282a36"
foreground = "#f8f8f2"
frame_color = "#50fa7b"
timeout = 10
[urgency_critical]
background = "#800000"
foreground = "#ffffff"
frame_color = "#ff5555"
timeout = 0
Rofi
Rofi is well documented, here is my config.rasi
file..
/*
* ░█▀▄░█▀█░█▀▀░▀█▀░░░▀█▀░█░█░█▀▀░█▄█░█▀▀
* ░█▀▄░█░█░█▀▀░░█░░░░░█░░█▀█░█▀▀░█░█░█▀▀
* ░▀░▀░▀▀▀░▀░░░▀▀▀░░░░▀░░▀░▀░▀▀▀░▀░▀░▀▀▀
*/
configuration {
ssh-command: "alacritty -e \"{ssh-client} {host} [-p {port}]\"";
}
@theme "arthur"
* {
drac-bgd: #282a36;
drac-bgd-t: #282a36cc;
drac-cur: #44475a;
drac-fgd: #f8f8f2;
drac-cmt: #6272a4;
drac-cya: #8be9fd;
drac-grn: #50fa7b;
drac-ora: #ffb86c;
drac-pnk: #ff79c6;
drac-pur: #bd93f9;
drac-red: #ff5555;
drac-yel: #f1fa8c;
font: "Roboto 18";
foreground: @drac-fgd;
}
window {
width: 800px;
}
inputbar {
color: @drac-cya;
background-color: @drac-bgd-t;
children: [ prompt,textbox-prompt-colon,entry,case-indicator ];
}
textbox-prompt-colon {
expand: false;
str: ":";
margin: 0px 0.3em 0em 0em ;
text-color: @drac-grn;
}
prompt,case-indicator {
text-color: @drac-grn;
}
listview {
background-color: @drac-bgd-t;
}
entry, prompt {
font: "Roboto 20";
}
element {
orientation: horizontal;
children: [ element-icon, element-text ];
spacing: 5px;
}
element {
orientation: horizontal;
children: [ element-icon, element-text ];
spacing: 5px;
}
element selected.normal {
background-color: @drac-pnk;
}
element normal active {
foreground: @drac-yel;
}
element normal urgent {
foreground: @drac-red;
}
element alternate active {
foreground: @drac-ora;
foreground: @drac-bgd;
}
element alternate urgent {
foreground: @drac-red;
}
element selected active {
background-color: @drac-ora;
foreground: @drac-bgd;
}
element selected urgent {
background-color: @drac-red;
foreground: @drac-bgd;
}
and the relevant keybindings from river/init
..
# Rofi
riverctl map normal Super Space spawn "rofi -modi run,drun,window,ssh -show drun"
riverctl map normal Super X spawn "rofi -modi \"clipboard:greenclip print\" -show clipboard"
GTK
I’m using the dracula gtk theme, but not the icons from the same place - I’m using Tela purple dark icons instead. You can use something like lxappearance to help configure the various GTK settings files, or just update them manually.
From ~/.gtkrc-2.0
..
gtk-theme-name="dracula"
gtk-icon-theme-name="Tela-purple-dark"
and from ~/.config/gtk-3.0/settings.ini
[Settings]
gtk-application-prefer-dark-theme=1
gtk-theme-name=dracula
gtk-icon-theme-name=Tela-purple-dark
The import-gsettings
script, called at the top of the river init
script, is a hacky workaround for some gnome/gtk software theming issues, I can’t recall where I found this now but credit goes to someone else for it…
config="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-3.0/settings.ini"
if [ ! -f "$config" ]; then exit 1; fi
gnome_schema="org.gnome.desktop.interface"
gtk_theme="$(grep 'gtk-theme-name' "$config" | sed 's/.*\s*=\s*//')"
icon_theme="$(grep 'gtk-icon-theme-name' "$config" | sed 's/.*\s*=\s*//')"
cursor_theme="$(grep 'gtk-cursor-theme-name' "$config" | sed 's/.*\s*=\s*//')"
font_name="$(grep 'gtk-font-name' "$config" | sed 's/.*\s*=\s*//')"
gsettings set "$gnome_schema" gtk-theme "$gtk_theme"
gsettings set "$gnome_schema" icon-theme "$icon_theme"
gsettings set "$gnome_schema" cursor-theme "$cursor_theme"
gsettings set "$gnome_schema" font-name "$font_name"
Wallpapers
I use another script, launched from the river init
file, to rotate the wallpaper on the desktop. The wallpaper images are all in a single directory and this script picks a random one every 15 minutes from those available then uses swaybg to display it.
My wallpapers are either predominantly purple, dracula/vampire themed, or both :)
#!/usr/bin/env bash
#
# ░█░█░█▀█░█░░░█░░░█▀█░█▀█░█▀█░█▀▀░█▀▄░█▀▀
# ░█▄█░█▀█░█░░░█░░░█▀▀░█▀█░█▀▀░█▀▀░█▀▄░▀▀█
# ░▀░▀░▀░▀░▀▀▀░▀▀▀░▀░░░▀░▀░▀░░░▀▀▀░▀░▀░▀▀▀
#
src_dir=$HOME/Pictures/wallpapers/
interval=900 # seconds
killall swaybg
swaybg -i $(find $src_dir -maxdepth 1 -type f | shuf -n1) -m fill &
old_pid=$!
while true; do
sleep $interval
swaybg -i $(find $src_dir -maxdepth 1 -type f | shuf -n1) -m fill &
next_pid=$!
sleep 5
kill $old_pid
old_pid=$next_pid
done
Swaylock
Here’s the ~/.local/bin/swaylock-desktop-image.sh
code touched on earlier.. some of the effects here you can get from an additional swaylock package (swaylock-effects
) that you might want to checkout instead.
#!/bin/bash
#
# ░█▀▀░█░█░█▀█░█░█░█░░░█▀█░█▀▀░█░█
# ░▀▀█░█▄█░█▀█░░█░░█░░░█░█░█░░░█▀▄
# ░▀▀▀░▀░▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀▀▀░▀░▀
#
# create a lock screen image from the current desktop background for swaylock
# to use.
#
# ----------------------------------------------------------------------------
tgt_dir=$HOME/Pictures
tgt_file=${tgt_dir}/screenlockimage.png
mkdir -p ${tgt_dir}
if [ $(command -v grim mogrify | wc -l) -eq 2 ]; then
rm -f ${tgt_file}
grim ${tgt_file}
mogrify -scale 5% -scale 2000% ${tgt_file}
mogrify -blur 0 ${tgt_file}
swaylock -i ${tgt_file}
else
swaylock
fi
..and my swaylock config (~/.config/swaylock/config
) with dracula theme colors
#
# ░█▀▀░█░█░█▀█░█░█░█░░░█▀█░█▀▀░█░█
# ░▀▀█░█▄█░█▀█░░█░░█░░░█░█░█░░░█▀▄
# ░▀▀▀░▀░▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀▀▀░▀░▀
#
ignore-empty-password
show-failed-attempts
daemonize
scaling=fill
font=Roboto
indicator-radius=250
indicator-thickness=25
line-uses-ring
bs-hl-color=ff79c6
key-hl-color=50fa7b
separator-color=f8f8f2
color=282a36
inside-color=282a36cc
ring-color=bd93f9
text-color=f8f8f2
inside-clear-color=282a36cc
ring-clear-color=f1fa8c
text-clear-color=f8f8f2
inside-wrong-color=282a36cc
ring-wrong-color=ff5555
text-wrong-color=f8f8f2
inside-ver-color=282a36cc
ring-ver-color=50fa7b
text-ver-color=f8f8f2
I also have the script bound to a key combo in river init
so that I can invoke it manually if I step away from the machine.
riverctl map normal Control+Alt Backspace spawn swaylock-desktop-image.sh
and here are the lock screens with 4 states of
-
unlock in progress
-
verifying
-
incorrect password
-
input cleared
Wrap Up
I’ve had a good time configuring the desktop so far - probably some more to come. But for now, it’s at least as good looking as my previous Xorg based setup was, better in places, and I’m happy with everything from usability to look & feel to performance. Hope you have as much fun ricing your own :)