New in 1.2.0Workspaces, mouse tiling and a cheat sheet
Shaka is a hand in your menu bar and a handful of keys under your fingers. Flow nudges windows around on a spring and enforces nothing. Reef packs every one of them into a tiling layout with no overlap, across nine workspaces per display. ⌃ / swaps between them, and coming back puts every window exactly where it was.
$curl -fsSL https://raw.githubusercontent.com/wes/shaka/main/install.sh | bashFree · MIT · macOS 13+ · Xcode Command Line Tools
~/Sites/shaka swift build -c release
[9/9] Compiling Shaka WindowManager.swift
Build complete! (12.84s)
🤙 Shaka Window Manager
─────────────────────────
ctrl + arrows focus
ctrl + opt + arrows move
ctrl + cmd + arrows snap
~/Sites/shaka ▌
That desktop is drawn, not screenshotted, and the windows are not on CSS transitions. Every move, resize and snap runs Shaka's own geometry, and switching it to Reef runs Shaka's own dwindle tree — the same recursion, the same gaps — integrated by Shaka's own spring at the same fixed 1/120 step the app uses. The overshoot you feel here is the overshoot you get at animation_damping = 28.
Two modes
Most people want a tiler right up until they want to drag a window somewhere silly. Shaka stopped arguing about it and shipped both, on one set of bindings, with a single keystroke in between — and the menu bar tells you which one you are in from across the room.
Free-floating
Windows go where you put them. Nudge, resize, centre, fill and snap, on a spring, with nothing enforced and nothing to lay out.
Dwindle tiling, nine workspaces
Every window becomes a tile in a binary tree that fills the screen. Nothing overlaps, nine workspaces per display hold nine of those trees, and one keystroke lifts it all back off.
⌃⌥ ← nudges a window in Flow and swaps two tiles in Reef. Nothing to relearn when you switch — the verb changes, the finger does not.
It is Hyprland's dwindle layout, down to the names: togglesplit, resizeactive, gaps_in and gaps_out. If you have used one on Linux, you already know this one.
Cocoa, the Accessibility API, a binary tree and a spring, with no third-party packages anywhere in Package.swift. You build it yourself, which means you can read all of it first.
Reef 👊
Entering Reef takes every tileable window on every display, writes down where it was, and packs it into a binary tree that fills the screen exactly. Each new window splits the focused tile in half along that tile's long axis, so the layout spirals inward the way dwindle does and the halves stay near-square instead of degenerating into columns.
Press ⌃ / again and every window goes back to the frame Reef found it in, on the same spring. That is the whole pitch: the structure is something you drop onto your screen for an afternoon and lift off again, not something you commit your desktop to.
One window fills the screen.
A is wider than tall, so it splits side by side.
B is now taller than wide, so C stacks under it.
C is wide again — D takes its right half.
Nobody chose those four rectangles. Each one is what host.rect.width >= host.rect.height decided when the window opened, which is why the shape is predictable even though you never picked a layout.
Every tileable window on every display has its frame written down before a single tile is laid, which is what makes leaving lossless rather than approximate.
Each screen gets nine dwindle trees and shows one of them. Run out of tiles pressing a direction and focus carries on onto the next display.
An Accessibility observer per app watches windows appear, close, minimise and come back; a five-second timer covers apps that report nothing. Bursts are coalesced into one diff.
Every saved frame is handed back to its window on the same spring, parked workspaces included, so Reef is something you drop onto the screen and lift off again — not something you commit to.
Reef adds the actions a tiler needs and a floating manager has no use for — these, plus the eighteen number keys the workspaces sit on. In Flow their bindings are left unbound rather than made inert — the tap passes the keystroke through to whatever app you are in, so ⌃⇧S stays yours until the moment you want it, and macOS keeps ⌃1 for its own Spaces.
gaps_out = 12 at the screen edge and gaps_in = 6 around each tile, so two neighbours sit 12 points apart — the same arithmetic Hyprland does. Inset the screen by gaps_out − gaps_in before splitting, inset each leaf by gaps_in after, and the outer margin and the gutters come out equal without a special case.
The dispatcher each Reef action is a reading of.
movefocusNearest tile in the direction, scored off the laid-out rects.
movewindowThe two leaves exchange windows; the tree shape does not move.
resizeactiveMoves the tile's trailing edge, or its leading one when that edge is the screen border.
togglesplitTurns a side-by-side pair into a stacked one and back.
togglefloatingThe tile leaves the tree and gets Flow behaviour at 60% of the screen.
fullscreenOne tile covers the whole visible frame, gaps and all.
movewindow mon:<dir>Removed from one tree and inserted into the next display's.
workspace NNine per display, each a full dwindle tree. The one you left is parked, not closed.
movetoworkspacesilentThe window goes; the screen you are looking at does not change.
workspace previousOne key bounces between the two you are actually using.
bindm movewindowModifier + left-drag. Tiles trade places as the cursor crosses them.
bindm resizewindowModifier + right-drag, from whichever corner the cursor started nearest.
Reef 👊 · Workspaces
Every display gets 9 workspaces, and each one is a full dwindle tree of its own — its own windows, its own splits, its own ratios. ⌃ 1 – 9 brings one to the screen. Add ⇧ and the focused window goes there without you following it.
These are Shaka's, not macOS Spaces. They live inside Reef, they leave Mission Control alone, and their keys are unbound in Flow — so if you have macOS's own Switch to Desktop N on ⌃1–9, Flow still hands those to macOS and Reef takes them for itself.
The same list the menu bar shows under Workspaces, with how full each one is; picking one there switches to it exactly as its shortcut would. The status item itself reads 👊 {n} — the one thing about the layout you cannot tell by looking at the windows in front of you.
Each workspace is a full dwindle tree with its own splits and ratios. Switching to one lays it out again from the tree, so a workspace you left an hour ago comes back the shape you left it.
Windows on a hidden workspace are slid below the display and kept at full size, so returning springs them back up into their tiles. Nothing is minimised and nothing is quit — set workspace_hide = "minimize" for apps that refuse to sit off screen.
⌃⇧ + a number hands the focused window to another workspace and leaves you where you are. ⌃ + ` `` bounces back to the one you came from, which is the pair of keys you actually wear out.
Reach a window parked on another workspace from the app switcher and Shaka switches to that workspace rather than leaving you on a screen that did not change.
Reef 👊 · The mouse
Every edge of a tile that is not the screen border is the split line of exactly one node in the tree, so resizing is never approximate: move that edge, the tiles either side of it reflow, and everything else stays where it is. Three gestures reach the same machinery, and so does ⌃⇧ ←.
The last one is the interesting one. Grab a tiled window's own border and Shaka does not intercept the drag at all — the app resizes itself the way it always has, Shaka reads the frame it lands on, and folds the difference back into the split ratios. The window keeps the size you gave it because the layout changed to agree with it.
Hyprland's bindm movewindow. Tiles trade places as the cursor crosses them, so what is on screen is a valid tiling at every moment of the drag rather than only at the end.
The two edges nearest where the drag started ride along with the cursor, each one moving the single split that draws it. Shaka eats the event, so no app underneath ever sees it.
Here Shaka touches nothing: the app resizes itself the way it always does, and Shaka reads the frame it lands on and folds the difference back into the split ratios. The window keeps your size because the layout changed to agree with it.
Drag a tiled window by its title bar onto a neighbour and the two swap places — the same operation ⌃⌥ + an arrow does, for when your hand is already on the mouse.
A bare ⌃ + click is macOS's right-click, and swallowing it would cost you context menus on every tiled window. Set mouse_modifier = "leader" for the one-modifier Hyprland feel, or "" to turn the gesture off.
No drag can leave a tile smaller than min_tile_size — 120 points by default — whichever of the three you are using, and the clamp is a real size rather than a fixed fraction of the split.
A trackpad emits well over a hundred events a second and every one would otherwise mean an Accessibility write per neighbour. A watched window's frame is read at 25Hz and new frames go out at most every 16ms.
Motion
Most window managers teleport. Shaka runs a damped spring on all four numbers of the frame at once — origin and size, on a timer at 8ms — so a window leans into the move, arrives, and settles. It is the difference between a window being repositioned and a window going somewhere.
Press a direction twice quickly and it does not stutter: the second press retargets the spring in flight, keeping the velocity it already had, so a held key glides instead of stepping.
Both modes share one animator, which is what makes switching feel like a decision rather than a redraw: every tile springs into place at once, each with its own velocity, off a single timer that drops to 16ms once more than two windows are moving so the Accessibility round trips have room to land.
Click anywhere on the strip to send the window there. Those two lines go in config.toml exactly as they read.
vel += (k·(target − cur) − c·vel)·dt
cur += vel·dt
Eight lines of physics, run over x, y, width and height. When the remaining distance and the remaining speed both fall under half a point, the window is set exactly on target and the timer stops.
The animation timer is created for a move and cancelled the moment the last window settles. Between keystrokes Flow is an event tap and a menu, and that is all.
This page's demo checks prefers-reduced-motion and jumps straight to the target instead of springing.
Flow 🤙 · Snapping
⌃ ⌘ ← puts the window on the left half. Press it again and it is a third; again and it is two thirds; again and you are back to half. Four common layouts live on one key, and changing direction starts the cycle over. In Reef the same key means something else entirely — it hands the tile to the next display.
It is fraction × (width − padding) − padding — the padding around the outside is spoken for first, and the gutter down the middle comes out of the two halves. Snap one window left and another right and the gap between them is the same 10 points as the gap around them, which is the only version of a half that looks right.
A nudge that lands within 20 points of an edge gives up the difference and sits at the padding instead — so windows line up without you aiming.
Grow a window and it spreads both ways rather than walking off to the right. It stops shrinking at 200 points, whichever key you are leaning on.
⌃ ⇧ ↩ fills the visible frame minus the padding. No green button, no Space of its own, no animation you have to sit through.
Focus
⌘-Tab cycles through applications in the order you last used them, which is a different question from the window on the left. Shaka answers the second one: ⌃ ← activates the app that owns the nearest window in that direction and raises it.
Flow reads the on-screen window list in a single call, so it sees every app's windows rather than only its own. Reef does not need to ask: it already has every tile's rectangle, so it scores them straight off the tree — and runs off the edge onto the next display when there is no tile left in that direction.
Distance is scored with the perpendicular axis weighted four times as heavily, so a window directly left wins over a nearer one off up a corner.
A neighbour has to be more than ten points away in the direction you pressed, which keeps two windows sharing an edge from swapping back and forth.
Standard windows, not minimised, whose position and size the app will actually let you set. Sheets, palettes, popovers and fixed-size dialogs are not windows you were trying to reach.
Focusing moves nothing. The owning app is activated and the matching window is raised — the geometry is left exactly as it was.
On a real desk
Everything above is drawn. This is not — it is a screen recording of the app moving real windows around a real Mac. It predates Reef, so what you are watching is Flow: nudging, snapping and filling, at the speed you actually use it.
Both modes · ⌃ K
A tiler with two modes and 45 bindings needs somewhere to look them up that is not a README. ⌃ K puts every shortcut that does something in the mode you are in on screen — built from the live config, so it stays right the moment after you rebind something. Press it again, hit esc, or click it to put it away.
It is a non-activating panel, so it never takes focus from the window you are about to rearrange, and switching modes while it is up swaps it for the other half rather than closing it. The same list lives in the menu bar under Shortcuts, where every row runs its action when you pick it.
👊 Reef mode
⌃/ switches modes · ⌃K or esc closes
Focus
Move
Resize
Snap & Displays
Layout
Workspaces
Shaka
Both halves of that come out of one catalogue in the source — Shortcuts.swift — which is also what writes the commented config file and builds the menu. An action added there shows up in all four places, and cannot show up in three of them with the wrong key.
The interface
Shaka is an LSUIElement, so it has no dock icon and no window of its own. Everything it can be asked to do is in this menu, and most days you will open it to switch modes with the mouse instead of the keyboard, or to turn the thing off for a screenshare and turn it back on afterwards.
The glyph is the status: 🤙 for Flow, 👊 for Reef — with the workspace number beside it in Reef — and 🤙⚠ when Accessibility has been revoked, at which point the menu drops everything except an explanation and a button that opens the right settings pane.
Configuration
~/.config/shaka/config.toml appears the first time Shaka runs, commented, with every default in it and the two modes' settings under their own headings. Change the leader to "opt" and the Mission Control collision disappears; set default_mode = "reef" and it tiles from launch; rebind any action to any key you like; hit Reload Config and it is live.
A binding you leave out falls back to the default rather than disappearing, so a config written before workspaces existed picks up all of their keys on the next launch instead of quietly losing them. Every line carries a comment saying what it does in Flow and then in Reef, which makes the file the same list of actions the menu and the cheat sheet are built from.
# ─────────────────────────────────────────────────────────────────────────────
# Shaka Window Manager — configuration
#
# Every option Shaka understands is listed here with its default. Delete a
# line to go back to that default; anything you leave out keeps working.
# Apply changes with 🤙 → Reload Config, no restart needed.
# ─────────────────────────────────────────────────────────────────────────────
# The modifier every "leader+..." binding below starts from.
# "ctrl" | "opt" (= "alt" / "option") | "cmd" (= "command") | "shift"
leader = "ctrl"
# Which mode Shaka starts in.
# "flow" — free-floating: nudge, resize and snap windows wherever you like
# "reef" — Hyprland-style dwindle tiling: every window is a tile
default_mode = "flow"
# ── Flow mode ────────────────────────────────────────────────────────────────
# How far one nudge moves a window, in points.
move_step = 80
# How much one resize grows or shrinks a window, in points.
resize_step = 80
# A move that lands within this many points of a screen edge sticks to it.
# Set to 0 to turn edge snapping off.
edge_snap = 20
# Margin kept between a snapped or filled window and the screen edge.
screen_padding = 10
# ── Reef mode ────────────────────────────────────────────────────────────────
# gaps_in: space around each tile, so neighbours sit 2x this apart
# gaps_out: space at the screen edge
gaps_in = 6
gaps_out = 12
# Smallest tile a resize may leave behind, in points.
min_tile_size = 120
# Workspaces per display, 1-9. Each one keeps its own tiling.
workspaces = 9
# How windows on a hidden workspace are put away:
# "offscreen" — slid below the display, instant and reversible
# "minimize" — sent to the Dock, for apps that refuse to be moved off screen
workspace_hide = "offscreen"
# ── Reef mouse ───────────────────────────────────────────────────────────────
# Hold this and drag inside a tile: left button moves it, right button resizes.
# Two modifiers by default, because a bare ctrl+click is macOS's right-click —
# set it to "leader" for the one-modifier Hyprland feel, or "" to turn the
# gesture off entirely.
mouse_modifier = "leader+cmd"
# Dragging a tiled window's own border reshapes the tiling around it.
# true | false
mouse_edge_drag = true
# ── Animation (both modes) ───────────────────────────────────────────────────
# Spring the windows travel on. Higher stiffness is snappier; lower damping
# overshoots and bounces. 300 / 28 lands quickly without wobbling.
animation_stiffness = 300
animation_damping = 28
# ── Bindings ─────────────────────────────────────────────────────────────────
#
# Write a combo as modifiers and one key joined by "+", e.g.
# "leader+shift+left" or "ctrl+opt+a". "leader" stands in for the leader key
# set above, so rebinding the leader moves every shortcut at once.
#
# Modifiers: leader, ctrl, opt (alt), cmd, shift
# Keys: left right up down · return space tab escape delete
# a-z · 0-9 · - = [ ] ; ' , . / ` \
#
# The comment after each line says what it does in Flow, then in Reef. Actions
# marked "Reef only" have no Flow equivalent: in Flow their keys are left
# alone, so they reach the focused app instead.
[bindings]
# --- Focus ---
focus_left = "leader+left" # Flow: Focus window left · Reef: Focus tile left
cycle_next = "leader+opt+tab" # Reef only: Cycle focus forward
cycle_prev = "leader+opt+shift+tab" # Reef only: Cycle focus back
# --- Move ---
move_left = "leader+opt+left" # Flow: Nudge window left · Reef: Swap tile left
center = "leader+return" # Flow: Center window · Reef: Promote to master
# --- Resize ---
grow_width = "leader+shift+right" # Flow: Grow width · Reef: Widen split
shrink_width = "leader+shift+left" # Flow: Shrink width · Reef: Narrow split
# --- Snap & Displays ---
snap_left = "leader+cmd+left" # Flow: Snap left ½ ⅓ ⅔ · Reef: Send to display left
# --- Layout ---
fill = "leader+shift+return" # Flow: Fill screen · Reef: Toggle fullscreen
toggle_split = "leader+shift+s" # Reef only: Toggle split direction
toggle_float = "leader+shift+f" # Reef only: Toggle floating
# --- Workspaces ---
workspace_1 = "leader+1" # Reef only: Workspace 1
move_to_workspace_1 = "leader+shift+1" # Reef only: Send to workspace 1
workspace_next = "leader+opt+]" # Reef only: Next workspace
workspace_prev = "leader+opt+[" # Reef only: Previous workspace
workspace_recent = "leader+`" # Reef only: Last workspace
# --- Shaka ---
toggle_mode = "leader+/" # Flow: Switch to Reef 👊 · Reef: Switch to Flow 🤙
show_shortcuts = "leader+k" # Show the shortcut cheat sheet
The file on disk spells out all four directions and all nine workspaces; this listing collapses the repeats. Everything else is what Shaka writes, comments included.
Join them with +. The word leader stands in for whatever leader is set to, so rebinding one line changes every shortcut at once.
Reference
The whole surface area of the app, grouped the way the menu, the cheat sheet and the config file all group it — one catalogue in the source feeds the three of them, and this table. One column of keys, because there is only one column of keys: the rows with nothing in the Flow column are Reef's, and in Flow they are not bound at all. The two numbered runs — nine keys to show a workspace, nine to send a window to one — collapse to a row each, exactly as they do in the menu.
How it works
One event tap at the top of the event stream, the Accessibility API for reading and writing window frames, and the window list for finding neighbours. There is no helper tool, no login item, no launch agent and nothing listening on a port.
The tap sees every keystroke — that is what a tap is — and swallows only the combos in your config, passing everything else straight through. Mouse events come through the same tap, because Reef's drag gestures have to be able to take a click before the app underneath reacts; outside a gesture they are looked at and handed on. If macOS ever disables the tap for taking too long, Shaka notices and switches it back on rather than quietly dying.
Reef adds the one thing Flow never needed: an Accessibility observer on every running app, so a window opening or closing is a notification rather than a poll. The signal is deliberately coarse — individual notifications are unreliable across apps, so any event triggers a full re-scan and diff, coalesced over 120ms, with a five-second timer behind it for apps that post nothing at all. Every AX request is capped at a quarter of a second, so one unresponsive app cannot stall the tap.
On first launch it asks for Accessibility, then polls once a second until you grant it, so the shortcuts start working the moment you tick the box — no relaunch.
CGEvent.tapCreate(tap: .cghidEventTap)eventsOfInterest: .leftMouseDragged | .rightMouseDragged | …AXUIElementCopyAttributeValue(kAXFocusedWindowAttribute)AXUIElementSetAttributeValue(kAXPosition, kAXSize)CGWindowListCopyWindowInfo(.optionOnScreenOnly)AXUIElementPerformAction(kAXRaiseAction)kAXPositionAttribute below the display, or kAXMinimizedAttributeAXObserverCreate(pid) + kAXWindowCreatedNotificationAXUIElementSetMessagingTimeout(systemWide, 0.25)NSScreen.screens[…].visibleFrameAXIsProcessTrustedWithOptions(kAXTrustedCheckOptionPrompt)Fine print
Better to read this now than to find out after the install.
The installer clones the repo and runs swift build -c release on your machine, which is why there is no Gatekeeper dance and why you need the Xcode Command Line Tools.
Shaka is ad-hoc signed, so a rebuild changes its signature and macOS quietly revokes the grant while still showing it ticked. Remove it from the list and add it back, or run tccutil reset Accessibility com.wes.shaka. The menu bar shows 🤙⚠ when this has happened.
macOS gives those to Mission Control, and ⌃ + K is a terminal's kill-to-end-of-line. Turn the first off in Keyboard ▸ Keyboard Shortcuts ▸ Mission Control, rebind the second, or set leader = "opt" and never think about it again.
The dwindle layout is the only one implemented — Hyprland's master layout is not there. It is honest about that rather than half-doing it.
Every edge is one split in the tree, so dragging the outer edge of a tile resizes the whole column it belongs to. Hyprland's dwindle behaves the same way: that split is genuinely shared by everything in the column.
They live entirely inside Reef and leave Mission Control alone, so a parked window still shows up in Mission Control and ⌘-Tab. Reaching it from there switches you to its workspace. Sending a window to a real macOS Space is still a Mission Control gesture.
A window with a minimum size will refuse to shrink past it, and anything that does not talk to the Accessibility API — the odd game, the odd Electron shell — simply will not budge. In Reef, ⌃⇧ F floats those out of the way.
No saved layouts, no per-app rules, no restore on login. You press keys, windows move. Reef is the only structure on offer, and it lasts exactly as long as you leave it on.
The tap takes mouse events as well as keys so a drag can be a layout gesture, and border drags are followed by polling the window's frame at 25 Hz while you hold it. Set mouse_edge_drag = false if you would rather it did not.
About sixty lines: key = value, [sections], # comments, strings, numbers and booleans. Arrays, tables and multi-line strings are not part of the deal.
Shaka is LSUIElement, so there is no dock icon and nothing to ⌘-Tab to. The menu bar item is the entire interface — the glyph is the mode, and the number beside it is the workspace.
The script clones the repo, builds it with Swift Package Manager and puts Shaka.app in /Applications. No tap, no DMG, nothing to drag.
$curl -fsSL https://raw.githubusercontent.com/wes/shaka/main/install.sh | bashmacOS 13+ · Xcode Command Line Tools
Installs
15
since 23 August 2026
There is no binary to download, so this is not a download count — it is how many times the repository has been cloned, which is what the install line does before it builds. The bars are the last fourteen days. GitHub keeps only that fortnight, so the total above is stitched together from every window before it, and the live one could not be read just now.
Two or three minutes, most of it Swift compiling. It prints what it is doing, and finishes with the two-mode shortcut table.
From Spotlight or /Applications. The 🤙 appears in the menu bar; nothing appears in the dock.
System Settings ▸ Privacy & Security ▸ Accessibility ▸ Shaka. The shortcuts start working straight away — no relaunch.
Prefer to look first? git clone the repo and run make install. Updating? Re-grant Accessibility afterwards — the ad-hoc signature changes with every build. Changed your mind? rm -rf /Applications/Shaka.app ~/.config/shaka and remove it from Accessibility.