macotron.window
List the windows of every app, then focus, move, resize, minimize, close, or tile them. Turn on drag-to-edge snapping, and put a saved layout back after a restart.
macotron.keyboard.on("Left half", "ctrl+opt+left", () => {
const win = macotron.window.focused();
if (win) macotron.window.moveToFraction(win.id, { x: 0, y: 0, w: 0.5, h: 1 });
});
Everything here goes through the macOS accessibility API. Declare permissions: ["accessibility"] in macotron.plugin(). Without the permission, lists come back empty and changes return false.
Window objects
getAll() and focused() return objects with these fields:
| Field | Meaning |
|---|---|
id | The handle to pass to every other call. |
title | The window title. It can be empty. |
app | The app's name, such as "Safari". |
bundleID? | The app's bundle id, such as "com.apple.Safari". |
display? | The id of the display that holds the window's center, the same id that macotron.display.list() returns. |
minimized | true while the window is in the Dock. Minimized windows are still listed. |
number? | The window server's own number. It stays the same for the life of the window. Use it to tell windows apart over time; the other calls still take id. Host API 1.5 and newer. |
frame | { x, y, width, height } in screen points, with the origin at the top-left of the main display and y going down. |
An id is built from the app's process and the window's place in the app's window list. It can change when another window of the app opens, closes, or comes to the front, and it changes after a restart. Look the window up again right before you act on it. To recognize a window later, keep its app, bundleID, and title, as restore() does.
Finding windows
getAll(): Window[]
Every window of every regular app, including minimized windows. Menu bar apps and background agents are left out.
focused(): Window | null
The window you are working in. Macotron's own windows are skipped, so a command run from the launcher gets the window behind it. Returns null when no app window is in front.
Changing windows
Each call returns false when the id no longer matches a window.
focus(id: number): boolean
Unminimizes the window, raises it, and activates its app.
move(id: number, frame: { x?, y?, width?, height? }): boolean
Sets the position, the size, or both, in top-left screen points. Fields you leave out keep their value. Returns true if the app accepted the position or the size.
moveToFraction(id: number, frac: { x?, y?, w?, h?, display?, gap? }): boolean
Places the window by fractions of a display's usable area, which excludes the menu bar and the Dock. { x: 0, y: 0, w: 0.5, h: 1 } is the left half; y: 0 is the top. Fields you leave out keep the window's current value.
display is an id from macotron.display.list(); without it, the window stays on its current display. gap keeps that many points between the window and each edge of its tile. The default is 0.
Returns false if the window ended up more than 8 points away from the size you asked for. That usually means the app has a minimum size larger than the tile.
minimize(id: number, on?: boolean): boolean
Minimizes the window to the Dock. Pass false to bring it back. The default is true.
close(id: number): boolean
Presses the window's close button. The app may still ask to save first.
setFullscreen(id: number, on: boolean): boolean
Enters or leaves full screen, as the green button does.
flash(id?: number): boolean
Draws an outline around the window for half a second, to show where focus went. With no id, it uses the focused window. Returns false when there is no such window.
previewFraction(frac: { x?, y?, w?, h?, display?, gap? } | null): boolean
Shows a translucent overlay where moveToFraction would put a window, without moving anything. Missing fields default to the whole screen: x: 0, y: 0, w: 1, h: 1. Without display, it uses the focused window's display, or the display under the pointer. Pass null to hide the overlay.
Snapping
Snapping tiles a window when you drag it to an edge or a corner of the screen, as on Windows. An overlay shows the destination while you drag, and the window moves when you let go. It only reacts to a real window drag. Settings last until plugins reload.
snap(opts: boolean | object): boolean
Turns snapping on with the given settings. snap(true) uses the defaults, and snap(false) turns it off.
| Option | Default | Meaning |
|---|---|---|
enabled | true | Turn snapping on or off. |
threshold | 20 | How close to an edge, in points, the pointer must be. |
corner | 80 | How close to a corner, in points, counts as the corner. Corners win over edges. |
gap | 0 | Points between a snapped window and its tile edges. |
zones | See below | A map from slot to a fraction { x, y, w, h }, as in moveToFraction. It replaces the whole default map, so a slot you leave out is turned off. |
modifiers | None | A map from keys such as "shift" or "cmd+shift" to another zone map. The map is used while those keys are held. If several match, the one with the most keys wins. |
The slots are left, right, top, bottom, tl, tr, bl, and br. The corner slots also accept top-left, topleft, and nw (and so on for the others), and top accepts maximize and full. By default, left and right are halves, top fills the screen, bottom is the lower half, and the corners are quarters.
macotron.window.snap({
gap: 8,
modifiers: {
shift: {
left: { x: 0, y: 0, w: 1 / 3, h: 1 },
top: { x: 1 / 3, y: 0, w: 1 / 3, h: 1 },
right: { x: 2 / 3, y: 0, w: 1 / 3, h: 1 },
},
},
});
setSnapEnabled(enabled: boolean): boolean
Turns snapping on or off and keeps the other settings. Returns false when the mouse tap cannot be installed, which usually means Macotron lacks Accessibility permission.
isSnapEnabled(): boolean
Whether snapping is on.
Saving and restoring layouts
restore(entries): { restored: number, missing: number }
Moves windows back to saved frames. Each entry has app, and optionally bundleID, title, and display, plus a frame as in move(). Objects from getAll() work as entries, so you can save them to localStorage as they are.
An entry matches by bundle id when both sides have one, and by app name otherwise. Within the app, it prefers an exact title, then a window whose title starts with the saved title, then the app's first window. Each window is used once. The result counts the entries that moved a window and the entries with no window to move.
display is accepted but not used. Frames are absolute screen points, so a layout saved with an external display attached lands in the same place only when that display is in the same position.
macotron.command("Save layout", "", () => {
localStorage.setItem("layout", JSON.stringify(macotron.window.getAll()));
});
macotron.command("Restore layout", "", () => {
const saved = JSON.parse(localStorage.getItem("layout") ?? "[]");
const { restored, missing } = macotron.window.restore(saved);
macotron.notify.toast("Layout", `${restored} moved, ${missing} missing`);
});
Events
Listen with macotron.on. Both events pass { id, title, app }.
window:created
A window opened.
window:focused
An app's focused window changed.
See also
- macotron.display for display ids and frames.
- macotron.spaces to move a window to another desktop.
- macotron.app to launch and switch apps.
- The
layouts.jsexample plugin.