Window tiling for macOS 14+

Perfect layouts.
First try.

Finestry arranges your windows into exact layouts with the same gap everywhere, remembers where each one goes, and saves them as scenes you bring back with one shortcut.

Download the beta See it in action Free during the beta
TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡
⌥Aopens the HUDGaps you choose, identical everywhereUp to 4 windows per layout

Every pixel put to work. Your windows move like pieces on a magnetic board.

Features

Everything in its place.

TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Auto-tiling

Open a second window and Finestry splits the screen between the two, with no shortcut and no menu. Close one and the other takes the whole screen back.

TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Drag to reorder

Open the HUD and drag any window card, the master included, to a new slot. Every window moves to its new place in one go.

⌥A
TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Magnetic resize

Drag the edge between two windows. When you let go, the neighbours adjust to fill the space and keep the gap exact, never below each app's minimum.

TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

It remembers where everything goes

Minimize a window and the rest fill the space. Bring it back and it returns to the exact slot it left. Finestry remembers your arrangements, even after a restart.

TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Scenes

Save a layout as a named scene, like Development or Meetings. Finestry marks it when something changes and restores it from the menu bar or a shortcut.

⌥S
TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Every display at once

A scene remembers each monitor. Restore it and every display falls into place together, on the right desktop.

TilingEngine.swift — Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import AppKit

// One solver for every path
struct Board {
  let gap: CGFloat = 12
  var frames: [CGRect]
}

func solveLayout(_ windows: [Window],
                in screen: CGRect) -> Board {
  let sizes = probe(windows)
  let board = reconcile(sizes, screen)
  return board.snappedToGrid()
}

// Quantized apps snap to anchor + n × step
func gridSnapDown(_ v: CGFloat,
    anchor: CGFloat, step: CGFloat) -> CGFloat {
  anchor + ((v - anchor) / step).rounded(.down) * step
}
finestry — zsh
~/finestry ❯ swift build
Compiling Finestry TilingEngine.swift
Build complete! (2.31s)
~/finestry ❯ swift test
Executed 150 tests, with 0 failures
~/finestry ❯ ▍
Browser
finestry.app
Notes

Beta checklist

Scenes per monitor
Crash reports
Landing page video
12 px
12 px
12 px
Main display
Scene: Development
Windows to stack
Add, remove, or drag to reorder
4 windows
Select layout
←→navigate↵apply
</>>_◎≡

Set a window aside

Float one window out of the layout with a shortcut and the rest close the gap. Press it again and the window returns to its original slot.

⌥⇧W
And also
  • Your own shortcutsSet shortcuts for every action, and for jumping straight to a saved scene.
  • Desktops and monitorsWorks across different macOS desktops and across different monitors.
  • Unlimited scenesCreate as many scenes as you want.
  • Auto-saveOptionally save every move you make, so you never have to save by hand.
The problem

Other tools guess the size. Apps refuse it.

Terminal snaps to its character grid. Some apps won't shrink below a minimum. Ask for a size an app won't take and you get crooked gaps and overlapping edges.

Finestry measures each app first: its real minimum and the steps it resizes in. Then it plans the whole layout around those numbers, so the first result is already right.

How it works

Measure once. Place once.

01

Probe

One quick probe per window finds the sizes each app really accepts. The result is cached per app and version.

02

Reconcile

Plain arithmetic on the real sizes: snap to each app's grid, fill the screen and keep every gap identical.

03

Apply

A final check covers minimums, screen edges, overlaps and gaps. Then only the windows that change are moved, once.

Who's behind it

Hi, I'm Vicent.

I build Finestry on my own, from Spain, in my spare time. I wanted my windows to line up perfectly every time, so I started making the tool myself.

There is no company and no support team. I read and answer every message myself, so a reply can take a few days.

A note on English: my first language is Spanish and I often use a translator. If something I write sounds a bit odd, that's why. Messages in Spanish are always welcome.

Contact email coming with the beta.

Pricing

Pay once. Keep it.

During the beta, every feature is unlocked.
Free
€0
  • Layouts for up to 4 windows
  • HUD with drag to reorder
  • Arrange windows with ⌥A
  • Magnetic resize
  • One display
Get Finestry
Pro
€3.99one-time
  • Everything in Free
  • Named scenes
  • Scenes across multiple displays
  • Set a window aside
  • Configurable shortcuts
  • Auto-save scenes
Download the beta

Plans and prices may change before launch.

FAQ

Questions, answered.

Which Macs does it run on?

Any Mac running macOS 14 Sonoma or later.

Why does it ask for Accessibility access?

macOS only lets an app move and resize other apps' windows through the Accessibility permission. Finestry needs it to place your windows.

What do crash reports include?

Reports are only sent with your consent. Logs never include window titles, and you can also send a report yourself from the menu.

Is it a subscription?

No. Finestry is a one-time purchase, and it is free while in beta.

Who answers my messages?

I do. Finestry is a one-person project, so please be patient if a reply takes a few days.

Beta

Your windows, in order.

Try the beta and tell me what breaks. Every feature is unlocked while I test it.