This commit moves layout-related code into a new workspace crate
komorebi-layouts, with the intention of re-using it all in komorebi for
Mac instead of maintaining two separate implementations.
Added customizable split ratios for layouts via layout_options
configuration. Users can now specify column_ratios and row_ratios arrays
to control window sizing in various layouts.
Ratios are validated at config load time: values are clamped between 0.1
and 0.9 to prevent zero-sized windows, and arrays are automatically
truncated when their cumulative sum would reach or exceed 1.0. This
ensures there's always remaining space for additional windows.
Ratio support varies by layout:
- Columns and Rows layouts use the full arrays for each column/row width
or height
- VerticalStack, RightMainVerticalStack, and HorizontalStack use the
first ratio for the primary split and the remaining ratios for stack
windows
- BSP uses the first value from each array for horizontal and vertical
splits respectively
- Grid only supports column_ratios since row counts vary dynamically.
- UltrawideVerticalStack uses the first two column ratios for center and
left columns.
All ratio-related values are now defined as constants in
default_layout.rs: MAX_RATIOS (5), MIN_RATIO (0.1), MAX_RATIO (0.9),
DEFAULT_RATIO (0.5), and DEFAULT_SECONDARY_RATIO (0.25 for
UltrawideVerticalStack).
This commit attempts tofixfixes a use-after-free bug in the
border_manager that was causing crashes with exception code 0xc000041d
(FATAL_USER_CALLBACK_EXCEPTION) when borders were being destroyed during
config updates.
The root cause was a race condition between the main thread destroying a
border window and the border's window thread still processing queued
window messages (EVENT_OBJECT_LOCATIONCHANGE, WM_PAINT).
The crash occurred when these callbacks invoked render_target.EndDraw(),
which internally calls HwndPresenter::Present() to present the rendered
frame to the HWND. By this point, the HWND and its associated Direct2D
surfaces had been freed by WM_DESTROY, resulting in Direct2D attempting
to dereference freed memory (0xbaadf00dbaadf00d - debug heap poison
value).
The previous attempts at fixing this issue (bdef1448, dbde351e)
addressed symptoms but not the fundamental race condition. bdef1448
attempted to release resources on the main thread before destruction,
but this created a cross-thread race. dbde351e moved resource cleanup to
WM_DESTROY, but this still allowed EVENT_OBJECT_LOCATIONCHANGE/WM_PAINT
handlers to check `render_target.is_some()`, context-switch to
WM_DESTROY which clears it, then context-switch back and call EndDraw()
on a now-invalid reference.
This commit attempts to eliminate the race condition by introducing an
atomic destruction flag that serves as a memory barrier between the
destruction path and the rendering paths:
- Added `is_destroying: Arc<AtomicBool>` field to the Border struct
- In destroy(): Sets the flag with Release ordering, sleeps 10ms to
allow in-flight operations to complete, then proceeds with cleanup
- In EVENT_OBJECT_LOCATIONCHANGE and WM_PAINT: Checks the flag with
Acquire ordering both at handler entry and immediately before calling
BeginDraw/EndDraw, exiting early if destruction is in progress
The Acquire/Release memory ordering creates a synchronizes-with
relationship that ensures:
1. When the destruction flag is set, all subsequent handler checks will
see it (no stale cached values)
2. Handlers that pass the first check but race with destruction will be
caught by the second check before touching D2D resources
3. The 10ms sleep window allows any handler already past both checks to
complete its EndDraw() before resources are freed
This is a lock-free solution with zero overhead on the hot rendering
path (atomic loads are nearly free) and provides defense-in-depth with
multiple barriers against the use-after-free condition.
This commit ensures that we check if a window is already managed in any
workspaces even after checking known_hwnds, because windows moved as
part of the earlier ensure_workspace_rules call can slip through the
cracks.
When a border is destroyed, the main thread was forcefully releasing D2D
resources, while the border window thread might still be trying to use
them in its message loop.
Releasing the RenderTarget on one thread while another is calling
EndDraw on it leads to a use-after-free scenario or invalid state for
the COM object, resulting in the crash.
This commit applies a fix that moves the resource cleanup logic from the
main thread to the window thread. Now, resources are only released
during the WM_DESTROY message processing, which guarantees
synchronization with other window messages like WM_PAINT.
This commit fixes a use-after-free bug in the border_manager that was
causing crashes with exception code 0xc000041d when borders were being
destroyed during workspace/monitor changes.
The root cause was a race condition between the main thread destroying a
border window and the border's window thread still processing queued
window messages (WM_PAINT, EVENT_OBJECT_LOCATIONCHANGE). The crash
occurred when these callbacks invoked render_target.EndDraw(), which
internally calls HwndPresenter::Present() to present the rendered frame
to the HWND. By this point, the HWND and its associated DirectX surfaces
had already been freed, resulting in Direct2D attempting to dereference
freed memory (0xFEEEFEEEFEEEFEEE - Windows heap poison value).
The issue stemmed from two problems in destroy_border():
1. Direct2D resources (render_target and brushes) were not being
released before closing the window. These COM objects hold internal
pointers to the HWND and its DirectX swap chain/surfaces. When
close_window() was called, Windows began tearing down these resources
while the Direct2D objects still held dangling pointers to them.
2. GWLP_USERDATA was not being cleared before closing the window. This
meant that any messages already queued in the window's message queue
could still retrieve the border_pointer and attempt to render using
the now-invalid Direct2D resources.
This commit addresses both issues:
- In destroy_border() (mod.rs): Explicitly set render_target to None and
clear the brushes HashMap before calling destroy(). This ensures that
Direct2D COM objects are properly released while the HWND is still
valid, preventing EndDraw() from accessing freed HWND resources.
- In destroy() (border.rs): Clear GWLP_USERDATA before calling
close_window(). This ensures that any pending window messages will see
a null pointer and exit early from the callbacks (which already have
null pointer checks in place).
These changes create two layers of defense against the race condition:
the callbacks won't access the border_pointer (it's null), and even if
they somehow did, the render_target would be None so no rendering
operations would occur.
I think this is the root cause of a lot of crash tickets that people are
mistakenly attributing to specific applications which lack
reproducibility across different users/machines, i.e. #1626, #1624.
Apparently there is a quirk of GetWindowLongPtr when querying styles. If
the style bits are genuinely 0, the API returns 0 but does not clear the
last error.
If a previous API call set an error, GetWindowLongPtr might define
"failure" as "return 0 AND GetLastError != 0".
Hence, it is important to clear the last/previous error before calling
GetWindowLongPtrW to ensure that we don't misinterpret a valid 0 return
value as an error.
The WorkspaceTenantName is populated far more consistently than MdmUrl.
This commit switches to extracting that instead and passing it on to the
MDM splash screen so that users on non-corporate devices who may have
unintentionally enrolled themselves into BYOD MDM by logging into an
account a clicking "Yes" on some dark pattern pop-up have a clear
indication of why they are seeing the splash, and can take the
appropriate steps to remove the MDM profile from their system if
desired.
I think this got broken as part of the automatic Rust version syntax
upgrades which joined two if clauses together with && which should have
been kept separate.
Now, if the user gives the --bar flag to the start command, and a static
config file is not resolved, or if the static config file does not have
a bar_configurations stanza, it will fallthrough to the default
PowerShell snippet to try and start komorebi-bar without an explicit
--config flag.
This commit fixes the stupidest of stupid bugs. Column width
calculations on the Scrolling layout should take the number of windows
into account, especially when lower than the configured column count.
I was getting really tired of having to switch between display inputs to
different platform-specific machines to be able to make and test changes
on komorebi for Windows and komorebi for Mac.
With this commit, the `flake.nix` provides a Nix devShell and crane
build for users to make and validate changes with `cargo check`, `cargo
clippy` and `cargo build` with the Windows MSVC toolchain on Linux and
macOS.
Getting tired of making little changes in both this and the komorebi for
Mac repo - I think eventually either komorebi-themes will live in its
own repo or komorebi for Mac will be integrated here.
But for now, at least everything is defined in komorebi-themes and I
don't have to redefine any theme-related stuff in komorebi for Mac.
This commit ensures that the various default values that the different
Option<T> config properties can be unwrapped to are encoded by schemars
so that they can be picked up by docgen.
This commit changes the way how the default interface and network
activity is loaded by spawning a new thread on each request. This way
the main thread is not blocked by this process.
There has been instances where getting the default interface blocked the
egui ui thread, resulting in a frozen bar.
fix#1423#1499