Add MultiWM - #5190
Conversation
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
|
Congratulations on being the first to submit a mod with an id shorter than 8 characters. Let me think about it, maybe we can lift this limitation. |
I appreciate it. |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The architecture is genuinely good - the serialized STA actor, the "OS events are dirty signals, the model is authoritative" split, and the tool-mod packaging are all the right calls, and the settings block is fully in sync with the code. The findings below are mostly about defaults, one window-resolution heuristic, and scope. 1. The default hotkeys take over
Please default if (!RegisterHotKey(nullptr, id, modifiers | MOD_NOREPEAT, key)) {2.
The function's own comment says it exists to bridge "apps that replace one top-level HWND with another", but it starts with 3. Verify the tool process's DPI awareness before trusting any of the geometry.
// GetScaleFactorForMonitor also guards against hosts
// whose DPI-awareness context makes GetDpiForMonitor report 96.
The root-cause fix is one line at the top of SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);See mods/classic-min-max-animations.wh.cpp#L962 for the same pattern on a worker thread. Once that's in place the 4. Manual mode silently admits every window when a layout command creates a workspace.
5. Hotkey keys only parse as a single character, but the settings documentation advertises words.
if (_wcsicmp(str, L"Space") == 0) return VK_SPACE;
if (_wcsicmp(str, L"Tab") == 0) return VK_TAB;
// ... Enter, Esc, F1-F12, arrowsor reject multi-character input ( 6. The diagnostics subsystem is a large amount of surface for what it delivers. Roughly 1,700 of the mod's 10,359 lines are the diagnostics report: ~90 lifetime counters, a I'd cut it down hard: keep the state dump if you find it useful in the field, but emit it through 7. Catalog overlap with Tiling Helper. You state the original author approved the continuation, and I'll take that at face value - but Tiling Helper stays in the catalog after this merges, so users browsing windhawk.net will see two tiling mods with no indication of the relationship. Worth coordinating with the maintainer and u2x1 on a follow-up PR that adds a "superseded by MultiWM" note to Tiling Helper's README (or deprecates it), so this doesn't just fragment the catalog. Optional improvements
Minor polish - none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
Updated limit to 6. |
|
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The previous round's findings are all addressed correctly: the PID+class resolution fallback is gone, the WM thread sets PMv2 explicitly, Manual mode no longer mass-admits windows when a layout command creates a workspace, 1. Every window movement on the system costs a DWM round trip plus a display-topology enumeration.
The queued side repeats the same work: This one has a clean fix and you already have the invalidation point:
With the cache in place the remaining per-event cost is just the DWM query, which is reasonable. (The related note from last round still stands: gating 2. Tiling Helper still ships alongside MultiWM. Carried over from last round and still open. I take the author approval at face value - the concern is only that after this merges, windhawk.net lists two tiling mods with no indication of the relationship. Worth agreeing with the maintainer (and u2x1) on a follow-up that adds a "superseded by MultiWM" note to Tiling Helper's README, or deprecates it. 3. Default You've already responded to this and I won't re-argue it - recording it only so the human reviewer sees it's a deliberate choice rather than an oversight. The concrete part ( Optional improvements
Minor polish - none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
|
/ready-for-reviewer |
|
cool! |
|
Regarding:
It will take time until 1.7.3 is phased out. For the time being, given that making it work for a 32-bit process requires more effort, perhaps it's better to just have a tool mod targeting explorer.exe. Just changing the target process from |
That might work. Thanks. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The host-process switch does what it was meant to: 1. The dedicated process is now a real Windhawk selects mods by the target process's image, and the tool process's image is
There is precedent - simple-window-switcher ships 2. Default hotkeys claim five Recorded once more only so the human reviewer sees it, not to re-argue it: Optional improvements
Minor polish - none of this affects users, so it's your call. Most carry over from the previous rounds and still apply.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
New commits were pushed, so this pull request left the human review queue and is back to waiting-for-author. Comment |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Since the last round: the host-process change is settled with the maintainer and the README documents it, the 1. A single per-window virtual-desktop query failure tears down and rebuilds the whole VD COM stack, and can latch into a ~1 s rebuild loop.
HRESULT hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
if (FAILED(hr) && ReinitializeVirtualDesktopAPI() && g_vd.desktopManager) {
hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
}
if (FAILED(hr)) RuntimeLifecycle::RequestMaintenance(false);But these are per-window queries, and a per-window
The distinction the code needs is "the platform is broken" vs. "I couldn't classify this one window". Only the former justifies reinitialization or a maintenance request: // Only a dead/disconnected proxy justifies rebuilding the VD stack. A per-window
// HRESULT (e.g. the HWND died between EnumWindows and this call) is just an
// unknown answer for that window.
static bool IsDeadVirtualDesktopProxy(HRESULT hr) {
switch (hr) {
case RPC_E_DISCONNECTED:
case RPC_E_SERVERFAULT:
case CO_E_OBJNOTCONNECTED:
case HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE):
case HRESULT_FROM_WIN32(RPC_S_CALL_FAILED):
return true;
default:
return false;
}
}
HRESULT hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
if (FAILED(hr) && IsDeadVirtualDesktopProxy(hr) &&
ReinitializeVirtualDesktopAPI() && g_vd.desktopManager) {
hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
}
if (FAILED(hr) && IsDeadVirtualDesktopProxy(hr)) {
RuntimeLifecycle::RequestMaintenance(false);
}
return SUCCEEDED(hr);
2. Raised in the previous two rounds as optional; promoting it because the visible half is a real artifact, not just a cost. In case EVENT_OBJECT_LOCATIONCHANGE:
if (!Platform::WindowEvents::HasTrackedMonitorOwnershipMismatch(hwnd) &&
HandleTiledWindowLocationChange(hwnd)) {
break;
}
ReconcileManagedWindowStateNow(
hwnd, ReconcileScope::Participation | ReconcileScope::Monitor);
ScheduleLifecycleReconcile(hwnd);
break;
case EVENT_OBJECT_LOCATIONCHANGE:
// A drag is authoritative until MOVESIZEEND, which reconciles monitor
// ownership for this HWND. Don't probe or migrate underneath the user.
if (IsMoveSizeGestureInProgress(hwnd)) break;
if (!Platform::WindowEvents::HasTrackedMonitorOwnershipMismatch(hwnd) && ...Optional improvements
Minor polish - none of this affects users, so it's your call. Most of these carry over from earlier rounds and still apply.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
Noted. I wouldn't consider those release-blocking issues. Leaving them unaddressed for now, and I'll assess those separately - I can do a follow up patch soon afterwards if they are genuine issues. |
|
/ready-for-reviewer |
|
That's correct, and generally it'd probably be best to limit the mod to x86-64 now:
|
Agreed. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.
1. A single per-window virtual-desktop query failure tears down and rebuilds the whole VD COM stack, and can latch into a ~1 s rebuild loop.
HRESULT hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
if (FAILED(hr) && ReinitializeVirtualDesktopAPI() && g_vd.desktopManager) {
hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
}
if (FAILED(hr)) RuntimeLifecycle::RequestMaintenance(false);These are per-window queries, and a per-window
The distinction the code needs is "the platform is broken" vs. "I couldn't classify this one window"; only the former justifies reinitialization or a maintenance request: // Only a dead/disconnected proxy justifies rebuilding the VD stack. A per-window
// HRESULT (e.g. the HWND died between EnumWindows and this call) is just an
// unknown answer for that window.
static bool IsDeadVirtualDesktopProxy(HRESULT hr) {
switch (hr) {
case RPC_E_DISCONNECTED:
case RPC_E_SERVERFAULT:
case CO_E_OBJNOTCONNECTED:
case HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE):
case HRESULT_FROM_WIN32(RPC_S_CALL_FAILED):
return true;
default:
return false;
}
}
HRESULT hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
if (FAILED(hr) && IsDeadVirtualDesktopProxy(hr) &&
ReinitializeVirtualDesktopAPI() && g_vd.desktopManager) {
hr = g_vd.desktopManager->IsWindowOnCurrentVirtualDesktop(hwnd, onCurrent);
}
if (FAILED(hr) && IsDeadVirtualDesktopProxy(hr)) {
RuntimeLifecycle::RequestMaintenance(false);
}
return SUCCEEDED(hr);
2. In case EVENT_OBJECT_LOCATIONCHANGE:
if (!Platform::WindowEvents::HasTrackedMonitorOwnershipMismatch(hwnd) &&
HandleTiledWindowLocationChange(hwnd)) {
break;
}
ReconcileManagedWindowStateNow(
hwnd, ReconcileScope::Participation | ReconcileScope::Monitor);
ScheduleLifecycleReconcile(hwnd);
break;
case EVENT_OBJECT_LOCATIONCHANGE:
// A drag is authoritative until MOVESIZEEND, which reconciles monitor
// ownership for this HWND. Don't probe or migrate underneath the user.
if (IsMoveSizeGestureInProgress(hwnd)) break;
if (!Platform::WindowEvents::HasTrackedMonitorOwnershipMismatch(hwnd) && ...Optional improvements
Minor polish - none of this affects users, so it's your call. These all carry over from earlier rounds and still apply to the current source.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
Changelog
MultiWM
MultiWM is a continuation of the original Tiling Helper by U2X1. Explicit approval from the original author has been obtained, as they no longer intend on maintaining the original Tiling Helper. Overlap concerns should be treated as settled.
A lightweight, low-cortisol window manager for Windows 11 with true per-virtual-desktop layouts - including floating - and simple, predictable controls.
Windows 10 support is currently untested.
Features
or float individual windows while retaining useful geometry.
and migrate windows, or keep explicitly tiled groups under your control.
instead of implementing a separate desktop system.
continuous polling.
Layouts
Getting Started
Move a tiled window to float it or swap it with the window underneath.
Hotkeys are available for tiling, changing layouts, moving windows through the logical
order, floating a window, and switching between Automatic and Manual management.
A small tray indicator shows the active workspace layout and provides quick access to layouts and management mode.
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.