LGUG2Z 209cd82892 fix(wm): prevent hidden_hwnds deadlock
I used a parking_lot to detect what I suspected to be the deadlock
resulting in issue #13.

I was pleasantly surprised by the alternative to std::sync::Mutex
provided by parking_lot, especially not having to unlock it to use it,
and of course the excellent and intuitive (even if experimental)
deadlock detector.

I have decided to use parking_lot::Mutex as an almost-drop-in
replacement for std::sync::Mutex, as I expect that this isn't the last
time komorebi will have a deadlocking issue, and I have put the deadlock
detection code which runs in a separate thread behind a
"deadlock_detection" feature.

The actual deadlock itself was solved by scoping the first lock in the
handler for WindowManagerEvent::Hide and then executing any required
operations (some of which, like window.maximize(), may require another
lock on HIDDEN_HWNDS) in a separate scope once the previous lock has
been dropped.

In the future I should look at integrating globals like HIDDEN_HWNDS
into WindowManager in a way that won't lead to double-mutable-borrow
issues.

fix #13
2021-08-19 06:31:02 -07:00
2021-07-29 16:23:42 -07:00
2021-07-29 16:23:42 -07:00

komorebi

Tiling Window Management for Windows.

screenshot

About

komorebi is a tiling window manager that works as an extension to Microsoft's Desktop Window Manager in Windows 10 and above.

komorebi allows you to control application windows, virtual workspaces and display monitors with a CLI which can be used with third-party software such as AutoHotKey to set user-defined keyboard shortcuts.

Description

komorebi only responds to WinEvents and the messages it receives on a dedicated socket.

komorebic is a CLI that writes messages on komorebi's socket.

komorebi doesn't handle any keyboard or mouse inputs; a third party program (e.g. AutoHotKey) is needed in order to translate keyboard and mouse events to komorebic commands.

This architecture, popularised by bspwm on Linux and yabai on macOS, is outlined as follows:

     PROCESS                SOCKET
ahk  -------->  komorebic  <------>  komorebi

Design

komorebi is the successor to yatta and as such aims to build on the learnings from that project.

While yatta was primary an attempt to learn how to work with and call Windows APIs from Rust, while secondarily implementing a minimal viable tiling window manager for my own needs (largely single monitor, single workspace), komorebi has been redesigned from the ground-up to support more complex features that have become standard in tiling window managers on other platforms.

komorebi holds a list of physical monitors.

A monitor is just a rectangle of the available work area which contains one or more virtual workspaces.

A workspace holds a list of containers.

A container is just a rectangle where one or more application windows can be displayed.

This means that:

  • Every monitor has its own collection of virtual workspaces
  • Workspaces only know about containers and their dimensions, not about individual application windows
  • Every application window must belong to a container, even if that container only contains one application window
  • Many application windows can be stacked and cycled through in the same container within a workspace

Getting Started

GitHub Releases

Prebuilt binaries are available on the releases page in a zip archive. Once downloaded, you will need to move the komorebi.exe and komorebic.exe binaries to a directory in your Path ( you can see these directories by running $Env:Path.split(";") at a PowerShell prompt).

Alternatively, you may add a new directory to your Path using setx or the Environment Variables pop up in System Properties Advanced (which can be launched with SystemPropertiesAdvanced.exe at a PowerShell prompt), and then move the binaries to that directory.

Scoop

If you use the Scoop command line installer, you can run the following commands to install the binaries from the latest GitHub Release:

scoop bucket add komorebi https://github.com/LGUG2Z/komorebi-bucket
scoop install komorebi

If you install komorebi using Scoop, the binaries will automatically be added to your Path and a command will be shown for you to run in order to get started using the sample configuration file.

Building from Source

If you prefer to compile komorebi from source, you will need a working Rust development environment on Windows 10. The x86_64-pc-windows-msvc toolchain is required, so make sure you have also installed the Build Tools for Visual Studio 2019.

You can then clone this repo and compile the source code to install the binaries for komorebi and komorebic:

cargo install --path komorebi --locked
cargo install --path komorebic --locked

Running

Once you have either the prebuilt binaries in your Path, or have compiled the binaries from source (these will already be in your Path if you installed Rust with rustup, which you absolutely should), you can run komorebic start at a Powershell prompt, and you will see the following output:

Start-Process komorebi -WindowStyle hidden

This means that komorebi is now running in the background, tiling all your windows, and listening for commands sent to it by komorebic. You can similarly stop the process by running komorebic stop.

Configuring

Once komorebi is running, you can execute the komorebi.sample.ahk script to set up the default keybindings via AHK (the file includes comments to help you start building your own configuration).

