Switching from Gnome to a tiling window manager

After having thought about it since "forever", I finally decided to switch to a tiling window manager. I went with sway since it runs on wayland and since it seems to be the recommended "wayland version of i3", a tiling window manager that many of my tech friends use ;)

After a few days of using sway, I'm pretty sure that I won't switch back anytime soon. It feels super convenient to have all windows tiled on the screen and being able to rearrange and resize them easily with a few keyboard shortcuts.

There's still some things that didn't work instantly, so I'll try to document them here in hope that it's useful to others. Feedback welcome!

This blog post covers the following topics:

Install sway on Debian Buster

I run Debian Buster on my work machine. The sway components aren't available in Buster or buster-backports yet, so I went with installing the packages from Unstable or experimental manually. I'll probably help with backporting them to buster-backports once I settled on using sway.

Lucky enough, sway packages only bring one dependency that's not satisfied in Buster, which is libjson-c4. So for now, to install the sway Debian packages on Buster, you have to do the following:

mkdir ~/devel/sway && cd ~/devel/sway

wget http://ftp.de.debian.org/debian/pool/main/w/wlroots/libwlroots3_0.7.0-2_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/scdoc/scdoc_1.10.0-1_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/swaybg/swaybg_1.0-2_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/swaylock/swaylock_1.4-1_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/swayidle/swayidle_1.5-1_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/sway/sway-backgrounds_1.2-1_all.deb
wget http://ftp.de.debian.org/debian/pool/main/j/json-c/libjson-c4_0.13.1+dfsg-6_amd64.deb
wget http://ftp.de.debian.org/debian/pool/main/s/sway/sway_1.2-1_amd64.deb

apt install ./libwlroots3_0.7.0-2_amd64.deb ./scdoc_1.10.0-1_amd64.deb ./swaybg_1.0-2_amd64.deb ./swaylock_1.4-1_amd64.deb ./swayidle_1.5-1_amd64.deb ./sway-backgrounds_1.2-1_all.deb ./libjson-c4_0.13.1 ./sway_1.2-1_amd64.deb

# Install dunst, i3status and dmenu
apt install dunst i3status suckless-tools

# Install brightnessctl (for controlling the screen backlight) and
# blueman (for bluetooth management)
apt install brightnessctl brightness-udev blueman

Basic sway configuration

Sway brings a good basic configuration at /etc/sway/config. In order to customize it, copy the file over to ~/.config/sway/config. First things I changed were the following:

# Disable windows title bars
default_borter pixel

# Use tilix wrapper as terminal emulator (more on that later)
set $term ~/.config/sway/scripts/tilix-wrapper.sh

# My internal laptop screen
set $laptop_screen eDP-1

# Command to lock screen
set $lock 'swaylock -F -f -e -K -l -c 000000'

# Default wallpaper
output * bg ~/Pictures/favourite_background.jpg fill

# Idle configuration
exec swayidle -w \
         timeout 300 $lock \
         timeout 600 'swaymsg "output * dpms off"' \
         resume 'swaymsg "output * dpms on"' \
         before-sleep $lock

# Internal Thinkpad Keyboard
input "1:1:AT_Translated_Set_2_keyboard" {
    xkb_layout de,us
    # Change keyboard layouts on <Super>+<Space>
    xkb_options grp:win_space_toggle
}

# Cherry Keyboard
input "1130:275:Cherry_GmbH_CHERRY_Wired_Keyboard" {
    xkb_layout de,us
    # Change keyboard layouts on <Super>+<Space>
    xkb_options grp:win_space_toggle
}

# Internal Thinkpad Touchscreen
input "2:7:SynPS/2_Synaptics_TouchPad" natural_scroll "enabled"

# Status Bar
bar {
    position top
    # Use i3status as status bar
    status_command i3status
}

# Custom key bindings

# Lock screen
bindsym $mod+Escape exec $lock

