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.

Minimalist and Functional Desktop Environment without 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 the gpg-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.

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.