Window manager for Linux: Switching to Sway and customizing it

Recently I’ve been embarking on a quest to find the best window manager for Linux. I finally switched to Sway this week, and here’s my experience.

The problem with my current window manager

Two weeks ago, the release of KWin 5.19.0 brought the hope of finally fixing the KDE Wayland experience.
I finally went back to AMD (X5700 XT) and for months now, I’ve been dealing with overlapping windows flickering as soon as the main front window is out of focus. That was apparently fixed with 5.19.

While Firefox and any other application don’t create any more flickering artifacts whenever they’re overlapping – subsurface clipping was one of the selling points of KWin 5.19 – now the mouse started to leave a trail whenever above the desktop background, and window positions started not matching their displayed place.

window manager for linux

This was with the fully opensource AMD graphics stack, which allows me to play games natively under Linux already (and mesa-9999).

I decided I had enough, and started checking what other compositors projects (never liked Gnome appearance and styles) were claiming Wayland support with little issues.
Being born just as an equivalent of an X compositor and tiling window manager, Sway seemed to be one of the most promising projects.

In fact, after emerging 4 packages, gui-wm/sway was installed.
Executing rc-config restart xdm made it instantly appear in SDDM’s list, but there were still a couple niceties to re-add before switching: KDE is a heavily integrated desktop environment. Migrating to a bare compositor/window manager meant (as far as I knew):

  1. giving up hotkeys
  2. giving up media hotkeys
  3. giving up graphical shortcuts to applications
  4. giving up the system tray
  5. giving up magnetic window borders

After a bit of fiddling, and some really good resources, I managed to solve 1) and 2) easily.
After one week, turns out 3) and 4) are not really a requirement, and 5) is sort-of baked in in Sway already.
One afterthought was the missing ALT+F2 combination to start apps: x11-misc/dmenu solves the problem brilliantly.

Requirements

# cd /etc/portage
# cat sets/sway
gui-wm/sway
media-sound/playerctl
x11-misc/dmenu

# cat package.accept_keywords/sway_unmasks
gui-apps/swaylock
gui-wm/sway
media-sound/playerctl

# cat package.use/sway
gui-wm/sway tray

### add "mpris" to global USEs in your make.conf
### enables controlling mpris-enabled players (mpv) via Media Player Remote Interfacing Specification (a DBus interface for remote control)

The tray USE on sway enables the desktop notification protocol used by KDE.

Tweaking the default config

