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.