If you have AutoHotKey installed and a komorebi.ahk file in your home directory (run $Env:UserProfile at a PowerShell prompt to find your home directory), komorebi will automatically try to load it when starting.

There is also tentative support for loading a AutoHotKey v2, if the file is named komorebi.ahk2 and the AutoHotKey64.exe executable for AutoHotKey v2 is in your Path. If both komorebi.ahk and komorebi.ahk2 files exist in your home directory, only komorebi.ahk will be loaded. An example of an AutoHotKey v2 configuration file for komorebi can be found here.

Common First-Time Troubleshooting

If you are experiencing behaviour where closing a window leaves a blank tile, but minimizing the same window does not , you have probably enabled a 'close/minimize to tray' option for that application. You can tell komorebi to handle this application appropriately by identifying it via the executable name or the window class:

komorebic.exe identify-tray-application exe Discord.exe
komorebic.exe identify-tray-application exe Telegram.exe

Configuration with komorebic

As previously mentioned, this project does not handle anything related to keybindings and shortcuts directly. I personally use AutoHotKey to manage my window management shortcuts, and have provided a sample komorebi.ahk AHK script that you can use as a starting point for your own.

You can run komorebic.exe to get a full list of the commands that you can use to customise komorebi and create keybindings with. You can run komorebic.exe <COMMAND> --help to get a full explanation of the arguments required for each command.

start                        Start komorebi.exe as a background process
stop                         Stop the komorebi.exe process and restore all hidden windows
state                        Show a JSON representation of the current window manager state
log                          Tail komorebi.exe's process logs (cancel with Ctrl-C)
focus                        Change focus to the window in the specified direction
move                         Move the focused window in the specified direction
stack                        Stack the focused window in the specified direction
resize                       Resize the focused window in the specified direction
unstack                      Unstack the focused window
cycle-stack                  Cycle the focused stack in the specified cycle direction
move-to-monitor              Move the focused window to the specified monitor
move-to-workspace            Move the focused window to the specified workspace
focus-monitor                Focus the specified monitor
focus-workspace              Focus the specified workspace on the focused monitor
new-workspace                Create and append a new workspace on the focused monitor
adjust-container-padding     Adjust container padding on the focused workspace
adjust-workspace-padding     Adjust workspace padding on the focused workspace
change-layout                Set the layout on the focused workspace
flip-layout                  Flip the layout on the focused workspace (BSP only)
promote                      Promote the focused window to the top of the tree
retile                       Force the retiling of all managed windows
ensure-workspaces            Create at least this many workspaces for the specified monitor
container-padding            Set the container padding for the specified workspace
workspace-padding            Set the workspace padding for the specified workspace
workspace-layout             Set the layout for the specified workspace
workspace-tiling             Enable or disable window tiling for the specified workspace
workspace-name               Set the workspace name for the specified workspace
toggle-pause                 Toggle the window manager on and off across all monitors
toggle-tiling                Toggle window tiling on the focused workspace
toggle-float                 Toggle floating mode for the focused window
toggle-monocle               Toggle monocle mode for the focused container
toggle-maximize              Toggle native maximization for the focused window
restore-windows              Restore all hidden windows (debugging command)
reload-configuration         Reload ~/komorebi.ahk (if it exists)
watch-configuration          Toggle the automatic reloading of ~/komorebi.ahk (if it exists)
float-rule                   Add a rule to always float the specified application
identify-tray-application    Identify an application that closes to the system tray
focus-follows-mouse          Enable or disable focus follows mouse for the operating system
help                         Print this message or the help of the given subcommand(s)

Features

  • Multi-monitor
  • Virtual workspaces
  • Window stacks
  • Cycle through stacked windows
  • Change focused window by direction
  • Move focused window container in direction
  • Move focused window container to monitor
  • Move focused window container to workspace
  • Mouse follows focused container
  • Resize window container in direction
  • Resize child window containers by split ratio
  • Mouse drag to swap window container position
  • Mouse drag to resize window container
  • Configurable workspace and container gaps
  • BSP tree layout
  • Flip BSP tree layout horizontally or vertically
  • Equal-width, max-height column layout
  • Floating rules based on exe name
  • Floating rules based on window title
  • Floating rules based on window class
  • Identify 'close/minimize to tray' applications
  • Toggle floating windows
  • Toggle monocle window
  • Toggle native maximization
  • Toggle focus follows mouse
  • Toggle automatic tiling
  • Pause all window management
  • Load configuration on startup
  • Manually reload configuration
  • Watch configuration for changes
  • View window manager state

Development

