A scrolling tiling window manager for macOS, built on the Niri column layout paradigm.
Nehir (Turkish for "river") — windows flow in columns, scrolling horizontally across your screen.
- Niri scrolling column layout — windows arranged in columns that scroll horizontally, with automatic overflow tabbing when stacked windows cannot fit at their minimum heights
- Workspace management — multiple workspaces with hotkey switching
- Window borders — configurable colored borders on the focused window
- Workspace bar — per-monitor status bar showing workspace names and app icons
- Focus follows mouse — optional hover focus
- Multi-monitor support — seamless window management across displays. For the best Niri scrolling experience, use an auto-hide Dock and arrange displays vertically in macOS System Settings to avoid parked offscreen windows bleeding onto neighboring monitors.
- Overview mode — bird's-eye view of all windows
- Command palette — fuzzy search for commands
- App rules — per-application layout overrides
- IPC — Unix socket for external control via
nehirctl - TOML configuration — split config under
~/.config/nehir/
After the first release is published, Nehir can be distributed from the guria/tap Homebrew tap:
brew tap guria/tap
brew install --cask nehirNehir requires Accessibility permissions after installation:
System Settings > Privacy & Security > Accessibility
# Package the app bundle
mise run package:release
# User-local install (no sudo)
mkdir -p "$HOME/Applications" "$HOME/.local/bin"
rm -rf "$HOME/Applications/Nehir.app"
cp -R dist/Nehir.app "$HOME/Applications/Nehir.app"
install -m 755 .build/apple/Products/Release/nehirctl "$HOME/.local/bin/nehirctl"Or use mise:
# User-local install
mise run install
# System-wide install
mise run install:system# Run
Nehir
# CLI control (requires IPC enabled)
nehirctl command focus left
nehirctl command switch-workspace 2
nehirctl --helpNehir restores parked/offscreen windows during a normal graceful quit. A force quit, crash, or kill -9 bypasses shutdown cleanup, so macOS may leave previously parked windows at the screen edge.
If that happens, use macOS's built-in window shortcuts to bring the selected Mission Control window back onscreen:
- Fill —
Control+Globe/Fn+F - Centre/Center —
Control+Globe/Fn+C
You can also find these actions in the macOS Window menu. In Mission Control, selecting the stuck window and applying Fill or Centre/Center usually restores it to the visible desktop.
Trace capture and the other debug commands are gated behind Developer Mode. Enable it in the Diagnostics tab of Settings — this unlocks the Debug: … commands in the command palette and hotkey settings, enables the IPC debug endpoints, and reveals the Runtime State and Recent Traces panels in that same tab.
With Developer Mode on, you can start a trace capture from anywhere:
- Command palette — Debug: Toggle Trace Capture (default hotkey
Ctrl+Option+Cmd+T) - Custom shortcut — bind it under Settings → Hotkeys
- Workspace bar — turn on Show Trace Capture Button (in the Diagnostics or Workspace Bar settings) for a one-click record button in the bar
- CLI / IPC —
nehirctl command debug trace toggle; accepts an optionaldesiredStateargument (active/inactive) for idempotent scripting, e.g.nehirctl command debug trace toggle active
A clean trace covers only the misbehavior, with Nehir's own UI out of the way:
-
Enable Developer Mode once, in Settings → Diagnostics.
-
Start trace capture. A custom hotkey or the workspace-bar button (Show Trace Capture Button) is ideal here — they fire without the Settings window open.
-
Close the Settings window. Nehir-owned windows (Settings, Command Palette, App Rules) change focus and layout behavior, so reproducing with them closed matches how issues actually happen in normal use.
-
Reproduce the issue as you normally would.
-
Stop trace capture. A log bundle is written to:
${XDG_STATE_HOME:-$HOME/.local/state}/nehir/traces/and its path is copied to your clipboard.
-
Share the trace file, not the path — a local path is only meaningful on your machine. Use Copy File in the Recent Traces list, or drag it out of the traces folder. The Recent Traces list in the Diagnostics tab keeps the last ten captures and lets you copy the path (handy for pasting into a terminal), copy the file, or reveal the folder.
Tip: start capture before you trigger the misbehavior and stop it immediately after, so the log isn't padded with unrelated activity.
The other debug commands in the same category:
- Debug: Dump Runtime State — copies the current runtime debug dump to the clipboard and writes it to the unified log
- Debug: Reset Runtime State — clears runtime debugging state and reboots tracking from a startup-style rescan
- Debug: Restart Clearing Runtime State — clears runtime debugging state and relaunches the app (the confirmation dialog has an Enable Tracing checkbox that starts capture as early as possible during bootstrap)
For IPC/CLI usage, see docs/IPC-CLI.md.
Nehir uses a split-file config layout under ~/.config/nehir/:
~/.config/nehir/
├── settings.toml # core app behavior
├── hotkeys.toml # physical keybindings
├── workspaces.toml # workspace definitions
├── apprules.d/ # one file per app rule
│ ├── com-google-chrome.toml
│ └── pip-floating.toml.sample # inactive sample
└── monitors.d/ # per-monitor overrides
└── studio-display.toml
All files are watched for changes — edits are applied live without restarting.
See Configuration Principles for the design rationale.
Two settings interact here:
moveMouseToFocusedWindowmoves the pointer after focus changes, but Nehir treats it primarily as a keyboard/command-navigation affordance. Pointer-originated focus changes do not warp the cursor: mouse hover/click, workspace bar clicks, tab overlay clicks, trackpad gestures, scroll animations, and floating-window clicks/drags all preserve pointer position.- Empty workspace command switching is the main intentional exception: when there is no focused window target, Nehir warps to the target monitor center so the pointer lands on the display you navigated to.
focusFollowsMouseis debounced and refreshes after scroll/swipe animations settle and after owned Nehir UI windows (Settings, App Rules, Command Palette) close, because those interactions may not generate a fresh mouse-move event.- With
focusFollowsMouseenabled, swipe gesture end updates the viewport selection but does not commit focus to the snapped column; final focus follows the pointer after the gesture/animation settles. - Tiled hover focus is disabled while a floating window is the active surface above the Niri layout, so moving toward that floating window does not accidentally focus and raise a tiled column behind it.
- A floating window that is merely visible but behind the active tiled window does not disable tiled hover focus.
- Hover focus is also blocked over visible unmanaged WindowServer windows and briefly suppressed after floating/unmanaged pointer interaction so clicking or dragging those windows does not immediately activate a tiled column behind them.
Nehir defaults are stored and shown as physical key chords.
- Option+Command — navigate, focus, and open UI
- Option+Shift+Command — move the focused window
- Control+Option+Command — larger-scope navigation such as workspace history and column indexes
- Hyper — physical Control+Option+Shift+Command, reserved for structural moves
For a lighter way to enter the base layer, see the Karabiner double-Command recipe.
The goal is a small set of predictable modifier patterns:
without Shift = go there
with Shift = move current window there
Hyper = reshape or move structure
# Build (debug)
mise run build
# Build and run
mise run dev
# Release build
mise run build:release
# Run tests (requires Xcode)
mise run test
# Clean
mise run cleanSee CONTRIBUTING.md for setup details, changesets, the release flow, and Nehir's maintenance principles.
Nehir is the macOS embodiment of an idea with a clear family tree.
Nehir's interaction model — windows in horizontally scrolling columns — is a direct port of Niri's design language. Niri is a scrollable tiling Wayland compositor; Nehir brings that workflow to macOS.
Nehir is an opinionated fork of OmniWM, a general-purpose macOS tiling window manager by BarutSRB. We borrowed its macOS window-management engine and narrowed it to a single layout engine, dropping backward-compatibility baggage to do one thing well. Deeply grateful to the original author for the foundation — see NOTICE.md for full attribution.
Upstream was briefly renamed to Hiro with a rewrite announced under that name, then returned to the OmniWM name. The Hiro announcement is no longer public upstream; an archived copy is preserved at web.archive.org. See NOTICE.md for the current attribution and fork-history details.
Notable changes from OmniWM
- Single layout model. Nehir is rebuilt around Niri-style scrolling columns instead of keeping multiple layout/control models.
- No legacy compatibility layer. Configuration, defaults, hotkeys, and behavior are allowed to change to fit Nehir's narrower workflow.
- Required motion stays enabled. Unlike OmniWM's user-toggleable animation preference, Nehir treats layout motion as part of the interaction model: disabling it makes Niri-style scrolling, resizing, and transition state hard to follow, so there is no
animationsEnabledsetting. - Split TOML configuration. Runtime config is organized under
~/.config/nehir/with separate files for settings, hotkeys, workspaces, app rules, and monitor overrides. - Close/collapse focus stays local. When macOS reports another same-app window as focused after closing or collapsing the current one, Nehir treats that as native fallback focus rather than user navigation. Same-app fallback to inactive workspaces is ignored, and unmanaged quick-terminal fallback is also ignored on the current workspace so the viewport does not scroll to that app's managed column. Explicit Nehir focus commands still take precedence.
- Configurable gesture scroll snap. Trackpad swipe gestures can snap to column boundaries or stop freely mid-scroll. Controlled by
gestures.scrollSnapinsettings.toml(defaulttrue). - Smarter mouse focus and cursor warp. Pointer-initiated focus no longer makes
moveMouseToFocusedWindowjump the cursor, and hover focus is constrained around floating/unmanaged windows to fit the Niri layout model. - Built-in runtime debugging tools. Nehir now ships command-palette and IPC/CLI actions to dump runtime state, reset/rebootstrap runtime state, restart while clearing runtime state, and capture runtime trace bundles under
${XDG_STATE_HOME:-$HOME/.local/state}/nehir/traces/.
Nehir is one of several projects exploring this workflow:
- PaperWM — GNOME Shell.
- Paneru — macOS (another Niri-inspired strip manager).
- karousel — KDE.
- papersway — sway/i3.
- hyprscroller and hyprslidr — Hyprland.
- PaperWM.spoon — macOS (Hammerspoon).
GPL-2.0-only