Finding the occasional joy in programming again
published on 2025-07-01
A couple months ago I ended up writing something in C++ for the very first time and even did a proper tagged release because I thought other people might find it useful too.
Ever since then I occasionally thought about writing about it, but never actually got around to it. In the meantime I ended up doing other stuff, so this is more of a collection of some things I worked on in the past ~4 months instead of a focused post.
Being fed up with GNOME
Every since I fully switched to Linux in August of 2023 I’ve been using GNOME. In the past I’ve mainly used Xfce (and for a bit i3) on various laptops I used for uni. At work I had a virtual machine that’s running openSUSE1 with Xfce. The one thing all of these machines had in common is that they either had not-that-great performance or not-that-great battery life (or both), so using something lightweight like Xfce felt like the right choice.
So when I decided to switch to Linux on my main desktop PC I didn’t have to think about any of these restrictions for the first time. On top of that I also wanted to try out Wayland – which was enough of a reason for me to give GNOME a shot.
The things I like about GNOME are
- That it’s a complete package, I don’t have to manage and configure different components myself.
- That it looks good enough out of the box. (I could never warm up to how KDE looks and themes don’t make it any better.)
- It can be really easily configured to look even better with plugins like Dash to Dock and Blur my Shell.
If there’s one thing you should take away from this is that I really like the look of a semi-transparent panel with a blur effect. Even on Windows I installed similar things to change the appearance of the taskbar.
So I was more or less happily using GNOME for 1.5 years. It had its occasional issues, but the way everything looked was always enough of a reason for me to look past them.
Then it ended up happening more and more frequently that GNOME would just crash. Either directly on login or very closely after when I opened a game for example. The system journal always pointed to an issue in some extension but it was hard to pinpoint what the exactly was the cause.
And exactly that is my primary problem with GNOME: The extensions. The API is unstable which means with every new version you first have to wait for the extensions to be compatible again in order to enjoy all the things you like about customizing GNOME (the blur!!). And on top of that it’s all a pile of JavaScript which doesn’t make it very pleasant to write an extension.
I know because I even wrote my own2 GNOME extension in those 1.5 years and specifically chose to use the TypeScript type definitions to make the whole experience less painful, and while it did make it better it was not great (and at least back then the types weren’t fully up to date with the latest GNOME version).
Putting Fire in my Way (it’s a Wayfire pun)
So in March I started trying out other floating window managers with Wayland support. I already ruled out KDE so that didn’t leave that many options.
At first I tried labwc because it felt closer to the things I like about Xfce, but it didn’t offer that many customization options and most importantly didn’t support any blur effects.
So after giving up on the idea for a short bit and going back to GNOME I gave Wayfire a try. I had already tried it in the past but didn’t feel like putting all the effort into setting up all the extra services I’d need (for having a nice panel and notifications and …). But I decided to give it another try because… it has blur effects.
Wayfire is funny because when you look at videos of it it feels like it’s too much. Too playful. There’s the cube plugin, there’s wobbly windows, there’s fire effects. My impression up until then was “this is the compositor for if you want complete animation overload in your desktop”. But I wanted to give it a try (blur effects!!) and thus configured the minimal things I’d want to have in order to try it out for a few weeks:
- A Waybar configuration that very closely mimics the GNOME panel (with blur effects!)
- A program launcher – finally getting to use Rofi, or rather its Wayland fork, again (I really missed this from Xfce)
- The necessary keybinds for things I commonly in order to feel right at home
And it was good enough! I could do almost everything the way I was used, it looked good, and I didn’t have any random crashes anymore.
But there was one thing I didn’t like about Wayfire and that was the lack of a proper window switcher. Or rather, one that looked good.
I tried some of the integrated options and plugins other people wrote, but I didn’t like any of them. So I ended up doing two separate things that will eventually (I feel like this blog post is getting too long already) lead me to my most recent project:
- Write a Wayfire plugin
- Write a Rofi script
Creating a Pretty Plugin (this is a C++ pun)
Another thing I really missed from GNOME was the Activity Overview. I might like it even more than the blur effects and I dearly miss it when I use Windows for work. To me it is the perfect way to switch between windows when there’s more than a couple open: You press the Super key, all windows get arranged so they are visible at the same time and roughly in the spot they were before, you click on a window to focus it and the Activity Overview closes. It’s such a pleasant user experience.
So I wondered: How hard could it be to write my own plugin for Wayfire that does exactly that?
I knew it must be possible because Wayfire ships with a Switcher plugin which already uses the concepts I would need for my own plugin:
- It moves and scales windows from their current position to a different one (although it only displays up to 3).
- It allows switching between the windows and changing the focus to the newly selected one.
- It can be opened and closed with a single hotkey.
The whole plugin is around 900 lines of C++ code which seemed manageable enough for someone who’s never touched C++ before in their life, but does have a fair amount of experience in other C style languages. I started playing around a bit with the plugin, changing small things here and there just to get a feeling for how Wayfire plugins work.
And the rest of the story isn’t even that exciting. I studied the logic of the Activity Overview in GNOME, I referenced the Switcher plugin, other Wayfire plugins and even looked into the core Wayfire source code to get a better idea of how things work.
And after less than a month I ended up with what I titled QWF Overview! I had so, so much fun working on this and learning more about Wayfire and C++. After not even a week of working on it (around April 1) the minimal version – which only displayed windows in a single row – worked well enough that I was using it in my main Wayfire session.
On April 17 I removed the GNOME session from my NixOS config because everything felt so much nicer now.
On April 21 I decided to release the 1.0 version of QWF Overview because it worked so well.
And you know what? Now, over two months later it still works really well and I didn’t have a single issue with it.
This might honestly be the best thing I’ve ever programmed (ignoring that it isn’t an original idea) because I use it every single day and it makes my experience so much nicer.
Getting Rusty
Lets return to before I even decided to work on QWF Overview.
Another thing I didn’t like about GNOME was that it doesn’t remember window positions. There is an extension that enables this, but I only really cared about a few windows that should have fixed positions while some should never remember their position, and the extension only allowed you to configure a blacklist but not a whitelist.
The nice thing about Wayfire is that it enables you to configure
window rules. For example I always want to have Firefox open on the
right side of my screen with a specific size, so I put the following
config in my wayfire.ini
:
firefox_1 = on created if (app_id contains "firefox" & !(title contains "Picture-in-Picture")) then set geometry 2240 30 1200 1410
popout_player_1 = on created if (app_id contains "firefox" & title contains "Picture-in-Picture") then set always_on_top
popout_player_2 = on created if (app_id contains "firefox" & title contains "Picture-in-Picture") then move 0 30
This also configures another rule so the pop out player opens in the
top left corner and is always on top. The issue with the special
language for the windows rules though is that it doesn’t give you full
control over the windows. It only has commands for a subset of the
features that Wayfire supports and even has issues like not supporting
an app_id
that contains a .
(the lexer for the
language tries to parse everything with a .
as a float
value, even if it is inside quotes).
I actually ended up looking into the Wayfire source code for the very
first time because I wanted to fix the .
issue (and did for
myself), but also found out this way of configuring window rules wasn’t
actively worked on anymore and the maintainer preferred if one used the
IPC (inter process communication) API provided by Wayfire for this instead.
Which finally leads us to the second thing I mentioned two sections ago: the Rofi script!
I still wanted a regular window switcher that would work with Alt+Tab and stumbled upon the idea of using Rofi for that. The Wayfire IPC would allow me to query the list of all open windows and change the focus of a window. It seemed like a pretty simple thing to do and since Rofi already looked good (because of the theme I had made for it) it should at least be a (visual) improvement over the other window switchers.
The official bindings for the Wayfire IPC are written in Python, and while I never enjoyed writing Python it looked like the fastest way to get something working.
I would link the Python code I initially wrote for this, but looks like I never actually committed it because it had one issue: Python is slow.
If you only want to position windows via the IPC it doesn’t really matter since things taking a second more or less will not be as noticeable. But if you want to open a window switcher with Alt+Tab it should be instant.
So I came to the only logical conclusion: rewrite it in Rust.
I had some rough experience with Rust from trying to write a small game in it two years ago and had been looking for a reason to get back to it again, so this came at just the right time. Luckily someone already wrote IPC bindings for Rust so I didn’t have to figure out everything by myself and could focus solely on porting the few lines of Python code.
I’m gonna embed the whole script at the end of the post in case anyone finds it interesting.
Having moved one Wayfire thing to Rust I thought “why not another one?” and ended up rewriting the window rules in Rust too. The added control allowed me to do nicer things like only moving the first opened Firefox window to a specific spot while subsequent windows would open at the default position (in the middle of the screen).
And then I ended up writing another thing in Rust: A small program that slowly fades the screen to black so you can chain it before a command that turns your screen off after a specific amount of time in order to get a “nicer” transition to a fully black screen. I initially wrote it using GTK4 Layer Shell (which you can still find under the 0.1.0 tag) before deciding GTK is way too heavy of a dependency for such a small thing that doesn’t even have UI and rewrote the whole thing using Smithay’s Client Toolkit while directly rendering the increasingly black frames with WGPU. There’s some minor things I haven’t bothered to fix yet, but I have been happily using it for over two months now and even decided to publish it to crates.io.
Finding joy in programming?
After switching to Wayfire, having lots of fun writing my own Wayfire plugin and smaller tools that make using my computer more enjoyable, I thought I might have found some joy in programming in my free time again – before completely losing all motivation shortly after. Maybe that’s just the trade-off for mostly writing things that I need myself, which doesn’t happen that often.
But then a month ago I started writing something new in Rust that was useful, felt bigger, and helped me learn lots of new concepts. And maybe I’ll even write about it in the next blog post.
Until then!
As promised, here is the bit of Rust I wrote to switch windows with Rofi using the Wayfire IPC. I tried to keep it as performant as possible, but if you notice any improvements please let me know!
Rofi gets started by having Alt+Tab execute the following command:
rofi -show wf-switcher \
-modes wf-switcher:/path/to/bin/rofi-wf-switcher \
-selected-row 1 \
-kb-accept-entry 'Return,!Alt+Tab,!Alt+Alt_L,!Alt_L' \
-kb-row-up 'Alt+ISO_Left_Tab' \
-kb-row-down 'Alt+Tab' \
-theme-str 'mainbox { children: [ message, listview ]; }'
And rofi-wf-switcher
gets compiled from the following
bit of code:
use std::cmp::Reverse;
use std::env;
use std::io::{BufWriter, Write, stdout};
use wayfire_rs::ipc::WayfireSocket;
#[pollster::main]
async fn main() {
let mut sock = WayfireSocket::connect().await.unwrap();
let args: Vec<String> = env::args().collect();
if args.len() == 2 {
let id = args[1].parse::<i64>().expect("did not get view id as int");
// ignore errors
let _ = sock.set_focus(id).await;
return;
}
let mut toplevels = match sock.list_views().await {
Ok(view) => view,
Err(e) => {
eprintln!("Failed to get views: {}", e);
return;
}
};
.retain(is_mapped_toplevel);
toplevels.sort_unstable_by_key(|v| Reverse(v.last_focus_timestamp));
toplevels
let lock = stdout().lock();
let mut writer = BufWriter::new(lock);
for v in toplevels {
writeln!(writer, "{}\0display\x1f{} ({})", v.id, v.app_id, v.title).unwrap();
}
.flush().unwrap();
writer}
Before I ended up switching every single machine to NixOS↩︎
It’s called Show Top Bar and a fork of Hide Top Bar which pretty much reverses its behavior because I wanted the panel to always be visible except for when a fullscreen window is open.↩︎