Shaka

New in 1.1.0Reef mode 👊 — Hyprland-style tiling

Float or tile. One key between them.

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. / swaps between them, and coming back puts every window exactly where it was.

$curl -fsSL https://raw.githubusercontent.com/wes/shaka/main/install.sh | bash

Free · MIT · macOS 13+ · Xcode Command Line Tools

FinderFileEditViewGoWindowHelp🤙100%Thu 9:41
Safari
‹ ›
shaka — swift build

~/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

Notes
Board meeting
Groceries
Shaka — todo
Rincon trip
Music
▶︎
Click the screen, then use the arrow keys — or the shortcuts below
⌃ / switches
Focus
+ arrows
Nearest window that way
Move
⌃⌥ + arrows
Nudge 80 points
Resize
⌃⇧ + arrows
80 points, from the centre
Snap
⌃⌘ + arrows
½, then ⅓, then ⅔
waiting for a key

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

Same keys. Opposite philosophies.

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.

🤙

Flow

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.

👊

Reef

Dwindle tiling

Every window becomes a tile in a binary tree that fills the screen. Nothing overlaps, everything is reachable, and one keystroke lifts it all back off.

One column of keys, two columns of meaning.

⌃⌥ nudges a window in Flow and swaps two tiles in Reef. Nothing to relearn when you switch — the verb changes, the finger does not.

Reef is borrowed, not invented.

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.

It is about 2,400 lines of Swift.

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 👊

A tiling manager you can put back down.

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.

A
01

One window fills the screen.

A
B
02

A is wider than tall, so it splits side by side.

A
B
C
03

B is now taller than wide, so C stacks under it.

A
B
C
D
04

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.

Entering records everything

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.

The tree is per display

Each screen gets its own dwindle tree filling its visible frame. Run out of tiles pressing a direction and focus carries on onto the next display.

It reconciles, it does not poll

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.

Leaving puts it all back

Every saved frame is handed back to its window on the same spring, so Reef is something you drop onto the screen and lift off again — not something you commit to.

Three keys Flow does not have

Reef adds the actions a tiler needs and a floating manager has no use for. 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.

toggle_splitFlip this tile's split axis⌃⇧ S
toggle_floatFloat it out of the tiling⌃⇧ F
cycle_nextCycle focus through the tiles⌃⌥ ⇥
6

The gaps add up right.

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.

If you are coming from Hyprland

The dispatcher each Reef action is a reading of.

Focus a tilemovefocus

Nearest tile in the direction, scored off the laid-out rects.

Swap tilesmovewindow

The two leaves exchange windows; the tree shape does not move.

Resize the splitresizeactive

Walks up to the nearest split on that axis and nudges its ratio.

Flip the splittogglesplit

Turns a side-by-side pair into a stacked one and back.

Float a windowtogglefloating

The tile leaves the tree and gets Flow behaviour at 60% of the screen.

Fullscreenfullscreen

One tile covers the whole visible frame, gaps and all.

Send to a displaymovewindow mon:<dir>

Removed from one tree and inserted into the next display's.

Motion

A window with weight.

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.

Nothing runs when nothing moves

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.

Reduced motion is respected here

This page's demo checks prefers-reduced-motion and jumps straight to the target instead of springing.

Flow 🤙 · Snapping

Press it again for a third.

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.

First press½
Second press
Third press
½

A half is not half the screen.

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.

Edges are sticky

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.

Resizing works from the centre

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.

Fill is not fullscreen

fills the visible frame minus the padding. No green button, no Space of its own, no animation you have to sit through.

Focus

Arrow keys, but for windows.

⌘-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.

01

Straight ahead beats close by

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.

02

Ten points of slack

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.

03

Only real windows

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.

04

Raise, don't rearrange

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

Twenty-five seconds, no narration.

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.

The interface

A mode picker and a shrug.

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, 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.

