A tiny virtual workspace manager for macOS
Inspired by 2bwm and wmutils,
and by the technical approach of AeroSpace.
OttoWM is a window manager for macOS under intense development.
Some important foundations:
- No dependency on third-party libraries or frameworks
- Relies on macOS public APIs only (up until now)
- Backwards compatibility, it works on macOS Big Sur onwards
It brings the workflow of a common Linux window manager to macOS, without fighting with Mission Control:
- Native macOS Spaces keep working, whatever happens there won't affect OttoWM
- Full screen apps are restored to their respective state when leaving full screen
- Reaching a window through Cmd-Tab, the Dock or Mission Control switches to its workspace
Multiple workspaces on a single native macOS Space: no Space switch animation, no Mission Control. A window that leaves a workspace is parked in the bottom right corner, a point of it left on screen, and comes back to the frame it had, with the focus it had.
OttoWM relies on the same strategy as AeroSpace, a tiny portion of windows accumulates in the bottom right corner, so the Pager covers this spot while offering nice other features.
Option-click the tab to open the About window.
Native tab groups (Terminal, Ghostty, Finder, …) count as one window: sending a tab to another workspace takes the whole group, and the group comes back with the tab that was active.
Download the latest OttoWM-<version>.zip from Releases:
curl -fsSL "$(curl -fsSL https://api.github.com/repos/brennovich/ottowm/releases/latest | grep -o 'https://[^"]*\.zip')" -o OttoWM.zip
unzip OttoWM.zip -d /Applications
xattr -cr /Applications/OttoWM.app
open /Applications/OttoWM.appThe app is ad-hoc signed, so Gatekeeper refuses it as coming from an unidentified developer until you clear the quarantine attribute. OttoWM needs Accessibility permission; grant it in System Settings → Privacy & Security → Accessibility on first launch.
OttoWM expects a few macOS settings to be in place. They all live in System Settings → Desktop & Dock, under Mission Control and Dock.
| Setting | Value | Why |
|---|---|---|
| Group windows by application | on | Fixes the positioning of windows on Mission Control |
| Displays have separate Spaces | on | Each display keeps its own Space, so a workspace switch stays on one display |
| Automatically rearrange Spaces based on most recent use | off | The Space order stays fixed, so the Space OttoWM runs on does not move |
| Automatically hide and show the Dock | on | Keep the parked windows as hidden as possible |
defaults write com.apple.dock expose-group-apps -bool true
defaults write com.apple.spaces spans-displays -bool false
defaults write com.apple.dock mru-spaces -bool false
defaults write com.apple.dock autohide -bool true
killall Dockspans-displays only takes effect after a log out.
To restore the macOS defaults:
defaults delete com.apple.dock expose-group-apps
defaults delete com.apple.spaces spans-displays
defaults delete com.apple.dock mru-spaces
defaults delete com.apple.dock autohide
killall DockYou can define your own bindings by creating a ~/.config/ottowm/ottowm, it's a good idea to start from the default:
mkdir -p ~/.config/ottowm
cp /Applications/OttoWM.app/Contents/Resources/ottowm ~/.config/ottowm/# This is a comment
# One `key combo = action`
lopt-1 = switch-to-workspace 1
# or `setting = value` per line
spacing = 20
lopt-shift-1 = move-window-to-workspace 1
lopt-h = focus west
lopt-shift-h = move-window west
# CMD+Ctrl+Option+Shift+Q quits OttoWM
hyper-5 = switch-to-workspace 5
| Entry | Type | Default | Description |
|---|---|---|---|
switch-to-workspace N |
Action | ⌥ + 1–4 | Switch to workspace N |
move-window-to-workspace N |
Action | ⌥⇧ + 1–4 | Move the focused window to workspace N |
focus D |
Action | ⌥ + H/J/K/L | Focus the window D leads to: north, east, south or west |
move-window D |
Action | ⌥⇧ + H/J/K/L | Move the focused window D by the spacing |
resize C |
Action | ⌥⌃⇧ + H/J/K/L | Resize the focused window by the spacing: C is wider, narrower, taller or shorter |
| center-window | Action | ⌥⌃ + C | Center the focused window on the screen, keeping its size |
| maximize | Action | ⌥⌃ + M | Fill the screen with the focused window (toggable) |
tile D |
Action | ⌥⌃ + H/J/K/L | Tile the focused window to the half of the screen D leads to (toggable) |
| quit | Action | ⌘⌃⌥⇧ + Q | Quit OttoWM, putting every parked window back |
| restart | Action | ⌘⌃⌥⇧ + R | Read the config file again and rebind the keys |
| about | Action | ⌘⌃⌥⇧ + A | Open the About window: version, Accessibility and hotkeys status, the config in use and the macOS settings above (toggable) |
pager = off |
Setting | on |
Hide the tab in the bottom right corner that shows the current workspace and covers the parked windows |
spacing = N |
Setting | 15 |
Number of points for the gap, the move-window step and the resize change |
Note: ⌘ Command, ⌃ Control, ⌥ Option, ⇧ Shift. By default only the left Option (lopt) key triggers the default workspace bindings; the right one is left free for typing special characters™. You can always rebind to use both with opt instead.
The About window (hyper-a, or Option-click on the pager tab) displays the all requirements and status statuses of the system, alongside with config and debug tools.
log stream --level debug --predicate 'subsystem == "com.github.brennovich.ottowm"'- No support for two displays at once (yet). But position and windows size are preserved per display
Note: I have been a software developer for a long time, but this project was built with the help of AI tools, as an opportunity to learn Swift and macOS development.



