A scrollable tiling window manager for Windows.
hero.webm
Most Windows tilers use tree or BSP layouts. LeopardWM is scroll-first: windows sit on a horizontal strip, and your monitor acts as a viewport that scrolls over them. Navigation stays spatially consistent as windows are added — you move through context instead of constantly rebuilding split trees.
- Vsync-aligned animations — smooth scrolling powered by a
DwmFlush-driven animation engine - First-class touchpad gestures — three-finger swipes drive focus and scroll out of the box
- Disables Windows 11 Snap Layouts on managed windows — no more accidental edge-snap when you drag a tile
- Auto-detected per-window rounded corners and high-contrast/reduced-motion/battery awareness — system integration that respects user settings
- WebView2 settings GUI with Mica backdrop and live theme switching — not just a config file
- GPL-3.0 — commercial use without a paid license, written in safe Rust
Overview — zoom out to a map of your non-empty workspaces and jump anywhere
overview.webm
Workspaces — per-monitor workspaces; switch between them and move windows across
workspaces.webm
Tabbed columns — collapse a column into a tab strip, only the active tab fills the rect
tabbed.webm
Scratchpad — stash a window out of the layout and summon it back as a floating overlay
scratchpad.webm
Sticky windows — pin a window so it follows you across workspaces, tiled or floating
sticky.webm
A few deliberate non-features, so you know what you're getting:
- Scroll-first, not multi-layout. No BSP, no DWindle, no Equal/Stair/UltrawideVerticalStack — and we won't add them. niri (Wayland) and PaperWM (GNOME) stay scrolling-only by choice; the horizontal strip is the identity. If you want 9 layout variants, komorebi is the right tool.
- No Virtual Desktop bridging. Per-monitor workspaces don't map cleanly to Windows' global Virtual Desktops, and the only library that bridges them (
winvd) breaks every 3-6 months on Windows feature updates. Instead,Win+Ctrl+Arrowis intercepted and routed to LeopardWM's workspace prev/next so the native muscle memory still works. - Named-pipe IPC, not WebSocket. Lower latency, no port allocation, no firewall prompts. If browser-based bar integration becomes a real ask, we'll add a thin bridge rather than make the daemon serve sockets directly.
- Multi-monitor workspaces with monitor-aware focus and move (9 workspaces per monitor)
- Global hotkeys with live config reload
- PowerToys Shortcut Guide export —
lwm query hotkeyslists effective bindings;lwm export-shortcut-guidewrites a user manifest (stdout,--output PATH, or--install) - Smooth scroll animations with layout transition effects (vsync-locked)
- Touchpad gestures with configurable swipe actions
- Drag-and-drop column reorder (Shift+drag to merge windows)
- Tabbed columns — toggle a column between vertical-stack and tab-strip mode (
Ctrl+Alt+T); only the active tab fills the column rect, the rest sit in a clickable strip above - Scratchpad — stash the focused window out of the layout (
Ctrl+Alt+Shift+S) and summon it back as a floating, centered overlay on demand (Ctrl+Alt+S); stash it again to release it back to tiling - Sticky windows — pin a window (
Ctrl+Alt+Y) so it follows you across workspaces, keeping its current mode: a tiled window stays tiled (a column you can cycle to), a floating window stays a floating overlay - Overview mode —
Ctrl+Alt+Spaceopens a map of the monitor's non-empty workspaces; click a window card to jump to it, click a row to switch workspace, or navigate with arrows or your configured keyboardfocus_left/focus_right/focus_up/focus_downbindings while keeping the overview open. Your configured keyboardmove_window_up/move_window_downbindings move the selected window to the previous / next numbered workspace on that monitor, including empty workspaces, without switching workspace or closing the overview; selection follows the moved window. Moves stop at workspace 1 or 9, sticky windows stay put, andmove_window_left/move_window_rightdo nothing while the overview is open. Enter selects, digits switch workspace, and Esc cancels without undoing moves. Other bound actions and gesture bindings keep their normal behavior. - Per-app window rules — float, ignore, or tile by class/title/executable, plus per-app open behavior: target workspace, initial column width, open maximized
- Floating and fullscreen toggles
- Width and height presets with column equalization, maximize-column, center-column
- Active focus border with auto-detected rounded corners
- System tray with pause, reload, settings, and diagnostics
- WebView-based settings GUI (Mica backdrop, live theme switching, dark mode)
- Safe mode for troubleshooting (
--safe-mode) - Built-in diagnostics (
lwm doctor) - Workspace persistence and session recovery
- Optional empty-workspace skipping for relative navigation
- Autostart via Registry, configurable from CLI / Settings / tray
- In-app update notifier — daily check against GitHub Releases, opt-out
- Windows 11 Snap Layouts disabled for managed tiled windows
- Battery-aware: animations auto-disable on battery / power saver
- Respects Windows reduced-motion and high-contrast settings
- DPI-aware gap and border scaling per-monitor
winget install jcardama.LeopardWM # Windows Package Manager
scoop bucket add extras # Scoop (first time only)
scoop install extras/leopardwmThis installs LeopardWM and puts leopardwm, leopardwm-cli, and lwm on your PATH. Use winget upgrade jcardama.LeopardWM or scoop update leopardwm to install the latest release.
Download LeopardWM-x.y.z-x86_64.msi from GitHub Releases and run it. Re-running a newer MSI upgrades in place — no manual uninstall needed.
For users who prefer not to install:
- Download
LeopardWM-x.y.z-x86_64-windows.zipfrom GitHub Releases - Extract to a permanent location
- Run
leopardwm.exe - (Optional) Enable autostart:
lwm autostart enable
Current releases are not code-signed, so Windows SmartScreen may show a warning on first install.
Prerequisites: Rust with the MSVC toolchain (stable-x86_64-pc-windows-msvc).
See Contributing for development checks and the optional Node.js Settings check.
git clone https://github.com/jcardama/LeopardWM.git
cd LeopardWM
cargo build --releaseStart the daemon:
./target/release/leopardwm.exeA default config is created automatically at %APPDATA%\leopardwm\config\config.toml. Customize via the tray icon → Settings, or edit the file directly.
Set new_window_monitor = "focused" under [behavior] or choose Settings → Behavior → New window monitor → Focused monitor to open new windows on the focused monitor's active workspace, including an empty workspace. The default, "opening", uses the monitor where Windows opened the window, falling back to the focused monitor when it opens outside all monitors. If no focused monitor is known, "focused" uses the opening behavior. A workspace rule selects the workspace index on the policy-selected monitor; sticky windows retain their opening monitor and active-workspace placement. Floating windows moved by this policy are fitted to the chosen monitor's work area before being centered. Changes apply live on config reload or Settings save only to windows that open while LeopardWM is running. Already-open windows discovered at startup or during reload/rescan keep their existing monitor and are added to its active workspace; their floating rectangles are not moved or fitted by this policy. Already-managed windows and startup persistence restores are unchanged.
Most hotkeys use Ctrl+Alt as the base modifier. Layered pattern: base = focus, +Shift = move, +Win = monitor scope. Every hotkey is rebindable in config.toml. Combos Windows reserves (like Win+Ctrl+Arrow) can't be bound directly, but the opt-in Reclaim Windows-reserved shortcuts setting lets you use them anyway.
| Key | Action |
|---|---|
Ctrl+Alt+H/L/J/K |
Focus left / right / down / up |
Ctrl+Alt+Home / End |
Focus start / end of strip |
Ctrl+Alt+Shift+H/L |
Move column left / right |
Ctrl+Alt+Shift+Home / End |
Move column to start / end of strip |
Ctrl+Alt+Shift+J/K |
Move window down / up in column |
Ctrl+Alt+[ / ] |
Move window to left / right column |
Ctrl+Alt+Shift+[ / ] |
Expel window to new column left / right |
Ctrl+Alt+, / . |
Consume left / right column's window into the focused column |
Ctrl+Alt+Minus / Ctrl+Alt+Equals |
Cycle column width down / up |
Ctrl+Alt+Shift+Minus / Ctrl+Alt+Shift+Equals |
Cycle window height down / up |
Ctrl+Alt+0 |
Equalize all column widths |
Ctrl+Alt+Shift+0 |
Equalize window heights in column |
Ctrl+Alt+M |
Maximize focused column to viewport width |
Ctrl+Alt+C |
Center focused column in viewport |
Ctrl+Alt+Win+,/. |
Focus monitor left / right |
Ctrl+Alt+Win+Shift+,/. |
Move window to monitor |
Ctrl+Alt+1...9 |
Switch to workspace 1–9 |
Ctrl+Alt+Shift+1...9 |
Move focused window to workspace 1–9 |
Ctrl+Alt+Space |
Toggle workspace overview |
Ctrl+Alt+Shift+Left / Right |
Workspace prev / next (cycles) |
Ctrl+Alt+W |
Close focused window |
Ctrl+Alt+F |
Toggle floating |
Ctrl+Alt+Shift+F |
Toggle fullscreen |
Ctrl+Alt+T |
Toggle tabbed mode on focused column |
Ctrl+Alt+S |
Toggle scratchpad (summon / hide) |
Ctrl+Alt+Shift+S |
Stash focused window to scratchpad (or release it back to tiling) |
Ctrl+Alt+Y |
Toggle sticky (follow across workspaces, keeping tiled/floating mode) |
Ctrl+Alt+P |
Toggle pause |
Ctrl+Alt+R |
Refresh (re-enumerate windows) |
Ctrl+Alt+Shift+R |
Reload config |
Win+Ctrl+Escape |
Emergency restore + panic-revert |
The scratchpad and sticky pins are session-scoped: they are keyed by window handle and reset when the daemon restarts.
Stack multiple windows into a clickable tab strip inside any column. Combine with the scrolling viewport for niri-style tabs that also pan horizontally — a combination no other Windows window manager ships today.
Basics
Ctrl+Alt+Ton the focused column toggles between vertical stacking (the default) and tabbed modeCtrl+Alt+J/Ctrl+Alt+Kcycle the active tab — same keys as intra-column focus, no new bindings to learn- Click any tab in the strip to activate it; the click is a real focus change, so the border, foreground state, and IPC events all follow
- Tab titles and icons update live as windows rename themselves or swap notification badges
Per-tab actions
- Hover any tab to reveal a close-X at its right edge — click to close the tabbed window
- Middle-click does the same as the close-X
- Right-click any tab for a context menu:
Close window/Untab this window/Rename tab… - The implicit close gesture (X-button / middle-click) is configurable in Settings → Behavior → "Tab close action" —
close_window(default, browser-style) oruntab(rip the tab out into a new vertical column to the right) - Right-click menu items always carry their literal action —
Close windowalways closes regardless of the toggle,Untab this windowalways untabs - "Rename tab…" opens a modal dialog seeded with the current tab title. Submitting saves a per-window override that survives untab, workspace moves, and daemon restart. Clearing the field removes the override and the live title returns
Drag-and-drop (Chrome semantics)
- Drop a window onto a tabbed column from anywhere — body or strip — and it appends as the rightmost tab and becomes active
- The drop-zone ghost spans the whole column rect so the target is unambiguous
Lifecycle
- A tabbed column with one window auto-reverts to vertical mode
- Tabbed state (and which tab is active) survives daemon restart, along with any per-tab title overrides
- Tab overrides for windows that no longer exist are pruned automatically at daemon startup
- The strip hides during fullscreen, pause, and on workspaces with no tabbed column
Customization — strip height, background, active/inactive text colours, active highlight, opacity, and the tab close action are configurable from the Settings UI or [appearance] / [behavior] (tab_strip_height, tab_strip_bg, tab_strip_active_bg, tab_strip_active_text, tab_strip_inactive_text, tab_strip_opacity, tab_close_action).
LeopardWM ships two interchangeable CLI binaries — both invoke the same code:
| Binary | When to use |
|---|---|
leopardwm-cli |
Canonical name. Use in docs, scripts, and shared examples. |
lwm |
Short alias for daily typing. |
Examples below use whichever is shorter for the line.
lwm run # start the daemon (idempotent — no-op if already running)
lwm stop # stop the daemon
lwm status # show version, monitor count, window count, uptimeMatching versions (Recommended): use the CLI and daemon from the same release. Corrected lwm run pending-apply handling depends on both sides understanding apply_pending. A matching pair reports a still-pending recovery landing as a non-success without emergency visibility restore. An older CLI maps that status to unknown and may invoke emergency restore. Mixed versions are not negotiated or isolated on the pipe. Pending handling does not change the Apply request shape. This release still advances the overall IPC protocol from v3 to v4 for workspace-state snapshots.
lwm query workspace # current workspace placements as JSON
lwm query focused # focused window info
lwm query all-windows # every managed window across all workspaces
lwm query hotkeys # effective bindings plus config diagnosticsExport the daemon's effective hotkeys as a PowerToys Shortcut Guide user manifest:
lwm export-shortcut-guide # YAML to stdout
lwm export-shortcut-guide --output PATH # write a file
lwm export-shortcut-guide --install # replace the user manifest--output and --install are mutually exclusive. --install writes
%LOCALAPPDATA%\Microsoft\WinGet\KeyboardShortcuts\LeopardWM.LeopardWM.en-US.yml.
Use matching CLI and daemon builds; query and export require IPC v3. The manifest is a snapshot: after changing bindings, reload LeopardWM and export again, then reopen Shortcut Guide. Query and export report resolved configuration, not proof that the keyboard hook is installed or active; safe mode can still list configured bindings.
Each alternative binding is a separate shortcut. Equivalent spellings of the same physical chord resolve consistently: the lexicographically first valid configured binding wins, and warnings identify ignored collisions. F13-F24 used as modifiers cannot be represented by PowerToys and are skipped with a warning; F13-F24 used as ordinary trigger keys can be exported. Warnings go to stderr so stdout remains usable YAML.
Choose Settings → Appearance → Language, or set this in config.toml:
[appearance]
language = "en" # "en" (default) or "zh-CN"The open Settings window, tray menu, and daemon notifications use the selected language after saving or reloading config; no daemon restart is needed. Unsupported values fall back to English with a config warning. There is no automatic OS-language selection. Simplified Chinese is a machine draft pending native-speaker review. Locale files are embedded in the daemon, so editing a translation requires a rebuild. See the localization contributor guide for file locations, placeholders, validation, and the surfaces that remain English. To edit or add a catalog, see Translating LeopardWM.
Enable Settings → Layout → Center single column, or set center_single_column = true under [layout], to center the only active tiled column when it fits within the viewport’s outer gaps. It is off by default and applies with every centering_mode (center, just_in_view, or on_overflow), independently of center_past_edges. Minimized-only columns and floating windows do not count; wider columns keep the normal scrolling behavior.
Taskbar buttons — Settings → Behavior → Taskbar buttons, or taskbar_buttons under [behavior], offers "hide_offscreen" (default, hide buttons on inactive workspaces and for tiled windows scrolled out of view), "hide_inactive_workspaces" (keep every button on each monitor's active workspace, even when scrolled out of view), and "show_all". Floating and minimized windows keep their buttons on the active workspace; inactive workspaces hide all buttons unless "show_all" is selected. Sticky windows follow the active workspace. Changes apply on config reload without changing window placement. Existing hide_offscreen_taskbar_buttons = true / false maps to "hide_offscreen" / "show_all"; when both keys are present, taskbar_buttons wins. Settings saves only the new key.
Most users drive the layout via hotkeys, but every hotkey has a CLI equivalent — useful for scripting or AutoHotkey integration.
lwm focus left | right | up | down
lwm cycle-width # next width preset, wrapping at the end
lwm cycle-height # next height preset, wrapping at the end
lwm move left | right # move focused column
lwm move-window up | down # reorder within a column
lwm workspace 3 # switch to workspace 3
lwm toggle-floating
lwm toggle-fullscreen
lwm scratchpad-stash # stash focused window (or release the scratchpad)
lwm scratchpad-toggle # summon / hide the scratchpad
lwm toggle-sticky # pin / unpin focused window on every workspace
lwm toggle-ignore # session-only ignore for the OS foreground windowSet floating_above_tiled = true under [behavior] or enable Settings → Behavior → Keep floating windows above tiled windows (off by default) to keep visible managed floating and sticky floating windows above tiled windows when a tiled window receives focus. This only changes relative ordering in the normal Z-order band: floating windows keep their order among themselves, do not become native topmost, and never take focus or move/resize. App-owned topmost and unmanaged windows are not modified. Minimized windows, inactive workspaces, and other monitors are excluded; fullscreen on that monitor and overview suspend re-raising.
floating_above_tiled works only when track_focus_changes = true (the default). Restart LeopardWM after changing track_focus_changes, since focus tracking hooks are configured at startup. Turning tracking off also stops floating re-raises immediately on reload.
Disabling the option simply stops re-raising. There is no daemon-owned Z-order state to restore on disable, ignore, release, or shutdown.
Set skip_empty_workspaces = true under [behavior] or enable Settings → Behavior → Skip empty workspaces (off by default) to skip empty slots on the same monitor. This applies to workspace_next / workspace_prev and to focus_down / focus_up when workspace_edge_wrap switches workspaces, wrapping 1 ↔ 9. If no other occupied workspace exists, they do nothing.
Tiled, floating, and minimized managed windows count as occupancy; sticky windows do not. Explicit numbered selection, all move-to-workspace commands (including edge-wrap moves), overview, and horizontal focus are unchanged. Closing or moving away the last window does not automatically switch workspaces.
cycle_width and cycle_height are unbound by default; assign each a shortcut in Settings or [hotkeys]. Height cycling applies only to vertically stacked multi-window columns.
lwm toggle-ignore targets the actual OS foreground window, not LeopardWM's cached
focus. Toggling out unmanages that window for this daemon session only; toggling
it back in re-admits it on the current monitor's active workspace. Persistent
Ignore rules still win. The action is in the hotkey catalog with no default
shortcut.
lwm release-all-windowsThis pauses tiling, clears LeopardWM's active decoration, globally attempts to
restore any top-level windows parked at LeopardWM's off-screen sentinel, and
cascades every tiled and floating managed window. Membership and admission stay
intact: use lwm toggle-pause to resume tiling. The command has no confirmation
prompt. If recovery fails or a live window cannot be restored, cascaded, or is
still maximized, release may be partial, tiling remains paused, and the CLI
reports the failure. Release first invalidates older animation work and waits up
to 100 ms for the animation worker. If that worker is busy, or a timed-out
placement worker is still recovering, no cascade is performed and tiling stays
paused. Retry lwm release-all-windows after the worker finishes; the failed
request never schedules a later cascade. Final cascade positions are ordered
after previously queued animation positions, and ordering failures are reported.
lwm autostart enable # writes HKCU\Software\Microsoft\Windows\CurrentVersion\Run
lwm autostart disable # removes itThis is also exposed as a Settings UI toggle and a tray menu item.
lwm subscribe # legacy event kinds, newline-delimited JSON
lwm subscribe --events workspace,focused_window # filtered subset
lwm subscribe --events workspace_state # complete all-monitor workspace state
lwm query workspaces # one complete workspace snapshot
lwm workspace 2 --monitor '\\.\DISPLAY2' # select a workspace on that display
lwm subscribe | jq # pretty-printed in another terminalAfter the daemon answers Subscribed, the connection stays open and streams
IpcEvent frames as state changes occur. An empty filter preserves only the
legacy event set (workspace_changed, focused_window_changed, layout_changed,
config_reloaded, heartbeat); complete workspace snapshots require explicit
--events workspace_state opt-in and acknowledgement (IPC v4). Targeted workspace commands
use 1-based indices and transfer focus to the named monitor; snapshot indices are
0-based. Full schemas and sample clients are in agent_docs/ipc-events.md.
Show all nine workspaces on a selected monitor and switch by clicking a button in Yasb, using its built-in Custom widget and the existing CLI. See the complete Yasb configuration and styling recipe. It includes active/occupied indicators and workspace-name tooltips; updates are polled every five seconds rather than streamed. No plugin or IPC change is required.
lwm doctor # diagnostic checks (config valid, daemon reachable, hotkey conflicts, etc.)
lwm collect-logs # bundles logs + crash reports into a zip for bug reports
lwm reload # reload config from disk without restarting
lwm refresh # re-enumerate windows after weird state
lwm panic-revert # emergency: uncloak everything, drop daemon out of managementRun lwm help (or lwm <subcommand> --help) for the full surface — there are ~40 subcommands.
Precision Touchpads that do not send three-finger wheel events can opt in to
native HID swipe detection with raw_input = true under [gestures] in
config.toml (or the Gestures setting). Restart LeopardWM after changing it.
lwm doctor and lwm collect-logs show whether native swipes are active or why
they are inactive.
Set the Windows three- and four-finger touchpad gestures to Nothing under
Settings > Bluetooth & devices > Touchpad, or Windows will consume the swipe.
The existing mouse hook still handles modifier-plus-scroll; if native
registration or device capability checks fail, wheel-based swipes remain active
when wheel_swipes is enabled.
Raw Input compatibility varies by device.
wheel_swipes = true under [gestures] is the default. Set it to false (or turn
off Settings > Gestures > Wheel-based swipes) to stop injected wheel events from
navigating windows, including mouse side wheels such as the MX Master. This
applies live on config reload; wheel events still reach the application, and
modifier-plus-wheel navigation is unchanged. Non-Precision touchpads that send
swipes as wheel events lose those swipes with it off. Precision Touchpads can
keep native swipes with raw_input = true. enabled = false still disables all
gesture support.
Failed physical gestures are diagnosed with an opt-in, short capture — not by leaving general logging at TRACE. Capture is default off; turning it on does not change gesture behavior.
-
In
%APPDATA%\leopardwm\config\config.toml, set a short interval under[gestures]:diagnostic_capture_secs = 15
The value is startup-only and clamped to 120 seconds. Settings saves preserve the knob; there is no live start command.
-
Restart the daemon with the documented workflow.
lwm reloadis not enough.lwm stop lwm run
Stopping and starting can disturb off-screen client-area or DWM frame geometry. That is a known restart limitation, not a gesture-compatibility claim.
-
During the capture window:
- Two-finger scroll without the configured navigation modifier (
hotkeys.scroll_modifier, defaultCtrl+Alt). Pass-through is expected (stage=classifier outcome=reject). - The same scroll with the modifier. Navigation should classify as a pass and may
recognized/dispatch. - All four three-finger directions, with a pause between each.
- Two-finger scroll without the configured navigation modifier (
-
Record the Windows touchpad setting as observed; do not treat it as a required mapping, and do not assume it guarantees wheel delivery to LeopardWM:
- Windows 10: Settings → Devices → Touchpad
- Windows 11: Settings → Bluetooth & devices → Touchpad
-
Use
lwm doctorto inspect the daemon and CLI process-integrity lines. They do not measure the arbitrary foreground app; separately reported elevated-window blocks are different evidence. The capture header recordsdaemon_integrityonly. This report cannot prove elevated hook visibility, finger count, or device origin. -
When the interval ends, the daemon writes a summary even with zero input. Inspect
%LOCALAPPDATA%\leopardwm\logs\leopardwm-gesture-capture.log, or the Gesture Capture section oflwm collect-logs(full file, not the last-100 daemon tail). For a gesture bug report, share that artifact.Interpretation:
Stage Meaning registrationdisabled/failed/registered/stopped— config and hook setup evidence, not a claim that a capture is running nowhook_deliveryA wheel message reached the production hook (axis, delta, flags, modifier held, swipe candidate) classifierpassentered navigation or swipe handling;rejectis pass-throughaccumulationRunning total after this event timeoutPartial swipe accumulator reset after the gesture timeout cooldownNavigation event suppressed during cooldown recognizedEngine emitted swipe_*orscroll_*dispatchDaemon binding: knownplus a canonical command name,no_action(empty binding), orunknown(command text omitted)no_input=truemeans the capture observed zerohook_deliveryrecords and had no dropped, capped, admitted-at-deadline, or deadline-boundary in-flight records; registration alone still counts as no hook input. It is not proof that the touchpad is dead. Ifrecords_dropped,records_capped,records_admitted_at_close,records_in_flight_at_close, orrecords_after_closeis non-zero, input may have been lost or crossed the bounded deadline — repeat with a shorter interval or fewer gestures. -
Set
diagnostic_capture_secs = 0afterward so the next restart does not rearm capture and overwrite the report. Default-off does not truncate an existing file; a new capture replaces it.
track_focus_changes under [behavior] synchronizes managed focus with Windows focus changes such as Alt-Tab. It defaults to true; changes to focus tracking hooks take effect the next time LeopardWM starts, not on config reload or Settings save.
Note: Crate names and on-disk paths still use
leopardwminternally. A full crate rename is future work.
| Item | Path |
|---|---|
| Config | %APPDATA%\leopardwm\config\config.toml |
| State | %APPDATA%\leopardwm\data\workspace-state.json |
| Log (stdout) | %TEMP%\leopardwm-daemon.log |
| Log (stderr) | %TEMP%\leopardwm-daemon.err.log |
| Gesture capture | %LOCALAPPDATA%\leopardwm\logs\leopardwm-gesture-capture.log |
LeopardWM is a Rust workspace with five crates:
| Crate | Responsibility |
|---|---|
leopardwm-core-layout |
Platform-agnostic scrolling layout engine |
leopardwm-platform-win32 |
Win32 integration, window operations, DwmFlush animation engine |
leopardwm-ipc |
Named-pipe command/response protocol |
leopardwm-daemon |
Runtime event loop, state management, dedicated message-pump threads |
leopardwm-cli |
User-facing CLI (also installed as lwm for shorter typing) |
LeopardWM is a window controller, not a compositor. DWM remains the compositor. Behavior can vary across app frameworks (Win32, WPF, Electron, UWP).
LeopardWM runs unprivileged. A window running at a higher privilege level (elevated/administrator, or a protected process) can't be repositioned by an unprivileged process, so LeopardWM leaves it floating instead of reserving an empty column for it, and lists it under lwm doctor. Run LeopardWM as administrator if you need those windows tiled (note: an elevated WM has the inverse limitation, drag-and-drop from normal apps into it is blocked).
LeopardWM automatically skips certain windows that should never be tiled. You can add your own rules via [[window_rules]] in the config, but these are always active.
These windows are filtered out during enumeration and never enter the layout engine:
| Class | Why |
|---|---|
Progman |
Program Manager (desktop) |
Shell_TrayWnd / Shell_SecondaryTrayWnd |
Taskbar |
WorkerW |
Desktop worker |
Windows.UI.Core.CoreWindow |
UWP system windows |
XamlExplorerHostIslandWindow / TopLevelWindowForOverflowXamlIsland |
XAML islands |
RAIL_WINDOW |
WSLg RemoteApp — RDP-projected Linux windows that break when repositioned |
Ghost |
DWM hung-window replacement — tiling would duplicate the original |
#32770 |
Standard Win32 dialog (Open/Save/Print/Properties) |
Chrome_RenderWidgetHostHWND |
Internal Electron/Chrome render widget, not a real window |
These processes are ignored via built-in window rules (action = ignore):
| Executable | Why |
|---|---|
smartscreen.exe |
Windows Defender SmartScreen |
consent.exe |
UAC elevation prompt |
msiexec.exe |
Windows Installer |
CredentialUIBroker.exe |
Windows credential/login prompt |
SnippingTool.exe |
Screen capture overlay |
Small helpers can share an app's class, title, and executable with its main window. For example, ignore QQ NT helpers with an app-specific size rule:
[[window_rules]]
match_executable = "QQ.exe"
match_max_width = 400
match_max_height = 300
action = "ignore"Both optional limits use the outer window size in logical pixels, the same units as rule width and height. At 100% display scaling, these equal the window's pixel dimensions. At 150%, a 600 × 450 pixel window counts as 400 × 300 logical pixels.
Limits are ANDed with the class, title, and executable conditions; size alone cannot match a window. Rules are first-match-wins, so put the size rule above broader rules for the same app. Size conditions apply on admission: an ignored small window is re-evaluated if it grows or its title changes, but a managed window is never dropped for shrinking. Edit Max width and Max height in a rule's Options menu in Settings. Leaving both blank preserves existing matching behavior.
The focus border tries to match each window's actual corner radius. Apps that explicitly set DWMWA_WINDOW_CORNER_PREFERENCE are honored (DONOTROUND → 0 px, ROUNDSMALL → 4 px, ROUND → 8 px); everything else falls back to the 8 px Win11 default.
Some apps draw their own non-DWM-composited frame with square corners while still reporting the OS default — Firefox / Zen Picture-in-Picture popups are the most common example. Override the corner style per window rule:
[[window_rules]]
match_class = "MozillaDialogClass"
corner_style = "square" # also: "rounded" | "small_rounded"The MozillaDialogClass → square rule ships in the default config as a working example. Open Settings → Window rules, then use a rule’s Options menu to edit corners (Auto / Square / Rounded / Small rounded), placement, or its initial Column width. Column width is a viewport fraction from 0.05 to 1.0; leave it blank for the default width.
If you find LeopardWM useful, consider supporting development:
See CONTRIBUTING.md.
Thanks to everyone who has helped shape LeopardWM.