Flow / Reef
The mode picker, ticked against the one you are in. Same thing ⌃ / does, for when your hand is already on the mouse.
Shortcuts
Every binding as it is currently configured, described for the mode you are in — the leader is spelled with whatever key you set.
Enabled
Disables the event tap without quitting, handing any tiled windows back first. Your keys go back to whatever they meant before.
Edit Config…
Opens config.toml in TextEdit, because that is a thing every Mac has.
Reload Config
Restores windows, re-reads the file, rebuilds the bindings and the menu. No restart, no relaunch.
Quit Shaka
Untiles, tears the tap down and exits.
🤙Pick a mode — the menu rebuilds, exactly as it does in the app.
Shaka — Flow mode
⌃ / to switch
Shortcuts
Enabled⌘E
Edit Config...⌘,
Reload Config⌘R
Quit Shaka⌘Q
⌃ ←Focus the window to the left
⌃ →Focus the window to the right
⌃ ↑Focus the window above
⌃ ↓Focus the window below
⌃⌥ ←Nudge it left
⌃⌥ →Nudge it right
⌃⌥ ↑Nudge it up
⌃⌥ ↓Nudge it down
⌃⇧ →Wider, from the centre
⌃⇧ ←Narrower, from the centre
⌃⇧ ↑Taller, from the centre
⌃⇧ ↓Shorter, from the centre
⌃⌘ ←Snap to the left ½ → ⅓ → ⅔
⌃⌘ →Snap to the right ½ → ⅓ → ⅔
⌃⌘ ↑Snap to the top ½ → ⅓ → ⅔
⌃⌘ ↓Snap to the bottom ½ → ⅓ → ⅔
⌃ ↩Centre it where it is
⌃⇧ ↩Fill the screen, minus the padding
⌃ /Switch to Reef

Configuration

One file, written for you on first launch.

~/.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 Reef existed picks up all three of its new actions on the next launch instead of quietly losing them.

# Shaka Window Manager Configuration
# Restart Shaka or click "Reload Config" after editing.
#
# Leader key options: "ctrl", "opt" / "alt", "cmd", "shift"
#
# Key names for bindings:
#   Arrows:  left, right, up, down
#   Special: return, space, tab, escape, delete
#   Letters: a-z
#   Numbers: 0-9
#   Combo example: "leader+shift+left"

leader = "ctrl"

# Which mode to start in: "flow" (free-floating) or "reef" (tiling)
default_mode = "flow"

# --- Flow mode ---
move_step = 80
resize_step = 80
edge_snap = 20
screen_padding = 10

# --- Reef mode ---
# gaps_out: space at the screen edge
# gaps_in:  space around each tile, so neighbours sit 2x this apart
gaps_in = 6
gaps_out = 12

# Spring animation parameters (both modes)
animation_stiffness = 300
animation_damping = 28

[bindings]
focus_left    = "leader+left"
focus_right   = "leader+right"
focus_up      = "leader+up"
focus_down    = "leader+down"
move_left     = "leader+opt+left"
move_right    = "leader+opt+right"
move_up       = "leader+opt+up"
move_down     = "leader+opt+down"
grow_width    = "leader+shift+right"
shrink_width  = "leader+shift+left"
grow_height   = "leader+shift+up"
shrink_height = "leader+shift+down"
snap_left     = "leader+cmd+left"
snap_right    = "leader+cmd+right"
snap_up       = "leader+cmd+up"
snap_down     = "leader+cmd+down"
center        = "leader+return"
fill          = "leader+shift+return"
toggle_mode   = "leader+/"
toggle_split  = "leader+shift+s"
toggle_float  = "leader+shift+f"
cycle_next    = "leader+opt+tab"
leaderBoth The modifier every shortcut starts with. "opt", "cmd" and "shift" also work.
default_modeBoth Which mode Shaka starts in. Set it to "reef" and it tiles from launch.
move_stepFlow Points a nudge travels.
resize_stepBoth Points a grow or shrink adds — split either side of the centre in Flow, absorbed by the neighbouring tiles in Reef.
edge_snapFlow How close to an edge a nudge has to land before it sticks to it.
screen_paddingFlow The gap left around a snapped or filled window.
gaps_inReef Space around each tile, so two neighbours sit 2 × this apart.
gaps_outReef Space between the outermost tiles and the edge of the screen.
animation_stiffnessBoth Spring constant. Higher gets there sooner.
animation_dampingBoth Friction. Lower overshoots and wobbles back.

Key names a binding will accept

Arrowsleft · right · up · down
Specialreturn · space · tab · escape · delete
Lettersa – z
Numbers0 – 9
Punctuation- = [ ] ; ' , . / \ `
Modifiersctrl · shift · cmd · opt / alt · leader

Join them with +. The word leader stands in for whatever leader is set to, so rebinding one line changes every shortcut at once.

