Dynamic vs. Static Config for My Tiling Window Manager
For the last few years I have been writing and maintaining a tiling window manager for Windows that has steadily grown in usage and popularity.
My first exposure to tiling window managers was on macOS with kwm
(which was
succeeded by chunkwm
and later
yabai
). Naturally, this meant that
whenever I used Linux, I would reach for
bspwm
.
I am a big proponent of what I call the “bspwm architecture” for tiling window managers.
PROCESS SOCKET
sxhkd --------> bspc <------> bspwm
Here is that same diagram in the context of my tiling window manager, komorebi
:
PROCESS SOCKET
whkd/ahk --------> komorebic <------> komorebi
In the case of bspwm
and yabai
, shell scripts can be leveraged thanks to
this architecture to configure the tiling window manager. After all, what
better way to call CLI commands than a shell script?
This was initially the path that I also took with komorebi
. It was a little
bit different, perhaps a little clunkier due to the limitations and differences
of CMD and PowerShell vs. bash
, but it worked pretty well.
Below is the sample PowerShell script provided to new users to configure komorebi
:
if (!(Get-Process whkd -ErrorAction SilentlyContinue))
{
Start-Process whkd -WindowStyle hidden
}
. $PSScriptRoot\komorebi.generated.ps1
# Send the ALT key whenever changing focus to force focus changes
komorebic alt-focus-hack enable
# Default to cloaking windows when switching workspaces
komorebic window-hiding-behaviour cloak
# Set cross-monitor move behaviour to insert instead of swap
komorebic cross-monitor-move-behaviour insert
# Enable hot reloading of changes to this file
komorebic watch-configuration enable
# Create named workspaces I-V on monitor 0
komorebic ensure-named-workspaces 0 I II III IV V
# You can do the same thing for secondary monitors too
# komorebic ensure-named-workspaces 1 A B C D E F
# Assign layouts to workspaces, possible values: bsp, columns, rows, vertical-stack, horizontal-stack, ultrawide-vertical-stack
komorebic named-workspace-layout I bsp
# Set the gaps around the edge of the screen for a workspace
komorebic named-workspace-padding I 20
# Set the gaps between the containers for a workspace
komorebic named-workspace-container-padding I 20
# You can assign specific apps to named workspaces
# komorebic named-workspace-rule exe "Firefox.exe" III
# Configure the invisible border dimensions
komorebic invisible-borders 7 0 14 7
# Uncomment the next lines if you want a visual border around the active window
# komorebic active-window-border-colour 66 165 245 --window-kind single
# komorebic active-window-border-colour 256 165 66 --window-kind stack
# komorebic active-window-border-colour 255 51 153 --window-kind monocle
# komorebic active-window-border enable
komorebic complete-configuration
The first if
block, which starts whkd
if
it isn’t already running, should be conceptually familiar to users of bspwm
and sxhkd
.
The next line however, calls another PS script full of generated commands, will
probably be less familiar. Take a moment to have a look at the contents of
komorebi.generated.ps1
before continuing with this article.
One of the major challenges with writing a tiling window manager for Windows is the extreme lack of uniformity in Windows GUI application development. This situation has only been made worse in recent years with the proliferation of Electron applications that explicitly aim to eschew consistency with the guidelines of the operating system in favour of their own internal cross-platform consistency.
Quite early on, I made the decision to not hard-code tweaks for various applications into the codebase, but to expose interfaces for users to be able to configure their own application-specific tweaks.
I still believe that this is the right design decision, but what is gained by not having to cut a new release to add compatibility fixes for individual applications comes with its own set of trade-offs.
If these fixes were hard-coded into the codebase, there would be a natural
centralized location for all of them. Since this is not the case, last year I
created a centralized location for application-specific
fixes
which users can contribute to by updating a user-friendly YAML file. It is from
this repository that komorebi.generated.ps1
is generated.
If you took a look at the contents of komorebi.generated.ps1
, you’ll see that
there are hundreds of komorebic
configuration commands that will be called.
It’s possible to cut these down to just the things you might need, but to my
knowledge, there have not been any users who have done so.
To be able to provide as complete and stable an out-of-the-box experience, applying these fixes is a must. However, as the list has grown longer, this has had an negative impact on startup times, and as the list continues to grow, this will only get worse.
The v0.1.17
release of komorebi
introduces static configuration
loading. While part of the
motivation for implementing this feature was user demand (on Windows, for
whatever reason, people seem to be more adverse to the idea of configuration
through scripts in general), for me personally, the biggest motivation was to
reduce the growing startup times.
The sample static configuration file for new users now looks like this:
{
"app_specific_configuration_path": "$Env:USERPROFILE/applications.yaml",
"window_hiding_behaviour": "Cloak",
"cross_monitor_move_behaviour": "Insert",
"alt_focus_hack": true,
"default_workspace_padding": 20,
"default_container_padding": 20,
"active_window_border": false,
"active_window_border_colours": {
"single": { "r": 66, "g": 165, "b": 245 },
"stack": { "r": 256, "g": 165, "b": 66 },
"monocle": { "r": 255, "g": 51, "b": 153 }
},
"monitors": [
{
"workspaces": [
{ "name": "I", "layout": "BSP" },
{ "name": "II", "layout": "VerticalStack" },
{ "name": "III", "layout": "HorizontalStack" },
{ "name": "IV", "layout": "UltrawideVerticalStack" },
{ "name": "V", "layout": "Rows" }
]
}
]
}
In addition to being a more visual representation of the desired window manager state, being able to specify the location to the raw application-specific configuration YAML file means all those hundreds of fixes can be loaded with a single atomic command, which results in a massive reduction in startup time.
You can check out the quickstart video for v0.1.17+ on YouTube below, and even compare it to the previous quickstart video to see the startup performance improvements compared to the previous dynamic configuration approach.
Support for dynamic configuration is not going away any time soon as it is
important to maintain backwards compatibility for existing users. However, for
users that wish to migrate to a static configuration file, a convenient
komorebic
command, generate-static-config
, has been added to automatically
generate a static configuration file from a running instance of komorebi
configured using a dynamic configuration script.
If you have any questions you can reach out to me on Mastodon and Twitter.
If you’re interested in what I read to come up with solutions like this one, you can subscribe to my Software Development RSS feed.
If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.