A macOS presentation tool for live demos. Instead of slides, Mize arranges your real app windows into named scenes you can page through with arrow keys — keeping the demo interactive (you type into the real terminal, click in the real browser) while hiding everything not part of the current scene.
You define scenes in a JSON file (or via the in-app editor). Each scene is a list of "panes," where each pane points to one of your open app windows and specifies where to put it on screen (full, left half, right half, etc.).
When a scene activates, Mize uses the macOS Accessibility API to move + resize the target windows into the layout, hides all other apps, and covers the desktop wallpaper + menu bar + dock with a curtain. You see only the scene's app windows on a black backdrop. PageDown / Cmd+→ advances to the next scene.
There's no screen capture, no mirroring, no input forwarding. The windows you interact with are the real windows — Mize just arranges them.
- macOS 14 (Sonoma) or later
- Swift 6.0 toolchain (ships with Xcode 16+ / Command Line Tools)
- Accessibility permission (you'll grant this once at first launch)
The on-disk bundle is Mize.app (ASCII) — macOS shows the display name
"Mize" in the Dock and Spotlight via CFBundleDisplayName.
git clone <repo-url> mize && cd mize
./scripts/create-signing-identity.sh # one-time: makes a self-signed cert
./scripts/finalize-identity.sh # one-time: trust + keychain partition (sudo + login pwd)
./scripts/build-app.sh # produces build/Mize.app
cp -R build/Mize.app /Applications/ # optional: install for SpotlightFirst launch will prompt you to grant Accessibility in System Settings → Privacy & Security → Accessibility. Toggle Mize on, then relaunch. Mize verifies on startup and exits with a console message if the permission isn't granted.
Launch via Spotlight (Cmd+Space, type "Mize") or:
open /Applications/Mize.appImportant: don't invoke the binary directly from a terminal
(./Mize.app/Contents/MacOS/Mize). macOS attributes Accessibility permission
to the launching terminal app rather than to Mize when you do that.
| Keys | Action |
|---|---|
PageDown / Cmd+→ |
Next scene |
PageUp / Cmd+← |
Previous scene |
Cmd+E |
Toggle the scene editor panel |
Cmd+, |
Open the active scenes file in your default text editor |
Cmd+O |
Open a different scenes file |
Cmd+Shift+S |
Save the current scenes as a new file |
ESC |
Restore window positions and quit |
Plain arrow keys (without modifiers) and other unbound keys pass through to whatever app is frontmost.
A floating panel with:
- Scene title
- Background color picker (system color panel)
- Multi-line text input + position anchor + size slider for an on-curtain title
- A scrollable list of currently-running windows with checkboxes — check to include in the current scene, with a layout dropdown (Full / Left Half / etc.)
- Add Scene / Delete Scene / Prev / Next buttons
Every edit auto-saves to the active scenes file. You can also drag the actual target windows around in the scene (the OS-native title-bar drag) — Mize captures the new frame on the next scene transition.
Stored at ~/Library/Application Support/Mize/Scenes/Default.json on first
launch. Cmd+O lets you switch between named scene sets in that directory.
Example:
{
"scenes": [
{
"title": "Title slide",
"backgroundColor": "#0c1a4d",
"texts": [
{
"content": "Welcome to Mize",
"x": 864, "y": 558,
"fontSize": 96,
"color": "#ffffff",
"alignment": "center"
}
],
"panes": []
},
{
"title": "Notes + Browser",
"backgroundColor": "#000000",
"panes": [
{
"appNameContains": "Notes",
"titleContains": "My demo notes",
"frame": {"x": 0, "y": 50, "width": 864, "height": 1017}
},
{
"appNameContains": "Brave Browser",
"titleContains": null,
"frame": {"x": 864, "y": 50, "width": 864, "height": 1017}
}
]
}
]
}Hot-reloads when saved externally.
Honest about what doesn't work cleanly:
- Multi-window-per-app disambiguation is fuzzy across sessions. Mize
identifies windows by
CGWindowID(stable within a session) plus an app-name + title fallback (used across sessions). If you have two Slack windows and switch channels in both, the fallback may match the wrong one on next launch. Workaround: stick to one window per app for scenes where precision matters. - System menu bar can show through. When you click into a target app, the active app's menu bar renders above Mize's chrome cover (macOS reserves this layer for the menu bar). Workaround: enable System Settings → Desktop & Dock → "Automatically hide and show the menu bar" → "Always".
- Single display only. Mize only arranges windows on the primary display.
- Slack
Cmd+→/Cmd+←collides with Mize's scene navigation. Inside Slack these jump channels. Use PageDown/PageUp instead, or click into a Mize scene area first. - Not notarized. Built locally with a self-signed identity. Gatekeeper
won't complain because you built it; if you distribute the
.appto others they'll need to right-click → Open the first time. - Restore on crash isn't guaranteed. Mize restores window positions on graceful exit (ESC, Cmd+Q). If it crashes mid-presentation, your windows stay at their scene positions until you manually drag them back.
Three Swift sources do most of the work:
WindowManager.swift— Accessibility (AX) wrapper. Snapshots all visible app windows at startup, exposes move/resize/raise/minimize, restores original positions on exit. Uses the private but ubiquitous_AXUIElementGetWindowto bridge AX elements ↔ CGWindowIDs.PresentationApp.swift— owns the Mize-side NSWindows (main curtain at level.normal - 1, two chrome covers atCGShieldingWindowLevel - 1), the scene activation pipeline, key monitors, and the in-memory scene state.SceneStore.swift— load/save scenes to JSON, watch the file for external edits via DispatchSource, remember the last-used scenes file via UserDefaults.
The editor panel (EditorPanel.swift) is AppKit + NSPanel hosting native
controls. Curtain rendering (CurtainView.swift) uses a layer-backed
NSView so background colour transitions animate via CATransaction.
The original architecture used ScreenCaptureKit to mirror the target windows
onto a composited surface, then forward synthesized input events back to the
real apps. It worked but had three persistent problems:
- Cursor duplication and slight resolution degradation from the capture loop
CGEvent.postToPidfor keyboard input is silently denied by Terminal's Secure Input mode (e.g., duringsudoprompts)- Latency made interactive demos feel unresponsive
Switching to AX-based real window manipulation removes all three at the cost of losing the ability to do multi-window-per-pane compositing (since macOS won't let one app raise another app's window above its own without private APIs). For live demos, that tradeoff is the right one.
PRs welcome. Things that would make this materially better:
- Better cross-session window identification (multi-window apps)
- Optional per-scene "snapshot of app state" so channel switches survive
- Multi-display support
- Crash-safe state restoration (write snapshot to disk; restore on next launch)
- Unit / integration tests beyond
SceneStoreTests
MIT — see LICENSE.