Fun with niri window manager

14 min read Original article ↗

As I wrote in my previous post, the other main hobby I practised during my recent holiday was tinkering more with my Linux box. This post will be a bit piecemeal, but I wanted to write about a few changes I have made recently.

A screenshot of a minimal Niri desktop with a Ghostty terminal showing three panes with in a Zellij layout. The colourscheme is warm dark brown backgrounds with warm-toned highlight colours.

A zellij project showing lazygit in the left pane, and taskwarrior-tui and a shell in the right pane.

Niri window manager

When I last wrote in detail about my setup, I was using Hyprland as my window manager. I liked it a lot, but had a few minor teething and stability issues with it. I then read (somewhere, I wish I could remember where) about Niri: a ‘scrollable-tiling’ window manager. I had previously played with PaperWM on Gnome (a Gnome extension with much the same design ethos), and found it surprisingly comfortable to use. However, like many extensions, it was limited by having to fit in with the desktop environment’s existing key bindings and so on.

The idea behind scrollable-tiling window managers is that each workspace offers a horizontally-scrolling space for windows. Opening or closing a window will never change the size of existing windows. Instead, windows are added to the viewable space on your monitor, and opening another window will scroll this horizontal view, making existing windows scroll off screen. It sounds odd, I know, but it is extremely practical once you get used to it.

Ordinary tiling window managers are great for being able to see the content of all your windows on a given workspace at once. It works well until you have too many windows on screen, at which point, some of them will end up being too small, no matter what arrangement you have. The way I dealt with that was generally to have no more than two or three windows on a workspace, and shift others to other workspaces, and so end up with six to eight workspaces in use.

Niri lets you set pre-set sizes for windows (proportional to the screen size) that you can toggle between them, and you can also go to full screen (preserving the status bar at the top) or covering the entire window (i.e. what is known as ‘fullscreen’ on other operating systems). Naturally, you can also easily focus different windows on screen and move their order around, as well as sending them to another workspace if you want. This has enabled me to build a very nice workflow, in which I put related windows together on one workspace. If it’s useful for me to see them simultaneously, I will make each window take up half the screen (my default). If there are two additional windows I want as a reference, and they would be legible at half the screen height, I can ‘consume’ one window into the column of the other so that I have half the screen showing my editor at full height taking up half of the screen, then the two other windows above each other on the right. If I need to check on other windows on the workspace, it’s a quick couple of Super+left/right keypresses to focus them and come back. If I need more room for my editor, I press Super+f to make it occupy the width and height of the screen, but if I toggle that again, my reference windows will again be visible alongside the editor. Another way to do it would be to make the reference windows tabbed, and then they would take the space of one window, but you could use Super+up/down to scroll through the tabs. Again, it sounds complicated, but — for me — it feels like a much more natural and ergonomic way to work, and it lets you change things quickly as you are working.

A screenshot of the niri desktop in 'overview mode' which shows zoomed out views of each of the workspaces and the windows within them.

Niri in overview mode

I now use fewer workspaces, with my current activity in Workspace 1, then other things I might need to use on other workspaces, but they can be brought to my current workspace if needed easily, then sent back again. I should also mention that Niri has a wonderful ‘overview’ feature, which shows you a zoomed-out view of all your workspaces and the windows on them. You can do anything in this overview that you can do normally, using the same key bindings, so you can move around windows or workspaces, and move the windows (or even switch the order of workspaces) in the overview, without getting lost or disoriented.

I’ve been really impressed with the quality and stability of Niri. I had very few issues porting over my settings to Niri from Hyprland, and everything works as before. Waybar has modules for Niri workspaces and windows, so that was easy to drop in, and otherwise I just had to translate my customisations from Hyprland. I actually need far fewer bindings on Niri as there are some clever ‘do what I mean’ functions which enable you to use a single binding that changes its action depending on the context. For example, I have bound the key (Super+down) to focus next window below to move-window-down-or-to-workspace-down. If there is a window below the current one on the same workspace, it will focus that, but if not, it will focus the window on the workspace below. You can do the same trick to move windows down or to the next workspace, and to ‘consume’ or ’expel’ windows to or from the current column.