Of the default config ( /etc/sway/config ), you may want to comment out things you don’t need.
Some other things you can overwrite:

  • output * bg <path-to-possibly-non-existent-background>: comment out: Sway will complain if you didn’t enable the wallpapers USE (which installs the default wallpaper)
  • bar { ... }: comment out: we will customize this further later, the default datetime format is US 12h
  • ensure that the last line include /etc/sway/config.d/* is not commented

/etc/sway/config.d/apps

Here we setup the default applications.
$menu is actually the same as the default config, but I still set it up in a config.d file in case the default ever changes.

Being a KDE user for a long time, konsole is the default choice. I actually still prefer Mod+d -> kons -> Enter anyway.

set $term konsole
set $menu dmenu_path | dmenu | xargs swaymsg exec --

/etc/sway/config.d/input

Here we setup the keyboard and mouse layout, nothing too fancy.
You can add multiple layouts and toggle between them via hotkeys (still haven’t used/learned it).

Note that the “type” selectors allow you to apply the same configuration to all keyboard and pointer inputs that sway identifies. You can further refine the selection if you have a strong case for it and add specific settings for specific input sources, but the config below will just work.

input "type:keyboard" {
    xkb_layout it,us
}

input "type:pointer" {
    left_handed disabled
    natural_scroll disabled
    dwt disabled
}

/etc/sway/config.d/lockscreen

This is the configuration for screen auto-lock.
I just tweaked the default settings to lower timeouts (180/270s vs 300/600).

exec swayidle -w \
    timeout 180 'swaylock -f -c 000000' \
    timeout 270 'swaymsg "output * dpms off"' \
        resume 'swaymsg "output * dpms on"' \
    before-sleep 'swaylock -f -c 000000'

/etc/sway/config.d/media_keys

This binds the media hotkeys, which were a very important point to me.
I really dislike having to always go for the system tray, pulling up the config, selecting the right sink and finally adjusting every single time my wife tries to communicate with me. Also, I don’t have a system tray anymore. You can put pavucontrol in a separate workspace and leave it maximized, but it gets super-annoying after a couple reboots.

Three notes on this one:

  • --locked will allow you to use the hotkeys even when the screen is locked
  • The AudioNext and AudioPrev buttons were hijacked to switch sinks and move all the streams around – see next section for the scripts
  • @DEFAULT_SINK@ it’s an identifier coming from Pulseaudio: it selects the sink that is currently set as default (note: this does not necessarily correspond to the currently in-use sink)
#bindsym XF86MonBrightnessDown exec brightnessctl set 5%-
#bindsym XF86MonBrightnessUp exec brightnessctl set +5%
bindsym --locked XF86AudioLowerVolume exec pactl set-sink-volume @DEFAULT_SINK@ -5%
bindsym --locked XF86AudioMicMute exec pactl set-source-mute @DEFAULT_SOURCE@ toggle
bindsym --locked XF86AudioMute exec pactl set-sink-mute @DEFAULT_SINK@ toggle
bindsym --locked XF86AudioNext exec /etc/sway/move_sinks_monitor.sh
bindsym --locked XF86AudioPlay exec playerctl play-pause
bindsym --locked XF86AudioPrev exec /etc/sway/move_sinks_headphones.sh
bindsym --locked XF86AudioRaiseVolume exec pactl set-sink-volume @DEFAULT_SINK@ +5%

/etc/sway/config.d/status_bar

This 95% copy-paste from the default config is just to switch away from the annoying American format for dates and times.

bar {
    position top
    status_command while date +'%Y.%m.%d %H:%M:%S'; do sleep 1; done

    colors {
        statusline #ffffff
        background #323232
    inactive_workspace #32323200 #32323200 #5c5c5c
    }
}

/etc/sway/config.d/wallpaper

Just replacing the default background with my own =)

output * bg /etc/sway/Wallpaper.png fill

Bash scripts to control audio

You may have noticed that the media_keys config file above references a couple bash scripts.
It took me a bit of experimentation (also thanks to this post), but eventually I managed to map the essential media keys (audio control) and use the previous/next track keys to switch streams between the audio outputs.

/etc/sway/move_sinks.sh

This script:

  1. Sets the default sink to the provided one (via Pulseaudio sink index)
  2. Moves all the streams to the new default sink

As I mentioned above, the default sink is not necessarily the currently used/playing one. It is the sink where new streams are automatically added to.
This means that if we were to move all the streams to the new sink without changing the default one, an application starting another stream will place it on the device that we want to “deselect”.

One more important consequence of not moving the default sink is that volume up/down and mute keys would not work. Since we don’t have a reference to which stream we want to change when we press the buttons, we can only change the volume for the @DEFAULT_SINK@ via pactl.
If we don’t change it here, we will keep changing the volume for another device.

If you are annoyed by the messages in the logs, you can later comment the logger lines, however I recommend keeping them at least in the beginning, where you may be debugging why things don’t work.

#!/bin/bash

logger "sway: $(basename $0): called with $1"
pacmd set-default-sink "$1"
logger "sway: $(basename $0): set default sink to: $(pacmd dump | grep set-default-sink)"
pacmd list-sink-inputs | grep index | while read line; do
    pacmd move-sink-input $(echo $line | cut -f2 -d' ') "$1"
done
logger "sway: $(basename $0): done moving sinks!"

/etc/sway/move_sinks_headphones.sh

This script uses some bash-fu to grab the sink index from a small part of the device name.

You can list your available sink names and indexes with pacmd list-sinks | grep -e 'index:' -e 'name:', and grab a unique part of the name for each sink. For my Logitech G933 headphones and my HDMI monitor, “G933” and “hdmi” work perfectly.

Note that the sink indexes and names may change, so you most likely want something unique from the name: alsa_output.usb-Logitech_G933_Artemis_Spectrum_Snow-00.iec958-stereo -> G933.

In my particular case, when I have a decent multi-channel source, I setup the headphone switching to also allow me to select the software-emulated surround sink: if the default sink is already the headphone one, I switch it to “vsurround” instead.
Each key press allows to toggle between the stereo and vsurround mode.

If you absolutely want to distinguish when one or the other is activated, you can play a very short sound that you can identify. If I’m in doubt, I usually quickly cycle through the monitor first, and then go again for the headphones.

#!/bin/bash

sink_identifier="G933"
sink_idx=$(pacmd list-sinks | grep -e 'index:' -e 'name:' | grep "${sink_identifier}" -B1 | head -1 | sed 's: ::g' | cut -d':' -f2)
if pacmd dump | grep set-default-sink | grep -q "${sink_identifier}"; then
    sink_identifier="vsurround"
    sink_idx=$(pacmd list-sinks | grep -e 'index:' -e 'name:' | grep "${sink_identifier}" -B1 | head -1 | sed 's: ::g' | cut -d':' -f2 | tr -d ' ')
fi
logger "sway: $(basename $0): sink_identifier: $sink_identifier"

/etc/sway/move_sinks.sh "$sink_idx"
logger "sway: $(basename $0): called move_sinks.sh with $sink_idx"

/etc/sway/move_sinks_monitor.sh

Just like the headphone one, this scripts switches to the monitor sink. This is simpler than the headphones one because of course, the monitor doesn’t really have a 5.1 output =)

#!/bin/bash

sink_identifier="hdmi"
sink_idx=$(pacmd list-sinks | grep -e 'index:' -e 'name:' | grep "${sink_identifier}" -B1 | head -1 | sed 's: ::g' | cut -d':' -f2)
logger "sway: $(basename $0): sink_idx: $sink_idx"
/etc/sway/move_sinks.sh "$sink_idx"
logger "sway: $(basename $0): called move_sinks with $sink_idx"

Wrap up

Setting everything up was much easier than expected.
Sway is a very minimal window manager, and my requirements are too.

I can still easily use the KDE apps I’m used to if I need, but my daily workflow usually boils down Firefox, Konsole and mpv. Steam games work too (sometimes thanks to XWayland), as they already did with KWin/Wayland and KWin/Xorg.

The desktop feels much more responsive. Window redraws are super-snappy, and so is the switching between workspaces, etc. Honestly, the part I miss less about KWin is the perceived lag, even when all the effects are disabled.

Being able to never leave the keyboard to switch, move and resize windows is an added bonus I didn’t know I’d appreciate this much.

About the author

Fabio Scaccabarozzi

Ever since 2002, I experimented with Linux. After some time spent on Slackware and Mandriva, I found Gentoo, I've been using it since then and I acquired a lot of knowledge on the internals of the distribution. I have created and mantained a custom liveCD targeted to power users and systems engineers, with a lot of tools usually not included in standard distributions and exotic filesystems. I'm currently working as a senior system administrator in Zurich, Switzerland.

1 Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.