If you would like to contribute code to this repository, there are a few requests that I have to ensure a foundation of code quality, consistency and commit hygiene:

  • Flatten all use statements
  • Run cargo +nightly clippy and ensure that all lints and suggestions have been addressed before committing
  • Run cargo +nightly fmt --all to ensure consistent formatting before committing
  • Use git cz with the Commitizen CLI to prepare commit messages
  • Provide at least one short sentence or paragraph in your commit message body to describe your thought process for the changes being committed

If you use IntelliJ, you should enable the following settings to ensure that code generated by macros is recognised by the IDE for completions and navigation:

  • Set Expand declarative macros to Use new engine here
  • Enable the following experimental features:
    • org.rust.cargo.evaluate.build.scripts
    • org.rust.macros.proc

Logs and Debugging

Logs from komorebi will be appended to ~/komorebi.log; this file is never rotated or overwritten, so it will keep growing until it is deleted by the user.

Whenever running the komorebic stop command or sending a Ctrl-C signal to komorebi directly, the komorebi process ensures that all hidden windows are restored before termination.

If however, you ever end up with windows that are hidden and cannot be restored, a list of window handles known to komorebi are stored and continuously updated in ~/komorebi.hwnd.json.

Restoring Windows

Running komorebic restore-windows will read the list of window handles and forcibly restore them, regardless of whether the main komorebi process is running.

Panics and Deadlocks

If komorebi ever stops responding, it is most likely either due to either a panic or a deadlock. In the case of a panic, this will be reported in the log. In the case of a deadlock, there will not be any errors in the log, but the process and the log will appear frozen.

If you believe you have encountered a deadlock, you can compile komorebi with --feature deadlock_detection and try reproducing the deadlock again. This will check for deadlocks every 5 seconds in the background, and if a deadlock is found, information about it will appear in the log which can be shared when opening an issu which can be shared when opening an issue.

Window Manager State and Integrations

The current state of the window manager can be queried using the komorebic state command, which returns a JSON representation of the WindowManager struct.

This may also be polled to build further integrations and widgets on top of (if you ever wanted to build something like Stackline for Windows, you could do it by polling this command).

{
  "monitors": {
    "elements": [
      {
        "id": 65537,
        "monitor_size": {
          "left": 0,
          "top": 0,
          "right": 3840,
          "bottom": 2160
        },
        "work_area_size": {
          "left": 0,
          "top": 40,
          "right": 3840,
          "bottom": 2120
        },
        "workspaces": {
          "elements": [
            {
              "name": "bsp",
              "containers": {
                "elements": [
                  {
                    "windows": {
                      "elements": [
                        {
                          "hwnd": 2623596,
                          "title": "komorebi  README.md",
                          "exe": "idea64.exe",
                          "class": "SunAwtFrame",
                          "rect": {
                            "left": 8,
                            "top": 60,
                            "right": 1914,
                            "bottom": 2092
                          }
                        }
                      ],
                      "focused": 0
                    }
                  },
                  {
                    "windows": {
                      "elements": [
                        {
                          "hwnd": 198266,
                          "title": "LGUG2Z/komorebi: A(nother) tiling window manager for Windows 10 based on binary space partitioning - Mozilla Firefox",
                          "exe": "firefox.exe",
                          "class": "MozillaWindowClass",
                          "rect": {
                            "left": 1918,
                            "top": 60,
                            "right": 1914,
                            "bottom": 1042
                          }
                        }
                      ],
                      "focused": 0
                    }
                  },
                  {
                    "windows": {
                      "elements": [
                        {
                          "hwnd": 1247352,
                          "title": "Windows PowerShell",
                          "exe": "WindowsTerminal.exe",
                          "class": "CASCADIA_HOSTING_WINDOW_CLASS",
                          "rect": {
                            "left": 1918,
                            "top": 1110,
                            "right": 959,
                            "bottom": 1042
                          }
                        }
                      ],
                      "focused": 0
                    }
                  },
                  {
                    "windows": {
                      "elements": [
                        {
                          "hwnd": 395464,
                          "title": "Signal",
                          "exe": "Signal.exe",
                          "class": "Chrome_WidgetWin_1",
                          "rect": {
                            "left": 2873,
                            "top": 1110,
                            "right": 959,
                            "bottom": 1042
                          }
                        }
                      ],
                      "focused": 0
                    }
                  }
                ],
                "focused": 2
              },
              "monocle_container": null,
              "floating_windows": [],
              "layout": "BSP",
              "layout_flip": null,
              "workspace_padding": 10,
              "container_padding": 10
            }
          ],
          "focused": 0
        }
      }
    ],
    "focused": 0
  },
  "is_paused": false
}
Description
No description provided
Readme 14 MiB
Latest
2025-12-10 02:06:14 +01:00
Languages
Rust 97.3%
Python 1%
AutoHotkey 0.8%
Nix 0.7%
Just 0.2%