Gruvbox colourscheme and Stylix

I had been using the catppuccin-mocha colourscheme, but started to find it visually tiring somehow. I have previously found brown-ish themes unappealing, but I found myself drawn to gruvbox-material which is a very warm brown-based scheme. This ‘material’ variant has some tweaks to the highlight colours to make them appear to have a similar contrast and prominence against the background. There are light and dark variants, and some variants of each with ‘softer’ or ‘harder’ contrast. Perhaps it’s a case of a change being as good as a rest, but I’m finding it quite restful and easy on the eyes.

Related to this, I have now made much better use of a nixOS package called Stylix. This lets you specify things like the colourscheme you want to use, which fonts, UI transparency for different classes of window, icon sets, cursor sets and so on in a single declaration. Once set up, you can then enable Stylix to automatically handle the themeing for a wide range of applications. I hadn’t set it up fully before, but now I have it automatically handling (almost!) everything, even specifying custom colours from the current Stylix theme for the few applications that are not set up for automatic themeing. Since Stylix can also handle specification of a light and dark theme, I am hoping to work on setting up a switch for that too in future.

Taskwarrior

One application I had not previously settled on for Linux was a todo application/task manager. In the interim I used Godspeed, as I had a licence through SetApp on macOS, and because I could use it cross-platform via the web app. I like Godspeed quite a lot, particularly its flexibility and the way you can drive it all from the keyboard. However, it doesn’t fit well with my usual practice of scheduling tasks for particular days.

I tried a few applications before settling on Taskwarrior. I had actually used it before, years ago, but now that I spend more time in the terminal, it felt more comfortable than it had previously. Taskwarrior seems simple on the surface, but is deceptively powerful. It is also largely agnostic about the system you might use to manage your tasks. On both Linux and macOS I use it mostly through taskwarrior-tui, which enables single-key commands to do things like modify, complete or add annotations to tasks. I use the context system tied to two tags: “+h” for home/personal tasks, and “+w” for work tasks. At any time (and through a convenient drop down menu in taskwarrior-tui), I can filter the tasks so that I show only work or home tasks (or both). When either the work or home context is set, adding a new task also sets the appropriate tag.

In addition to contexts, you can filter your tasks using queries involving any of the properties of a task. For example, you can limit to a particular project, tasks scheduled or due on a day or date range, with particular tags, and so on. This can be done ad hoc at the command line, but if you have queries you use frequently, you can create custom ‘reports’ which run the query and can be set to show the fields you are interested in with the entries sorted in the way you prefer. This makes it very easy to set up a powerful workflow that is designed for the way that you personally prefer to manage your tasks. The other big positive about Taskwarrior is that you can export your tasks in a number of user-friendly formats, so you are not locked in to it.

One of the interesting things about Taskwarrior is that it has a built in algorithm to calculate an urgency score for each task, and — by default — the tasks are sorted by urgency. The neat thing is that you have complete control over this and can set your own coefficients for each of the elements, in a way that makes sense for the way that you work. For example, you can change the way that certain tags or projects affect urgency, or the way that urgency accrues as the task ages. When I used Taskwarrior before, I didn’t realise that I could personalise this, and found the urgency aspect irritating. Now, I love it, and it decreases my fear that tasks will end up languishing unseen, as they gradually rise up the list as they age, even without other features that increase their urgency.

It does have weaknesses. You can set tasks to recur on a schedule, but I have found it a little confusing, and it can get into a mess if you recurrence on more than one machine and sync the tasks (of which more below). In practice, I haven’t found this a problem, as I had already moved most of my recurring tasks to the iOS app Due. This makes it much easier to manage recurring tasks, and it nags me efficiently and relentlessly if I don’t do them. Most of my recurring tasks were fairly mundane personal chores anyway, so probably don’t really belong in a task management system. I do have a couple of simple work-based recurring tasks in Taskwarrior and they work well enough for this purpose.

