Minimalist and Functional Desktop Environment without Xorg
A brief write-up on how I’ve set up my Linux desktop environment to be minimal yet functional, to let me work and use the computer as efficiently as possible. All without using Xorg.
I’ve been back on the Linux desktop full-time for three years at this point, and just recently I moved from a SFFPC workstation to a Linux laptop, running hardened Gentoo – my distribution of choice. Unlike what entertainers try to make you believe, it has been and continues to be a terrific experience, in part due to the modern ecosystem that is Wayland.
I remember the times I had to fist-fight XFree86, and after that Xorg, only to get the graphical user interface running – let alone having something like hardware acceleration. Even with the most compatible hardware, there were always issues with the X11 configuration that required a couple of hours of debugging until it would become usable enough. And the moment I got it working and naively tried to connect an external display it all came crashing down again. If you ever wanted to read grown men crying, go ahead and type “xinerama multi-monitor” or “xrandr multi-monitor” into your favorite search engine.
Long story short, Wayland pretty much changed all that and more. With the right
hardware these days Wayland compositors are pretty much
plug and play. In this brief write-up, I will go through my battle-tested setup
and explain how I set up a minimal yet functional Linux desktop environment, all
while having -X
in my /etc/portage/make.conf
global USE
flags.
Basics
The basic system is pretty much what I’ve already documented in the StarBook setup. However, this desktop environment does not depend on Gentoo and could be configured on any other Linux distribution. Keep in mind, however, that most pre-built distributions these days still ship with X code and libraries that you might not need, bloating programs unnecessarily.
Compatible hardware
AMD and Intel GPUs are the best options for running a Wayland Linux desktop. However, even NVIDIA, which has been notoriously hostile towards open-source, has started to slowly move into this direction – especially with Valve steering the community in that direction with their Gamescope efforts.
Generally, however, you can try running Wayland on NVIDIA hardware, but YMMV.
Login manager
I don’t use a login manager, as I have no need for it. Instead, I log in on the
command line and use a sway-launch
alias to start the desktop
session.
Window manager (aka compositor)
My window manager (compositor) of choice is Sway. Sway is a tiling window manager and drop-in replacement for the well-known i3 window manager on X11. If you have ever used i3, dwm, xmonad, or any of the other tiling window managers on X11, you will feel home within Sway right away.
I don’t do a lot of ricing these days and keep my desktop fairly
basic. I change a few colors and fonts every once in a while, and I recently set
up swaycons
for the sake of having a better overview of all open
windows, but that’s about it. It’s not like Sway allows for much customization
either – for that Hyprland would probably be the better choice.
Layout
Most of the time I’m working in a two-column layout, with the left one being a
tabbed GUIs column (e.g. browsers, Zathura, Gimp,
Blender), and the right one being solely terminals – Alacritty in
my case. I can easily tab through the windows using the Meta key in combination
with the H
, J
, K
, and L
keys. To switch from a tabbed view directly to
another column I add the Ctrl
key.
Terminal
Alacritty is my GUI terminal of choice. One important configuration change that I made was to disable the mouse binding for the middle mouse button. Other than that it’s mainly visual customization and a handful of special key bindings.
My default shell is Zsh, I use OMZ and Starship, and I have a relatively
extensive .zshrc
. The Zsh configuration is designed to run on
different operating systems, with different tools. It consists of functions that
help me keep my dotfiles in sync across multiple machines.
Session credentials
I use pass
for session credentials. It’s not my main password store,
but it stores tokens like e.g. GitHub tokens, that are usually required by
command line tools. pass
uses the gpg-agent
for the password storage key,
which in turn uses pinentry-qt5
to request the key passphrase. Whenever I’m
launching a tool that requires a token, a password entry will pop up that
prompts me to unlock my key.
Note: I’m in the process of testing and possibly migrating to YubiKey USB keys. I am also watching KeePassXC’s progress and hence might eventually replace
pass
and thegpg-agent
configuration.
SSH
For most SSH connections I already use a YubiKey setup, with YubiKey-backed SSH
keys. Because oftentimes I don’t see the YubiKey blinking, asking me for touch
validation, I use yubikey-touch-detector
, which displays a
notification when touch input is required.
Because by default the YubiKey was sending random data when touching it while no touch was requested, I had to disable these events in Sway:
swaymsg input "4176:1031:Yubico_YubiKey_OTP+FIDO+CCID" events disabled
For SSH I use a wrapper function, that checks my ~/.ssh/config
and selects either mosh
or ssh
, based on the configuration for a specific
host. I have previously documented this function.
Menu/Status bar
Instead of swaybar
, the standard menu/status bar that comes with Sway, I use
Waybar, a more advanced bar implementation that comes with modules. While
there are some Wayland bar alternatives around today, they are either tedious
to configure or too rudimentary for my taste. Waybar
on the other hand offers a solid set of basic modules, that can be further
extended with custom modules. In general Waybar’s
wiki is a solid resource for configuration and customization.
My Waybar config is split by machine and I’m using a special
swaybar_command
, that launches a wrapper
script, which in turn loads the correct config. This allows me
to use my dotfiles across multiple computers, with different
configurations, without having to manually adjust the paths to configuration
files or perform other trickery.
keyd
& usbec
I like keyboards, very much in fact, and that’s why I truly dislike the StarBook’s keyboard, just as much as I dislike pretty much all modern laptop keyboards. Whenever I can, I use an external keyboard like the Corne V3. For those times for which I cannot afford to carry a dedicated board with me, or when I simply forget to bring it, I make sure that I can at least deal with the integrated keyboard.
Therefore, I use keyd, which allows me to remap individual keys on the integrated keyboard, so that I do not have to struggle as much with muscle memory, as I would otherwise.
My `/etc/keyd/default.conf’ looks as follows:
[ids]
*
-5241:060a
-5241:4b52
-4653:0001
[main]
capslock = leftcontrol
leftalt = leftmeta
rightalt = rightmeta
leftmeta = leftalt
rightmeta = rightalt
\ = backspace
backspace = \
home = `
` = escape
[end]
w = up
a = left
s = down
d = right
The [ids]
section might be a bit confusing. This is because I have to specify
a blacklist, instead of a white list, as I didn’t get keyd
to work with the
device identifier(s) that I found for the internal keyboard. Hence, instead of
mapping this configuration to solely the internal keyboard, I instead map it to
any keyboard (*
) apart from a handful of external ones that I’m regularly
using and that are configured with a similar HHKB-type layout already through
QMK.
One issue that I kept having when using external keyboards and placing them on
top of my integrated laptop keyboard was involuntary key presses. I tried to
deal with this on udev
level, but I didn’t figure out a way to disable the
internal keyboard. However, luckily Sway had the swaymsg input events
command
that allowed me to do so fairly easily. Due to how udev
works, however, it’s
relatively complex (and hacky) to trigger the swaymsg
command from within a
udev
rule. I ultimately ended up writing a nearly 150 lines long Go daemon,
that would take care of this: usbec, the USB Equipment Commander.
The daemon runs in the background and listens for hot plugged USB devices.
Whenever a device is plugged in, it checks whether it matches a specific ID, and
if so, disables the internal keyboard. As of right now, it’s a super basic
program that doesn’t allow for any configuration. In case people are interested
to using this tool, I could make it configurable, though. Update
2024-09-20: I made usbec
configurable, so you can now, too, use it for all
sorts of different USB trigger actions and spare yourself the PITA that is
udev. While solutions like these can work, they aren’t exactly
safe – just replace the content in /tmp/builtin_keyboard_devname
with code
that is able to exploit cat
or the udev call and make the file read-only –
they are hard to debug and in case you need to run scripts that take longer to
finish they simply won’t work. usbec
doesn’t need root privileges to run and
can execute programs as the user that runs it.
Power
I use poweralertd
to remind me to plug in the laptop before
it turns off. The daemon also shows useful information on battery-powered
peripherals.
Launcher
I use bemenu
as launcher, which is similar to
rofi
/wofi
. It is lightweight, and fast and I can use it
inside scripts (e.g. for presenting a selection of options to pick from). While
there are more Wayland-native alternatives these days, like fuzzel
,
anyrun
, and walker
, I haven’t had the need to switch
just yet.
VPN
I use a custom Waybar module to display VPN information and
launch a custom script that allows me to select a VPN to connect
to. The screw grew historically, hence it not only supports nmcli
, but also
wg
and openvpn
directly. It requires no configuration itself to display
existing VPN connections. When selecting a VPN to connect to, it uses another
script to show a notification with the current public IP
address. For that, however, the service used by the get-public-ip-info
script
requires an API token, that is stored in pass
.
Browser
I’m using Firefox, as well as Ungoogled
Chromium as my main browsers. In order to be
able to do so, I use a specific configuration, to make
/usr/local/bin/browser
my default browser:
xdg-settings set default-web-browser browser.desktop
This way, whenever I click on a URL, bemenu
opens and presents me
a list of possible browsers to open the URL in. Because the browser
script
uses cexec, my selection is cached for 10 seconds, allowing me to click
multiple URLs one after the other and have them open in the same browser. The
script even uses url-parser
to find out whether it’s an Onion
URL and, if so, automatically opens Tor Browsers without prompting for a browser
selection.
Audio
For my setup, I’m using Pipewire and Wireplumber. Gentoo comes with a script
that is called gentoo-pipewire-launcher
, that gets executed via Sway
configuration right at launch.
I have built audio-router
, a helper script that allows me to
easily switch sound output between different devices. I’m calling it mainly via
Sway shortcuts and it makes sure that the music won’t blow out my ears whenever
I switch from let’s say the external speakers to the analog port with
headphones.
As I’m using Pipewire, the script makes use of pactl
. Additionally, it runs
the notify-send
binary to show a notification with the newly activated sound
output.
Although not configurable, the script can be adjusted fairly easily, by finding
the Pipewire sink name of the device and adding it as an option to the case
at
the beginning of the script.
Other than that, I use pavucontrol-qt
, mainly because it’s more
lightweight and gives me fewer headaches in regard to its dependencies than the
GTK version.
Screenshots
I use three tools in combination to take screenshots of my desktop:
The first one, slurp
, allows selecting a specific region of the screen. I have
a shortcut that allows me to use a combination of slurp
and
grim
to take screenshots of parts of my screen. I have a separate
shortcut that only screenshots the currently active window. Last
but not least, I have a shortcut that takes a screenshot of the
whole screen.
The third tool, tesseract
, does optical character recognition. I use it in
combination with slurp
and grim
and it allows me to copy the text from a
specific region of the screen – for which I also have a
shortcut. It comes in handy when there is text on an image and I
don’t feel like opening Gimp to crop the image to only the text I need, to then
manually feed it into tesseract
. Instead, I can take a screenshot of that
portion and the parsed text will be sent directly into my clipboard so I can
C-v
/C-V
it.
Screen recordings
To record my screen or a portion of it, I use wf-recorder
in
combination with slurp
. To make things easier, I build a
script that I use within a custom Waybar
module. This allows me to start a screen recording
simply by clicking on the icon in Waybar, which then turns red, signaling that
it is currently recording. With another click on the same icon, I can end the
running recording. The files are conveniently stored inside my ~/downloads
folder.
Screen sharing, video conferencing & streaming
I use OBS Studio when I need to share my screen, video conference or stream. I also use it for screen recording when I record videos. OBS works perfectly fine on Wayland and supports hardware encoding (VAAPI).
For screen sharing to work on Wayland/Sway, the
gui-libs/xdg-desktop-portal-wlr
backend is required.
Contacts & calendar
I use addrb and caldr in combination with some scripts, that show notifications for calendar entries as well as people’s birthdays when I log in.
For everything else, I use the contacts and calendar apps on my phone.
Ergonomics
For ergonomics, I have the laptop connected to an external display via HDMI whenever possible. I have two lightweight and small 3D printed laptop stands that raise my laptop to an optimal height, so that its bottom screen border is usually in line with the external display’s bottom border.
As for the display, I use wlsunset
, which automatically adjusts
the image temperature depending on the time of the day. Unfortunately, it
requires reconfiguration when travelling.
I use an external mouse, even though I could navigate by solely using the
keyboard. My mouse has natural_scroll
enabled, due to having become victim to
it years ago, which now haunts me with its own twists and
turns.
Etc.
For more info, feel free to dig through the computer page, as well as the infrastructure page. If you happen to also have a particular interest in command-line tools, make sure to check the command-line page, as well as my projects. And everything else can probably be found somewhere deep within my dotfiles.
Enjoyed this? Support me via Monero, Bitcoin, Lightning, or Ethereum! More info.