# Audio and brightness key bindings
bindsym XF86AudioRaiseVolume exec pactl set-sink-volume @DEFAULT_SINK@ +5%
bindsym XF86AudioLowerVolume exec pactl set-sink-volume @DEFAULT_SINK@ -5%
bindsym XF86AudioMute exec pactl set-sink-mute @DEFAULT_SINK@ toggle
bindsym XF86AudioMicMute exec pactl set-source-mute @DEFAULT_SOURCE@ toggle
bindsym XF86MonBrightnessDown exec brightnessctl set 5%-
bindsym XF86MonBrightnessUp exec brightnessctl set +5%
bindsym XF86AudioPlay exec playerctl play-pause
bindsym XF86AudioNext exec playerctl next
bindsym XF86AudioPrev exec playerctl previous

# Bindings for Firefox and Thunderbird
bindsym $mod+Shift+b exec "env MOZ_ENABLE_WAYLAND=1 firefox"
bindsym $mod+Shift+m exec "thunderbird"

# Autostart

# Start dunst, a notification daemon
exec dunst

# Start some programs in fixed worspaces
assign [app_id="firefox"] → 1
exec "env MOZ_ENABLE_WAYLAND=1 firefox"
assign [class="thunderbird"] → 2
exec "thunderbird"

Picking an application launcher

The default application launcher to be used is dmenu (from suckless-tools). While it works okayish, I don't particularly like it. In my eyes, it looks rather old-fashioned, and even worse, it doesn't seem to have support for freedesktop.org desktop entries.

I looked around a bit and wofi sounded pretty promising. It's not in Debian yet but was easy to compile. A big downer though is that it depends on a newer libglib2.0 version (2.60) than in Debian Buster. I still compiled it in a Bullseye schroot and got a first impression. I like it's look and feel (after a bit CSS customization) and probably I'll go with packaging it for Debian.

For the moment, I'm stuck with dmenu on my working system, though.

Update: I packaged in the meantime and decided to install libglib2.0 from Bullseye to fullfill its dependencies. So I'm running wofi now and I'm very happy with it so far.

Fore reference, here's my wofi config file (~/.config/wofi/config):

mode=drun
colors=colors
filter_rate=100

And my custom wofi stylesheet (~/.config/wofi/style.css):

window {
    margin: 5px;
    #border: 2px solid #282C34;
    #border: 2px solid blue;
    #background-color: #282C34;
    #background-color: #282C34;
    background-color: transparent;
}

#input {
    margin-left: 70px;
    margin-right: 70px;
    margin-top: 10px;
    margin-bottom: 10px;
    border: 2px solid blue;
    #border: 2px solid #777D87;
    border: 2px solid grey;
    #background-color: #E5C07B;
    #background-color: #282C34;
    background-color: darkgrey;
}

#scroll {
    margin: 5px;
    #border: 2px solid #282C34;
    border: 2px solid #61AFEF;
    #background-color: #777D87;
    #background-color: #ABB2BF;
    background-color: #282C34;
}

#inner-box {
    margin: 20px;
}

#text {
    margin: 5px;
    color: #E5C07B;
}

Configure the status bar

I decided to go with the i3status status bar and it serves my purposes pretty well. Here's my config (/.config/i3status/config):

# i3status configuration file.
# see "man i3status" for documentation.

# It is important that this file is edited as UTF-8.
# The following line should contain a sharp s:
# ß
# If the above line is not correctly displayed, fix your editor first!

general {
        #colors = true
        colors = false
        interval = 5
}

order += "load"
order += "wireless _first_"
order += "ethernet _first_"
order += "path_exists VPN"
order += "battery all"
order += "tztime local"

# Customized wireless status
wireless _first_ {
        format_up = "W: (%quality at %essid) %ip"
        format_down = "W: down"
}

# Only show ethernet status when connected
ethernet _first_ {
        # if you use %speed, i3status requires root privileges
        format_up = "E: %ip"
        format_down = ""
}

# Display VPN status
path_exists VPN {
        # path exists when a VPN tunnel launched by nmcli/nm-applet is active
        path = "/proc/sys/net/ipv4/conf/tun0"
}

# Customized battery status
battery all {
        format = "%status %percentage"
        status_chr = "⚡"
        status_bat = "🔋"
        status_full = "☻"
}

# Localized time format
tztime local {
        #format = "%Y-%m-%d %H:%M:%S"
        format = "%a %d. %b %Y %H:%M"
}