Taskwarrior stores the data in a local sqlite3 database, but taskchampion-sync-server enables you to sync between multiple local replicas in a very smart way. It handles potential conflicts superbly, so you don’t really even need to sync immediately on any given replica. I still don’t quite understand exactly how it works, but so far I have had no problems at all with it. I set up taskchampion-sync-server on the app deployment platform Fly.io using Docker, and that enables me to sync my tasks between all my machines, wherever they are. Fly.io charges in a metered way, and since the app is small and needs few resources, and only I am using it, it costs very little. In the months I have been using it, my bill has been less than $5 per month, and since Fly.io don’t collect on amounts less than $5, it has been free to me. I did try to set it up on my NAS, but for some reason I kept getting errors. The project has a Docker compose example, but I needed to deviate from that to get Tailscale to handle serving the service, and I was obviously missing something. I will try again, but for now, it is fine on Fly.io. The local replicas encrypt the data locally before syncing it, and I have set it so that only clients using a secret UUID can sync with the server, so it is secure enough.

The missing piece was iOS. There are a couple of great web front ends for Taskwarrior that can sync with TaskChampion sync server, but I like taskwarrior-web the most. I have set that up on my NAS, syncing with my task server, and so I can enter new tasks or check things off from my phone. I’m actually hoping to play around with the styling on small screens to make the layout a bit more practical, so hopefully I will eventually be able to put together a pull request for the project.

Other smaller changes and new finds

You may remember that I had tried all the nixos neovim frameworks and had settled on nixCats. I still think it is a very clever setup, but it is a bit too complicated for my needs. I kept coming back to my configuration to adjust something or add a package and being confused about how to do that. My problem no doubt, but I eventually reinstated by old nvf config and am using that quite happily.

I had been using tmux as a terminal multiplexer for particular projects, but switched back to Zellij, as you can see in the first screenshot in this article. Zellij recently introduced a new option for bindings in which you issue a prefix command with Ctrl+g to unlock sets of further commands, avoiding binding conflicts with the apps you are using in the terminal. This is the way tmux works, and was why I originally returned to tmux. However, Zellij is much easier to configure, has nice binding hints for when you inevitably forget them, and lets you set up fully configured layouts for particular projects. It also has some really nice features such as creating panes which re-run commands (such as a command to run tests) when you hit enter, or to watch files for changes. These would not be impossible in to set up in other ways, but they are a very sleekly integrated. I don’t use a zellij session for all my terminal work, but I do like it for things like working on my nixos configuration, writing articles for my blog or other development projects where I want to set up a persistent workspace which I can jump in and out of, and pick up where I left off.

And — finally — a recent find. rmpc is a terminal music player for mpd, which is what I use to play music I own locally. It is very pretty and usable, and quite easy to configure the way you want it. I now use this instead of ncmpcpp.

Rust

Looking back at this list, it’s interesting to me how many of these apps I enjoy are written in the Rust programming language. Just from the ones mentioned above, niri, taskwarrior/taskchampion-sync-server, taskwarrior-tui, zellij and rmpc are all written in Rust. Rust seems to have ballooned in popularity in the last few years, and I have found that apps written in Rust are consistently fast, solid and reliable. Those qualities led me to be curious about the language, so I have started to learn it. It’s a language with many features derived from C, and so I wasn’t sure that I would get on with it. I did try to learn C many years ago, and found it difficult and — for me at least — not an enjoyable experience coding experience. Since then, most of the languages I dabble in are interpreted rather than compiled, and hold the coder’s hand more often with the difficult bits.

To my surprise, I found Rust delightful. Perhaps it is the accumulation of experience with other languages which helped me this time, but I found it fairly easy to get in to. There are aspects which are particular to Rust which are more of a challenge to grasp, but overall, it has been fun. The ecosystem around Rust and the official documentation plays a big part in that experience. The official Rust book is very well written, and I found that it hit just the right level for me. I skipped some chapters and came back to them later once I had a more solid grounding in the basics, but it was a great introduction. I have since worked my way through a couple of other books, and while I am still a beginner, I’m at the level where I could tweak bits of code in existing projects to try out ideas, or write simple apps for myself. I know that Rust is a bit divisive, but like using it, and love using apps written in Rust that other — much more expert — coders have produced.