Installation
Homebrew installation (Preferred)
Homebrew is a package manager for macOSManual installation
- Download the latest available zip from the FlightDeck releases page
- Unpack zip
- Put unpacked
FlightDeck-v$VERSION/FlightDeck.appto/Applications - Put unpacked
FlightDeck-v$VERSION/bin/flightdeckanywhere to$PATH(The step is optional. It is only needed if you want to be able to interact with FlightDeck from CLI)
Configuring FlightDeck
Custom config location
FlightDeck tries to find the custom config in four locations, in this order:~/.flightdeck.toml${XDG_CONFIG_HOME}/flightdeck/flightdeck.toml~/.aerospace.toml${XDG_CONFIG_HOME}/aerospace/aerospace.toml(environment variableXDG_CONFIG_HOMEfallbacks to~/.configif the variable is not presented)
flightdeck.toml always takes precedence over an aerospace.toml: if any FlightDeck config exists,
the AeroSpace locations are ignored. If two configs are found within the same tier (for example both
~/.flightdeck.toml and ${XDG_CONFIG_HOME}/flightdeck/flightdeck.toml) then the ambiguity is reported.
The AeroSpace-compatible paths are intentionally retained so existing configurations continue to work unchanged.
Commands inside the TOML config also remain unchanged.
Only terminal invocations use the flightdeck executable instead of aerospace.
FlightDeck does not install an aerospace CLI alias, and FlightDeck and AeroSpace should not run simultaneously.
Config samples
Please see the following config samples:- The default config
- i3 like config
- https://github.com/search?q=path%3A*aerospace.toml&type=code[Search for configs by other users on GitHub] for inspiration
Default config
The default config is part of the documentation, it contains all trivial configuration keys with comments. Please read the default config! Non-trivial configuration options are mentioned further in this guide. If no custom config is found, FlightDeck will load the default config. If the key is omitted in the custom config, it falls back to the value in the default config, unless it’s stated otherwise for the specific keys. Namely:mode.*.binding. It falls back to the empty TOML table. Your config is the source of truth for keyboard bindings. You must explicitly mention all the keyboard bindings and binding modes in your config.on-focused-monitor-changed. It falls back to the empty TOML array.execTOML table. See: exec environment variables (It’s so boring and verbose, I don’t even want to mention it in thedefault-config.toml)
Binding modes
You can create multiple sets of bindings by creating different binding modes. When you switch to a different binding mode, all the bindings from the current mode are deactivated, and only the bindings specified in the new mode become active. The initial binding mode that FlightDeck starts out with is “main”. This feature is absolutely identical to the one in i3 Working with binding modes consists of two parts:- defining a binding to switch to the binding mode and 2. declaring the binding mode itself.
Commands
Commands are the thing you use to manipulate FlightDeck and query its state. There are two ways on how you can use commands:- Bind keys to run FlightDeck commands. Example:
- Run commands in CLI. Open up a Terminal.app and type:
Keyboard layouts and key mapping
By default, key bindings in the config are perceived asqwerty layout.
If you use different layout, different alphabet, or you just want to have a fancy alias for the existing key, you can use key-mapping.key-notation-to-key-code.
- For
dvorakandcolemakusers, FlightDeck offers preconfigured presets.
exec-* Environment Variables
You can configure environment variables ofexec-* commands and callbacks (such as exec-and-forget, exec-on-workspace-change-callback)
exec.inherit-env-vars = trueconfigures whether inherit environment variables ofFlightDeck.appor not. (The default istrue)- You can override env variables with the following syntax:
${ENV_VAR}
- You can inspect what is the end result of environment variables using
list-exec-env-varscommand - GUI apps on macOS don’t have Homebrew’s prefix in their
PATHby default (docs.brew.sh). That’s why unless you overrideexecsection in your config, FlightDeck falls back to the followingexecconfiguration:
Tree
FlightDeck stores all windows and containers in a tree. FlightDeck tree tiling model is inspired by i3. Definition. Each non-leaf node is called a “Container” WARNING: i3 has a different terminology. “container” in i3 is the same as “node” in FlightDeck.- Each workspace contains its own single root node
- Each container can contain arbitrary number of children nodes
- Windows are the only possible leaf nodes. Windows contain zero children nodes
- Every container has two properties:
- Layout (Possible values:
tiles,accordion) - Orientation (Possible values:
horizontal,vertical)


Layouts
In total, FlightDeck provides 4 possible layouts:h_tileshorizontal tiles (in i3, it’s called “horizontal split”)v_tilesvertical tiles (in i3, it’s called “vertical split”)h_accordionhorizontal accordion (analog of i3’s “tabbed layout”)v_accordionvertical accordion (analog of i3’s “stacked layout”)
tiles layout.
Accordion is a layout where windows are placed on top of each other.
- The horizontal accordion shows left and right paddings to visually indicate the presence of other windows in those directions.
- The vertical accordion shows top and bottom paddings to visually indicate the presence of other windows in those directions.