load {
        format = "L: %1min"
}

Configure a notification daemon

I'm really used to getting notifications by my chat programs (XMPP, IRC, Signal), and I don't want to dismiss this. So I installed dunst and configured sway to auto-start it (see above). That's it, it worked instantly. Well, that was easy :)

Preserve working directory in new terminal instances

One thing that really annoyed me after switching to sway was, that the working directory wasn't preserved when spawning new terminal instances. I often open five or more terminal instances in parallel when working on a complex project, and I'm very used to just open a new terminal and continue working in the same directory there immediately.

So I was really eager to find a solution here. Turned out that it's not that easy and needs a bit of dirty scripting, but I found a solution (with help from some nice folks in #sway on Freenode).

First some words about the problem: spawning a new terminal in sway doesn't use whatever sophisticated means to spawn new instances of the same terminal process. Instead, it just spawns a fresh process of your favourite terminal emulator. While I really like tilix and used it as a tiling terminal emulator, I no longer want to use it's tiling features when I now have a tiling window manager. I'll stick for tilix for now as I like its look and feel, though.

So if the new terminal emulator process doesn't know about the working directory of your former terminal, what to do about it?

The solution: Luckily, it's possible to identify the PID of your focused window in sway using swaymsg -t get_tree. In case that the focused window is a terminal emulator, it's parent ID should be your shell. And the shells PWD can easily be determined by reading the symlink /proc/$PID/cwd.

So let's put this in a wrapper script under ~/.config/sway/scripts/tilix-wrapper.sh:

#!/bin/sh

# Small script that tries to determine the PWD of the focused terminal
# (in sway tiling window manager) and pass it to the newly spawned one.

TERMINAL_CMD="tilix --new-process"

FOCUSED_PID=""
if [ ! type jq 2>/dev/null ]; then
    echo "ERROR: jq not installed" >&2
else
    FOCUSED_PID="$(swaymsg -t get_tree | jq '.. | select(.type?) |
        select(.type=="con") | select(.focused==true).pid')"
fi

FOCUSED_PWD=""
# Check if $FOCUSED_PID is an integer
if [ "$FOCUSED_PID" -eq "$FOCUSED_PID" 2>/dev/null ]; then
    FOCUSED_PPID="$(ps -o pid= --ppid "$FOCUSED_PID" | awk '{print $1}')"
    if [ "$FOCUSED_PPID" -eq "$FOCUSED_PPID" 2>/dev/null ]; then
        FOCUSED_PWD="$(readlink "/proc/$FOCUSED_PPID/cwd")"
    fi
fi

# Spawn terminal in background
if [ -d "$FOCUSED_PWD" ]; then
    $TERMINAL_CMD --working-directory="$FOCUSED_PWD" $@ &
else
    $TERMINAL_CMD $@ &
fi

Finally, we have to set the script as $term in sways config (see above). Yay, now I've a solution to preserve my working directory when spawning new terminals!

Use gnome-keyring as SSH agent with sway

Another super annoying thing was that my SSH agent no longer worked with sway, mostly because I used gnome-keyring before and it wasn't spawned automatically when starting sway. So let's change that. I found it a bit complicated to get this working as docs on the internet said a lot of different things, but in the end, the following worked.

Since I still use gdm3 as desktop manager, gnome-keyring-daemon is started automatically during login. So the only thing that's missing is to initalize the gnome-keyring-daemon when starting a terminal. To do so, add the following to ~/.profile (in order to only do it on a login shell):

# Connect to and initalize gnome-keyring-daemon when in sway session
if [ "$DESKTOP_SESSION" = "sway" ]; then
    export $(gnome-keyring-daemon --start)
fi

What's missing

  • I want to start profanity (XMPP client) and irssi (IRC client) automatically in workspace 3, but so far I failed to find a working filter for sways assign feature to identify tilix instances with profanity/irssi (in order to automatically assign those terminals to workspace 3).
  • I miss the redshift feature of gnome 3. redshift itself doesn't support wayland yet. There's a fork with wayland support, but I didn't find time to look into it yet.
  • I'll probably switch from i3status to py3status soon as it's list of modules looks really promising.