Reference

All 22 of them, both ways.

The whole surface area of the app, in the order the config file writes it. One column of keys, because there is only one column of keys — the last three are Reef's, and in Flow they are not bound at all.

⌃ ←focus_leftFocus the window to the leftFocus the tile to the left
⌃ →focus_rightFocus the window to the rightFocus the tile to the right
⌃ ↑focus_upFocus the window aboveFocus the tile above
⌃ ↓focus_downFocus the window belowFocus the tile below
⌃⌥ ←move_leftNudge it leftSwap with the tile left
⌃⌥ →move_rightNudge it rightSwap with the tile right
⌃⌥ ↑move_upNudge it upSwap with the tile above
⌃⌥ ↓move_downNudge it downSwap with the tile below
⌃⇧ →grow_widthWider, from the centreWiden the split
⌃⇧ ←shrink_widthNarrower, from the centreNarrow the split
⌃⇧ ↑grow_heightTaller, from the centreHeighten the split
⌃⇧ ↓shrink_heightShorter, from the centreShorten the split
⌃⌘ ←snap_leftSnap to the left ½ → ⅓ → ⅔Send to the display left
⌃⌘ →snap_rightSnap to the right ½ → ⅓ → ⅔Send to the display right
⌃⌘ ↑snap_upSnap to the top ½ → ⅓ → ⅔Send to the display above
⌃⌘ ↓snap_downSnap to the bottom ½ → ⅓ → ⅔Send to the display below
⌃ ↩centerCentre it where it isPromote to the master tile
⌃⇧ ↩fillFill the screen, minus the paddingToggle fullscreen
⌃ /toggle_modeSwitch to ReefSwitch to Flow
⌃⇧ Stoggle_splitunbound — passes through to the appFlip this tile's split axis
⌃⇧ Ftoggle_floatunbound — passes through to the appFloat it out of the tiling
⌃⌥ ⇥cycle_nextunbound — passes through to the appCycle focus through the tiles

How it works

Nine system calls, no daemon.

A keyboard 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. 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.

Hears the keysCGEvent.tapCreate(tap: .cghidEventTap)
Finds your windowAXUIElementCopyAttributeValue(kAXFocusedWindowAttribute)
Moves and sizes itAXUIElementSetAttributeValue(kAXPosition, kAXSize)
Finds the neighboursCGWindowListCopyWindowInfo(.optionOnScreenOnly)
Raises oneAXUIElementPerformAction(kAXRaiseAction)
Watches for new onesAXObserverCreate(pid) + kAXWindowCreatedNotification
Refuses to hangAXUIElementSetMessagingTimeout(systemWide, 0.25)
Knows the displaysNSScreen.screens[…].visibleFrame
Asks permissionAXIsProcessTrustedWithOptions(kAXTrustedCheckOptionPrompt)

Fine print

What it will not do.

Better to read this now than to find out after the install.

It is not signed or notarized

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.

Re-granting Accessibility is a step

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.

⌃ + arrows is already taken

macOS gives those to Mission Control. Turn them off in Keyboard ▸ Keyboard Shortcuts ▸ Mission Control, or set leader = "opt" and never think about it again.

Reef is experimental

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.

Reef does not watch the mouse

Drag or resize a tiled window by hand and the tile keeps the frame it was assigned; the next layout pass puts it back. Reef is driven from the keyboard.

Neither mode does Spaces

Windows move within the display they are on, and each display gets one Reef tree rather than one per Space. Sending a window to another Space is still a Mission Control gesture.

Some windows say no

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.

Flow remembers nothing

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 TOML parser is small

About sixty lines: key = value, [sections], # comments, strings, numbers and booleans. Arrays, tables and multi-line strings are not part of the deal.

There is no window

Shaka is LSUIElement, so there is no dock icon and nothing to ⌘-Tab to. The menu bar item is the entire interface, and it tells you which mode you are in: 🤙 or 👊.

Go move a window. Then tile all of them.

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 | bash

macOS 13+ · Xcode Command Line Tools

01

Run the line

Two or three minutes, most of it Swift compiling. It prints what it is doing, and finishes with the two-mode shortcut table.

02

Launch it

From Spotlight or /Applications. The 🤙 appears in the menu bar; nothing appears in the dock.

03

Tick the box

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.