tiles layout, you can use the focus command to navigate an accordion layout.
You can navigate the windows in an h_accordion by using the focus (left|right) command.
While in a v_accordion, you can navigate the windows using the focus (up|down) command.
Accordion padding is configurable via accordion-padding option.
Normalization
By default, FlightDeck does two types of tree normalizations:- Containers that have only one child are “flattened”.
The root container is an exception, it is allowed to have a single window child.
Configured by
enable-normalization-flatten-containers - Containers that nest into each other must have opposite orientations.
Configured by
enable-normalization-opposite-orientation-for-nested-containers
Floating windows
Normally, floating windows are not considered to be part of the tiling tree. But it’s not the case with focus command. From focus command perspective, floating windows are part of tiling tree. The floating window parent container is determined as the smallest tiling container that contains the center of the floating window. This technique eliminates the need for an additional binding for focusing floating windows.Emulation of virtual workspaces
Native macOS Spaces have a lot of problems- The animation for Spaces switching is slow
** You can’t disable animation for Spaces switching (you can only make it slightly faster by turning on
Reduce motionsetting, but it’s suboptimal) - You have a limit of Spaces (up to 16 Spaces with one monitor)
- You can’t create/delete/reorder Space and move windows between Spaces with hotkeys (you can only switch between Spaces with hotkeys)
- Apple doesn’t provide public API to communicate with Spaces (create/delete/reorder/switch Space and move windows between Spaces)
cmd + tab) windows are placed back to the visible area of the screen.
When you quit the FlightDeck or when the FlightDeck detects that it’s about to crash, FlightDeck will place all windows back to the visible area of the screen.
FlightDeck shows the name of currently active workspace in its tray icon (top right corner), to give users a visual feedback on what workspace is currently active.
The intended workflow of using FlightDeck workspaces is to only have one macOS Space (or as many monitors you have, if Displays have separate Spaces is enabled) and don’t interact with macOS Spaces anymore.
For better or worse, macOS doesn’t allow to place windows outside the visible area entirely.
You will still be able to see a 1 pixel vertical line of “hidden” windows in the bottom right or left corner of your screen.
That means, that if FlightDeck crashes badly you will still be able to manually “unhide” the windows by dragging these few pixels to the center of the screen.If you want to minimize the visibility of hidden windows, it’s recommended to place Dock in the bottom (and additionally turn automatic hiding on)
Proper monitor arrangement
Since FlightDeck needs a free space to hide windows in, please make sure to arrange monitors in a way where every monitor has free space in the bottom right or left corner. (System Settings -> Displays -> Arrange...)
If you fail to arrange your monitors properly, you will see parts of hidden windows on other monitors.
.Bad monitor arrangement. Monitor 2 doesn’t have free space in either of the bottom corners
A note on mission control
For some reason, mission control doesn’t like that FlightDeck puts a lot of windows in the bottom right corner of the screen. Mission control shows windows too small even when there is enough space to show them bigger. There is a workaround. You can enableGroup windows by application setting:
System Settings -> Desktop & Dock -> Group windows by application). For whatever weird reason, it helps.
A note on ‘Displays have separate Spaces’
There is an observation that macOS works better and more stable if you disable Displays have separate Spaces. (It’s enabled by default)
People report all sorts of weird issues related to focus and performance when this setting is enabled:
- Wrong window may receive focus in multi-monitor setup: #101 (Bug in Apple API)
- Wrong borderless Alacritty window may receive focus in single monitor setup: #247 (Bug in Apple API)
- Performance issues: #333
- macOS randomly switches focus back: #289
Displays have separate Spaces is enabled,
moving windows between monitors causes windows to move between different Spaces which is not correctly handled by the public APIs FlightDeck uses,
apparently, these APIs are not aware about Spaces existence.
Spaces are just cursed in macOS. The less Spaces you have, the better macOS behaves.
‘Displays have separate Spaces’ is enabled | ’Displays have separate Spaces’ is disabled | |
| Is it possible for window to span across several monitors? | ❌ No. macOS limitation | 👍 Yes |
| Overall stability and performance | ❌ Weird focus and performance issues may happen (see the list above) | 👍 Public Apple API are more stable (which in turn affects FlightDeck stability) |
| When the first monitor is in fullscreen | 👍 Second monitor operates independently | ❌ Second monitor is unusable black screen |
| macOS status bar … | … is displayed on both monitors | … is displayed only on main monitor |
Displays have separate Spaces.
You can disable the setting by running:
System Settings -> Desktop & Dock -> Displays have separate Spaces). Logout is required for the setting to take effect.
Callbacks
on-window-detected callback
You can useon-window-detected callback to run commands every time a new window is detected.
Here is a showcase example that uses all the possible configurations:
run commands are run only if the detected window matches all the specified conditions.
If no conditions are specified then run is run every time a new window is detected.
Several callbacks can be declared in the config.
The callbacks are processed in the order they are declared.
By default, the first callback that matches the criteria is run, and further callbacks are not considered.
(The behavior can be overridden with check-further-callbacks option)
Available window conditions are:
| Condition TOML key | Condition description |
if.app-id | Application ID exact match of the detected window |
if.app-name-regex-substring | Application name case insensitive regex substring of the detected window |
if.window-title-regex-substring | Window title case insensitive regex substring of the detected window |
if.during-aerospace-startup a | - If true then run the callback only during FlightDeck startup. - If false then run callback only NOT during FlightDeck startup. - If not specified then the condition isn’t checked |
if.workspace | Window’s workspace name exact match |
-
if.during-aerospace-startup = trueis useful if you want to do the initial app arrangement only on startup. -
if.during-aerospace-startup = falseis useful if you want to relaunch FlightDeck, but the callback has side effects that you don’t want to run on every relaunch. (e.g. the callback opens new windows)
app-id:
- Take a look at the precompiled list of Apple application IDs
- You can use
flightdeck list-appsCLI command to get IDs of running applications mdls -name kMDItemCFBundleIdentifier -r /Applications/App.app
window-title-regex-substring may not work as expected for such windows
Examples of automations:
- Assign apps on particular workspaces
- Make all windows float by default
on-focus-changed callbacks
You can track focus changes using the following callbacks:on-focus-changed and on-focused-monitor-changed.
on-focus-changedis called every time focused window or workspace changes.on-focused-monitor-changedis called every time focused monitor changes.
exec-on-workspace-change callback
exec-on-workspace-change callback allows to run arbitrary process when focused workspace changes.
It may be useful for integrating with bars.
exec.env-vars, the process has access to the following environment variables:
AEROSPACE_FOCUSED_WORKSPACE- the workspace user switched toAEROSPACE_PREV_WORKSPACE- the workspace user switched from
AEROSPACE_* names and commonly used aerospace_workspace_change event name are intentionally retained for compatibility with existing AeroSpace configurations and integrations.
For a more elaborate example on how to integrate with Sketchybar see
/goodies#show-flightdeck-workspaces-in-sketchybar
Multiple monitors
- The pool of workspaces is shared between monitors
- Each monitor shows its own workspace. The showed workspaces are called “visible” workspaces
- Different monitors can’t show the same workspace at the same time
- Each workspace (even invisible, even empty) has a monitor assigned to it
- By default, all workspaces are assigned to the “main” monitor (“main” as in
System -> Displays -> Use as)
- FlightDeck takes the assigned monitor of the workspace and makes the workspace visible on the monitor
- FlightDeck focuses the workspace
Observation
The idea of making the pool of workspaces shared is based on the observation that most users have a limited set of workspaces on their secondary monitors. Secondary monitors are frequently dedicated to specific tasks (browser, shell), or for monitoring various activities such as logs and dashboards. Thus, using one workspace per secondary monitor and “the rest” on the main monitor often makes sense.The only difference between FlightDeck and i3 is switching to empty workspaces.
When you switch to an empty workspace, FlightDeck puts the workspace on an assigned monitor; i3 puts the workspace on currently active monitor.
- I find that FlightDeck model works better with the observation listed above.
- FlightDeck model is more consistent (it works the same for empty workspaces and non-empty workspaces)
Assign workspaces to monitors
You can useworkspace-to-monitor-force-assignment syntax to assign
workspaces to always appear on particular monitors
- Left hand side of the assignment is the workspace name
- Right hand side of the assignment is the monitor pattern
main- “Main” monitor (“main” as inSystem Settings -> Displays -> Use as)secondary- Non-main monitor in case when there are only two monitors<number>(e.g.1,2) - Sequence number of the monitor from left to right. 1-based indexing<regex-pattern>(e.g.dell.*,built-in.*) - Case insensitive regex substring pattern
Dialog heuristics
- Apple provides accessibility API for apps to let others know which of their windows are dialogs
- A lot of apps don’t implement this API or implement it improperly
isDialogHeuristic function in FlightDeck sources.
You can also use on-window-detected to force tile or force float all windows of a particular application:
- Force tile all the windows (or windows of a particular app)
- Force float all the windows (or windows of a particular app)