Creating a modern tiling desktop environment using i3

Modern desktop environments like GNOME and KDE involving a lot of mousing around and I much prefer using the keyboard where I can. This is why I switched to the Ion tiling window manager back when I interned at Net Integration Technologies and kept using it until I noticed it had been removed from Debian.

After experimenting with awesome for 2 years and briefly considering xmonad , I finally found a replacement I like in i3. Here is how I customized it and made it play nice with the GNOME and KDE applications I use every day.

Startup script

As soon as I log into my desktop, my startup script starts a few programs, including:

  • dunst package: displays desktop notifications
  • gnome-settings-daemon: makes GTK applications look nice by applying my preferred theme amongst other things
  • gnome-keyring-daemon: remembers ssh public keys for the duration of my session
  • i3lock: locks the screen when I'm not around
  • nm-applet: handles wifi and VPN connections
  • syncthing: keeps my folders synchronized between machines
  • xcompmgr: required for applications that display shadows and other similar effects

Because of a bug in gnome-settings-daemon which makes the mouse cursor disappear as soon as gnome-settings-daemon is started, I had to run the following to disable the offending gnome-settings-daemon plugin:

dconf write /org/gnome/settings-daemon/plugins/cursor/active false

Notifications

You will probably also want to set the following in /etc/xdg/dunst/dunstrc to ensure that notifications use your default web browser:

browser = /usr/bin/sensible-browser

Here are the keyboard shortcuts you'll need to interact with the notifications that pop up:

  • Ctrl-Space to close the current notification
  • Ctrl-Shift-Space to close all notifications
  • Ctrl-<backtick> to show the last notification
  • Ctrl-Shift-. to show the context menu for the current notification

Screensaver

To make i3lock automatically lock my screen, I installed xautolock and added it to my startup script:

xautolock -time 30 -locker "i3lock -c 000000 -f" &

I can also trigger it manually using the following shortcut defined in my ~/.i3/config:

bindsym Ctrl+Mod1+l exec xautolock -locknow

Wallpaper

To set the wallpaper, I use feh. The first step is to run it once:

feh --bg-scale /usr/share/images/desktop-base/desktop-grub.png

so that it generates a script for itself at ~/.fehbg.

Then you can add that to your startup script or to ~/.i3/config:

exec --no-startup-id ~/.fehbg

Keyboard shortcuts

While keyboard shortcuts can be configured in GNOME, they don't work within i3, so I added a few more bindings to my ~/.i3/config:

# volume control
bindsym XF86AudioLowerVolume exec /usr/bin/pactl set-sink-volume @DEFAULT_SINK@ '-5%'
bindsym XF86AudioRaiseVolume exec /usr/bin/pactl set-sink-volume @DEFAULT_SINK@ '+5%'
bindsym XF86AudioMute exec /usr/bin/pactl set-sink-mute @DEFAULT_SINK@ toggle

# brightness control
bindsym XF86MonBrightnessDown exec /usr/bin/brightnessctl set 10%-
bindsym XF86MonBrightnessUp exec /usr/bin/brightnessctl set +10%

# show battery stats
bindsym XF86Battery exec gnome-power-statistics

# interactive screenshot by pressing printscreen
bindsym Print exec /usr/bin/gnome-screenshot -i
# crop-area screenshot by pressing Mod + printscreen
bindsym --release $mod+Print exec /usr/bin/gnome-screenshot -a

to make volume control, screen brightness, battery status and print screen buttons work as expected on my laptop.

These bindings require the following packages or scripts:

and your user account needs to be in the video group:

adduser francois video

Keyboard layout switcher

Another thing that used to work with GNOME and had to re-create in i3 is the ability to quickly toggle between two keyboard layouts using the keyboard.

To make it work, I wrote a simple shell script and assigned a keyboard shortcut to it in ~/.i3/config:

bindsym $mod+u exec /home/francois/bin/toggle-xkbmap

Suspend script

Since I run lots of things in the background, I have set my laptop to avoid suspending when the lid is closed by putting the following in /etc/systemd/login.conf:

HandleLidSwitch=lock

though you might want to have the laptop suspend if it's running on battery, using the following setting instead:

HandleLidSwitchExternalPower=lock

Instead, when I want to suspend to ram, I use the following keyboard shortcut:

bindsym Ctrl+Mod1+s exec /home/francois/bin/s2ram

which executes a custom suspend script to clear the clipboards (using xsel), flush writes to disk and lock the screen before going to sleep.

Window and workspace placement hacks

While tiling window managers promise to manage windows for you so that you can focus on more important things, you will most likely want to customize window placement to fit your needs better.

Working around misbehaving applications

A few applications make too many assumptions about window placement and are just plain broken in tiling mode. Here's how to automatically switch them to floating mode:

for_window [class="VidyoDesktop"] floating enable

You can get the Xorg class of the offending application by running this command:

xprop WM_CLASS

before clicking on the window.

Keeping IM windows on the first workspace

I run Gajim on my first workspace and I have the following rule to keep any new window that pops up (e.g. in response to a new incoming message) on the same workspace:

assign [class="Gajim"] 1

Automatically moving workspaces when docking

Here's a neat configuration blurb which automatically moves my workspaces (and their contents) from the laptop screen (eDP-1) to the external monitor (DP-3-1) when I dock my laptop:

# bind workspaces to the right monitors
workspace 1 output DP-3-1
workspace 2 output DP-3-1
workspace 3 output DP-3-1
workspace 4 output DP-3-1
workspace 5 output DP-3-1
workspace 6 output eDP-1

You can get these output names by running:

xrandr --display :0 | grep " connected"

Finally, because X sometimes fail to detect my external monitor when docking/undocking, I also wrote a script to set the displays properly and bound it to the appropriate key on my laptop:

bindsym XF86Display exec /home/francois/bin/external-monitor

Putting the system tray on the right monitor

If you find your systray on the wrong display after plugging an external monitor, try adding the following to your i3 config file:

bar {
    status_command i3status
    tray_output primary
}

and then restarting i3.

Mouse cursor

On Pop!_OS 20.04, I ran into a problem where only the mouse was using the default Xorg cursor instead of a styled and scalable one.

This was fixed using this work-around:

mkdir -p ~/.icons/default
cp -r /usr/share/icons/Pop/* ~/.icons/default/