A sliding, tiling window manager for MacOS.
2,171
stars
315
commits
Rust
primary language
Sep 10, 2026
updated
A sliding, tiling window manager for MacOS.
Paneru is a MacOS window manager that arranges windows on an infinite strip, extending to the right. A core principle is that opening a new window will never cause existing windows to resize, maintaining your layout stability.
Each monitor operates with its own independent window strip, ensuring that windows remain confined to their respective displays and do not "overflow" onto adjacent monitors.
https://github.com/user-attachments/assets/cbc2e820-635f-408b-923a-6cb47c44704c
(Video by @emreekici3 - https://github.com/emreekici3/dotfiles)
https://github.com/user-attachments/assets/793e7eaa-7909-4086-8380-1fb7861f8780
The fundamental architecture and window management techniques are heavily inspired by Yabai, another excellent MacOS window manager. Studying its source code has provided invaluable insights into managing windows on MacOS, particularly regarding undocumented functions.
The innovative concept of managing windows on a sliding strip is directly inspired by Niri and PaperWM.spoon.
Like all non-native window managers for MacOS, Paneru requires accessibility access to move windows. Once it runs you may get a dialog window asking for permissions. Otherwise check the setting in System Settings under "Privacy & Security -> Accessibility".
Check your System Settings for "Displays have separate spaces" option. It should be enabled - this allows Paneru to manage the workspaces independently.
Multiple displays. Paneru is moving the windows off-screen, hiding them
to the left or right. If you have multiple displays, for example your laptop
open when docked to an external monitor you may experience weird behavior.
The issue is that when MacOS notices a window being moved too far off-screen
it will relocate it to a different display - which confuses Paneru! The
solution is to change the spatial arrangement of your additional display -
instead of having it to the left or right, move it above or below your main
display.
A similar situation
exists with Aerospace window manager.
An option exists (horizontal_mouse_warp) which can make a vertical
arrangement of displays "feel" horizontal.
Off-screen window slivers. Because macOS will forcibly relocate windows
that are moved fully off-screen, Paneru keeps a thin sliver of each
off-screen window visible at the screen edge. The sliver_width and
sliver_height options control the size of this sliver. This is a
workaround for a macOS limitation, not a design choice.
Paneru is built using Rust's cargo. It can be installed directly from
crates.io or if you need the latest version, by fetching the source from Github.
$ cargo install paneru
$ git clone https://github.com/karinushka/paneru.git
$ cd paneru
$ cargo build --release
$ cargo install --path .
It can run directly from the command line or as a service. Note that you will need to grant accessibility privileges to the binary.
If you are using Homebrew, you can install from the formula with:
$ brew install paneru
Or by first adding the tap and then installing by name:
$ brew tap karinushka/paneru
$ brew install paneru
See nix/README.md.
Paneru checks for configuration in following locations:
$HOME/.paneru$HOME/.paneru.toml$XDG_CONFIG_HOME/paneru/paneru.tomlAdditionally it allows overriding the location with $PANERU_CONFIG environment variable.
If none of these files exists, Paneru creates
$XDG_CONFIG_HOME/paneru/paneru.toml with the built-in defaults on first launch.
A Lua script ($XDG_CONFIG_HOME/paneru/init.lua, $HOME/.paneru.lua, or
$PANERU_LUA) replaces the TOML rather than layering on top of it: when one
exists, no paneru.toml is read, created, or watched.
You can use the following basic configuration as a starting point. For a complete guide to all available options, keybindings, and window rules, see the Configuration Guide.
# basic .paneru.toml
[options]
focus_follows_mouse = true
mouse_follows_focus = true
[bindings]
window_focus_west = "cmd - h"
window_focus_east = "cmd - l"
window_resize = "alt - r"
window_center = "alt - c"
quit = "ctrl + alt - q"
Alternatively, the embedded Lua runtime can declare the entire configuration
via paneru.setup{...}, making the TOML file optional — see the
Lua Scripting Guide:
-- init.lua
paneru.setup {
options = { focus_follows_mouse = true, mouse_follows_focus = true },
bindings = {
["window focus west"] = "cmd - h",
["window focus east"] = "cmd - l",
["quit"] = "ctrl + alt - q",
},
}
Changes made to the active configuration file are automatically reloaded while Paneru is running. This is useful for tweaking keyboard bindings and other settings without restarting the application.
Paneru saves managed window layout state to the user state directory
($XDG_STATE_HOME/paneru/state.json, usually
~/.local/state/paneru/state.json) and loads it when Paneru starts. During the
startup restore window, Paneru matches reopened windows to the saved session and
restores their layout placement, virtual workspace row, and display assignment
where possible.
Restore is startup-only. After the configured startup grace period expires, new
or unmatched windows follow the normal configuration and window-rule behavior.
Saved windows that are not present are ignored by default and the restored
layout is compacted around the windows that were found. The behavior is
configured with [restore]; see the
Session Restore section in the
configuration guide.
$ paneru install
$ paneru start
To start Paneru from Spotlight, Alfred, Raycast, or another application launcher, install the lightweight app wrapper:
$ paneru install-app
This creates $HOME/Applications/Paneru.app. Opening the app starts the
installed Paneru launch agent and exits immediately. Remove the wrapper with:
$ paneru uninstall-app
$ paneru
Paneru exposes a send-cmd subcommand that lets you control the running
instance from the command line over a Mach service
(com.github.karinushka.paneru). Any
command that can be bound to a hotkey can also be sent programmatically:
$ paneru send-cmd <command> [args...]
| Command | Description |
|---|---|
window focus <direction|number|managed|unmanaged> | Move focus by direction, column number, managed or unmanaged |
window swap <direction> | Swap the focused window with a neighbour |
window center | Center the focused window on screen |
window resize | Cycle through preset_column_widths |
window grow | Grow to the next preset width |
window shrink | Shrink to the previous preset width |
window vertical resize | Cycle a stacked window through preset_stack_heights |
window vertical grow | Grow a stacked window to the next preset height |
window vertical shrink | Shrink a stacked window to the previous preset height |
window fullwidth | Toggle full-width mode for the focused window |
window manage | Toggle managed/floating state |
window equalize | Distribute equal heights in the focused stack |
window balance | Make all columns match the focused window width |
window stack | Stack the focused window onto its left neighbour |
window unstack | Unstack the focused window into its own column |
window nextdisplay | Move the focused window to the next display |
window nextdisplaysend | Move the window to the next display but stay here |
window virtual <dir> | Switch to the previous/next virtual workspace |
window virtualnum <n> | Switch directly to numbered virtual workspace |
window virtualmove <dir> | Move the window to a different virtual workspace |
window virtualmovenum <n> | Move the window to numbered virtual workspace and follow it |
window virtualsend <dir> | Send the window to a virtual workspace but stay |
window virtualsendnum <n> | Send the window to numbered virtual workspace but stay |
window snap | Snap the focused window into the visible viewport |
mouse nextdisplay | Warp the mouse pointer to the next display |
printstate | Print the internal ECS state to the debug log |
quit | Quit Paneru |
restart | Restart the Paneru service |
Where <direction> is one of: west, east, north, south, first, last.
Window numbers are 1-based and count columns from left to right.
# Move focus one window to the right.
$ paneru send-cmd window focus east
# Swap the current window to the left.
$ paneru send-cmd window swap west
# Center and resize in one shot (two separate calls).
$ paneru send-cmd window center && paneru send-cmd window resize
# Balance all columns to the focused window's width.
$ paneru send-cmd window balance
# Cycle backward through preset widths.
$ paneru send-cmd window shrink
# Grow the focused window's height inside its stack.
$ paneru send-cmd window vertical grow
# Jump to the left-most window.
$ paneru send-cmd window focus first
# Jump to the second window from the left.
$ paneru send-cmd window focus 2
# Switch directly to virtual workspace 3.
$ paneru send-cmd window virtualnum 3
# Send the focused window to virtual workspace 3 without following it.
$ paneru send-cmd window virtualsendnum 3
Paneru also exposes structured JSON state for scripts and status bars:
$ paneru query state --json
$ paneru query virtual-workspaces --json
$ paneru query active --json
$ paneru subscribe --json
query prints a JSON snapshot and exits. subscribe --json keeps the channel
open and emits line-delimited JSON events for changes that integrations usually
care about, including focus changes, virtual workspace changes, window-list
changes, title changes, and display changes. See
QUERY_AND_SUBSCRIBE_FORMAT.md for the
full payload contract.
Because send-cmd talks to the running daemon, you can drive Paneru from shell
scripts, cron jobs, or other automation tools:
open -a Safari && sleep 0.5 && paneru send-cmd window resize.paneru send-cmd window balance to make every
column the same width as the focused window — great for resetting layouts
after unplugging a monitor or when windows get shuffled.paneru send-cmd for
compound actions that go beyond a single hotkey.paneru send-cmd window nextdisplay && paneru send-cmd mouse nextdisplay
paneru query state --json to render the
initial workspace labels, then keep them current with paneru subscribe --json.paneru.setup), event hooks (paneru.on),
keybindings (paneru.bind), and state queries — see the Lua Scripting Guide.There is a public Matrix room
#paneru:matrix.org. Join and
ask any questions.
For a detailed high-level overview of Paneru's internal design, data flow, and ECS patterns, please refer to the Architecture Guide.
Paneru's architecture is built around the Bevy ECS (Entity Component System), which manages the window manager's state as a collection of entities (displays, workspaces, applications, and windows) and components.
The system is decoupled into three primary layers:
src/platform/): Directly interfaces with macOS via objc2 and Core Graphics. It runs the native Cocoa event loop and pumps OS events into a channel consumed by Bevy.src/manager/): Defines OS-agnostic traits (WindowManagerApi, WindowApi) that abstract window manipulation. The macOS-specific implementations (WindowManagerOS, WindowOS) bridge these traits to the Accessibility and SkyLight APIs.src/ecs/): The "brain" of the application. Bevy systems process incoming events, handle input triggers, and manage animations.main branch: Contains the stable, released code.testing branch: Used for experimental features and architectural refactors. This branch is volatile and may be force-pushed.Here are some other projects which implement a similar workflow:
(top 30 of 56)
Rust
98.1%
Nix
1.8%
A sliding, tiling window manager for MacOS.
2,171
stars
315
commits
Rust
primary language
Sep 10, 2026
updated
A sliding, tiling window manager for MacOS.
Paneru is a MacOS window manager that arranges windows on an infinite strip, extending to the right. A core principle is that opening a new window will never cause existing windows to resize, maintaining your layout stability.
Each monitor operates with its own independent window strip, ensuring that windows remain confined to their respective displays and do not "overflow" onto adjacent monitors.
https://github.com/user-attachments/assets/cbc2e820-635f-408b-923a-6cb47c44704c
(Video by @emreekici3 - https://github.com/emreekici3/dotfiles)
https://github.com/user-attachments/assets/793e7eaa-7909-4086-8380-1fb7861f8780
The fundamental architecture and window management techniques are heavily inspired by Yabai, another excellent MacOS window manager. Studying its source code has provided invaluable insights into managing windows on MacOS, particularly regarding undocumented functions.
The innovative concept of managing windows on a sliding strip is directly inspired by Niri and PaperWM.spoon.
Like all non-native window managers for MacOS, Paneru requires accessibility access to move windows. Once it runs you may get a dialog window asking for permissions. Otherwise check the setting in System Settings under "Privacy & Security -> Accessibility".
Check your System Settings for "Displays have separate spaces" option. It should be enabled - this allows Paneru to manage the workspaces independently.
Multiple displays. Paneru is moving the windows off-screen, hiding them
to the left or right. If you have multiple displays, for example your laptop
open when docked to an external monitor you may experience weird behavior.
The issue is that when MacOS notices a window being moved too far off-screen
it will relocate it to a different display - which confuses Paneru! The
solution is to change the spatial arrangement of your additional display -
instead of having it to the left or right, move it above or below your main
display.
A similar situation
exists with Aerospace window manager.
An option exists (horizontal_mouse_warp) which can make a vertical
arrangement of displays "feel" horizontal.
Off-screen window slivers. Because macOS will forcibly relocate windows
that are moved fully off-screen, Paneru keeps a thin sliver of each
off-screen window visible at the screen edge. The sliver_width and
sliver_height options control the size of this sliver. This is a
workaround for a macOS limitation, not a design choice.
Paneru is built using Rust's cargo. It can be installed directly from
crates.io or if you need the latest version, by fetching the source from Github.
$ cargo install paneru
$ git clone https://github.com/karinushka/paneru.git
$ cd paneru
$ cargo build --release
$ cargo install --path .
It can run directly from the command line or as a service. Note that you will need to grant accessibility privileges to the binary.
If you are using Homebrew, you can install from the formula with:
$ brew install paneru
Or by first adding the tap and then installing by name:
$ brew tap karinushka/paneru
$ brew install paneru
See nix/README.md.
Paneru checks for configuration in following locations:
$HOME/.paneru$HOME/.paneru.toml$XDG_CONFIG_HOME/paneru/paneru.tomlAdditionally it allows overriding the location with $PANERU_CONFIG environment variable.
If none of these files exists, Paneru creates
$XDG_CONFIG_HOME/paneru/paneru.toml with the built-in defaults on first launch.
A Lua script ($XDG_CONFIG_HOME/paneru/init.lua, $HOME/.paneru.lua, or
$PANERU_LUA) replaces the TOML rather than layering on top of it: when one
exists, no paneru.toml is read, created, or watched.
You can use the following basic configuration as a starting point. For a complete guide to all available options, keybindings, and window rules, see the Configuration Guide.
# basic .paneru.toml
[options]
focus_follows_mouse = true
mouse_follows_focus = true
[bindings]
window_focus_west = "cmd - h"
window_focus_east = "cmd - l"
window_resize = "alt - r"
window_center = "alt - c"
quit = "ctrl + alt - q"
Alternatively, the embedded Lua runtime can declare the entire configuration
via paneru.setup{...}, making the TOML file optional — see the
Lua Scripting Guide:
-- init.lua
paneru.setup {
options = { focus_follows_mouse = true, mouse_follows_focus = true },
bindings = {
["window focus west"] = "cmd - h",
["window focus east"] = "cmd - l",
["quit"] = "ctrl + alt - q",
},
}
Changes made to the active configuration file are automatically reloaded while Paneru is running. This is useful for tweaking keyboard bindings and other settings without restarting the application.
Paneru saves managed window layout state to the user state directory
($XDG_STATE_HOME/paneru/state.json, usually
~/.local/state/paneru/state.json) and loads it when Paneru starts. During the
startup restore window, Paneru matches reopened windows to the saved session and
restores their layout placement, virtual workspace row, and display assignment
where possible.
Restore is startup-only. After the configured startup grace period expires, new
or unmatched windows follow the normal configuration and window-rule behavior.
Saved windows that are not present are ignored by default and the restored
layout is compacted around the windows that were found. The behavior is
configured with [restore]; see the
Session Restore section in the
configuration guide.
$ paneru install
$ paneru start
To start Paneru from Spotlight, Alfred, Raycast, or another application launcher, install the lightweight app wrapper:
$ paneru install-app
This creates $HOME/Applications/Paneru.app. Opening the app starts the
installed Paneru launch agent and exits immediately. Remove the wrapper with:
$ paneru uninstall-app
$ paneru
Paneru exposes a send-cmd subcommand that lets you control the running
instance from the command line over a Mach service
(com.github.karinushka.paneru). Any
command that can be bound to a hotkey can also be sent programmatically:
$ paneru send-cmd <command> [args...]
| Command | Description |
|---|---|
window focus <direction|number|managed|unmanaged> | Move focus by direction, column number, managed or unmanaged |
window swap <direction> | Swap the focused window with a neighbour |
window center | Center the focused window on screen |
window resize | Cycle through preset_column_widths |
window grow | Grow to the next preset width |
window shrink | Shrink to the previous preset width |
window vertical resize | Cycle a stacked window through preset_stack_heights |
window vertical grow | Grow a stacked window to the next preset height |
window vertical shrink | Shrink a stacked window to the previous preset height |
window fullwidth | Toggle full-width mode for the focused window |
window manage | Toggle managed/floating state |
window equalize | Distribute equal heights in the focused stack |
window balance | Make all columns match the focused window width |
window stack | Stack the focused window onto its left neighbour |
window unstack | Unstack the focused window into its own column |
window nextdisplay | Move the focused window to the next display |
window nextdisplaysend | Move the window to the next display but stay here |
window virtual <dir> | Switch to the previous/next virtual workspace |
window virtualnum <n> | Switch directly to numbered virtual workspace |
window virtualmove <dir> | Move the window to a different virtual workspace |
window virtualmovenum <n> | Move the window to numbered virtual workspace and follow it |
window virtualsend <dir> | Send the window to a virtual workspace but stay |
window virtualsendnum <n> | Send the window to numbered virtual workspace but stay |
window snap | Snap the focused window into the visible viewport |
mouse nextdisplay | Warp the mouse pointer to the next display |
printstate | Print the internal ECS state to the debug log |
quit | Quit Paneru |
restart | Restart the Paneru service |
Where <direction> is one of: west, east, north, south, first, last.
Window numbers are 1-based and count columns from left to right.
# Move focus one window to the right.
$ paneru send-cmd window focus east
# Swap the current window to the left.
$ paneru send-cmd window swap west
# Center and resize in one shot (two separate calls).
$ paneru send-cmd window center && paneru send-cmd window resize
# Balance all columns to the focused window's width.
$ paneru send-cmd window balance
# Cycle backward through preset widths.
$ paneru send-cmd window shrink
# Grow the focused window's height inside its stack.
$ paneru send-cmd window vertical grow
# Jump to the left-most window.
$ paneru send-cmd window focus first
# Jump to the second window from the left.
$ paneru send-cmd window focus 2
# Switch directly to virtual workspace 3.
$ paneru send-cmd window virtualnum 3
# Send the focused window to virtual workspace 3 without following it.
$ paneru send-cmd window virtualsendnum 3
Paneru also exposes structured JSON state for scripts and status bars:
$ paneru query state --json
$ paneru query virtual-workspaces --json
$ paneru query active --json
$ paneru subscribe --json
query prints a JSON snapshot and exits. subscribe --json keeps the channel
open and emits line-delimited JSON events for changes that integrations usually
care about, including focus changes, virtual workspace changes, window-list
changes, title changes, and display changes. See
QUERY_AND_SUBSCRIBE_FORMAT.md for the
full payload contract.
Because send-cmd talks to the running daemon, you can drive Paneru from shell
scripts, cron jobs, or other automation tools:
open -a Safari && sleep 0.5 && paneru send-cmd window resize.paneru send-cmd window balance to make every
column the same width as the focused window — great for resetting layouts
after unplugging a monitor or when windows get shuffled.paneru send-cmd for
compound actions that go beyond a single hotkey.paneru send-cmd window nextdisplay && paneru send-cmd mouse nextdisplay
paneru query state --json to render the
initial workspace labels, then keep them current with paneru subscribe --json.paneru.setup), event hooks (paneru.on),
keybindings (paneru.bind), and state queries — see the Lua Scripting Guide.There is a public Matrix room
#paneru:matrix.org. Join and
ask any questions.
For a detailed high-level overview of Paneru's internal design, data flow, and ECS patterns, please refer to the Architecture Guide.
Paneru's architecture is built around the Bevy ECS (Entity Component System), which manages the window manager's state as a collection of entities (displays, workspaces, applications, and windows) and components.
The system is decoupled into three primary layers:
src/platform/): Directly interfaces with macOS via objc2 and Core Graphics. It runs the native Cocoa event loop and pumps OS events into a channel consumed by Bevy.src/manager/): Defines OS-agnostic traits (WindowManagerApi, WindowApi) that abstract window manipulation. The macOS-specific implementations (WindowManagerOS, WindowOS) bridge these traits to the Accessibility and SkyLight APIs.src/ecs/): The "brain" of the application. Bevy systems process incoming events, handle input triggers, and manage animations.main branch: Contains the stable, released code.testing branch: Used for experimental features and architectural refactors. This branch is volatile and may be force-pushed.Here are some other projects which implement a similar workflow:
(top 30 of 56)
Rust
98.1%
Nix
1.8%