This documentation is an overview of the JavaScript API provided by Phoenix. Currently, the supported version of JavaScript is based on the ECMAScript 6 standard. macOS versions prior to Sierra (10.12) support ECMAScript 5.1. Use this as a guide for writing your window management script. Your script should reside in ~/.phoenix.js. Alternatively — if you prefer — you may also have your script in ~/Library/Application Support/Phoenix/phoenix.js or ~/.config/phoenix/phoenix.js. Phoenix includes Lodash (4.17.11) — you can use its features in your configuration. Lodash provides useful helpers for handling JavaScript functions and objects. You may also use JavaScript preprocessing and languages such as CoffeeScript to write your Phoenix-configuration.
- Keys
- Events
- Preferences
- Require
- Phoenix
- Storage
- Point
- Size
- Rectangle
- Identifiable
- Iterable
- Key
- Event
- Timer
- Task
- Image
- Modal
- Screen
- Space
- Mouse
- App
- Window
This documentation uses pseudocode to outline the API. Many of the classes represent global objects in the script’s context — functions that are marked as static can be accessed through these global objects. All other functions are instance functions. Instance objects can be accessed through the global objects or constructed with relevant constructors.
For example, to bind a key to a function, you construct a Key-object. Notice that you must keep a reference to the handler, otherwise your callback will not get called.
var handler = new Key('q', [ 'control', 'shift' ], function () {});To move the focused window to a new coordinate, you can call the setTopLeft-function for a Window-instance. To get a Window-instance, you can for example get the focused window with the focused-function for the global Window-object.
Window.focused().setTopLeft({ x: 0, y: 0 });To combine, bind a key to move the focused window.
var handler = new Key('q', [ 'control', 'shift' ], function () {
Window.focused().setTopLeft({ x: 0, y: 0 });
});As an other example, to bind an event to a function, you construct an Event-object. Again notice that you must keep a reference to the handler, otherwise your callback will not get called.
var handler = new Event('screensDidChange', function () {});As previously mentioned you must keep a reference to your handlers, otherwise your callbacks will not get called. In return, if you release the reference to the handler, it will also be disabled eventually. Beware that this can be rather delayed and you are always safer to manually disable the handlers before letting the reference go. This gives you full control over the lifecycle of your handlers and can be especially useful when you want to dynamically create handlers.
Obviously, in most cases you do not want to worry about the lifecycle of your handlers. This is why Phoenix also provides managed handlers that are held for you. You can use these managed handlers to set keys, events, timers and tasks, but also to disable them. Basically, when you create a managed handler, the handler is constructed and its reference is stored. You will get an identifier for the handler which you can then use to disable it. When you disable the handler, Phoenix will take care of properly disposing it for you.
For example, to bind a key to a function.
Key.on('q', [ 'control', 'shift' ], function () {});You can disable the handler with its identifier.
var identifier = Key.on('q', [ 'control', 'shift' ], function () {});
Key.off(identifier);Your configuration file is loaded when the app launches. All functions are evaluated (and executed if necessary) when this happens. Phoenix also reloads the configuration when any changes are detected to the file(s). You may also reload the configuration manually from the status bar or programmatically from your script.
The following locations are valid configuration paths and the first existing file will be used. You may also use these paths for the debug-configuration (.debug.js). Whilst loading, all symlinks will be resolved, so in the end your configuration can also be a symlink to any desired destination.
~/.phoenix.js~/Library/Application Support/Phoenix/phoenix.js~/.config/phoenix/phoenix.js
You may add JavaScript preprocessing to your configuration by adding a Shebang-directive to the beginning of your file. It must be the first statement in your file. Phoenix should support all popular JavaScript compilers, but be aware that you need to have the compiler installed on your setup and accessible through your shell’s PATH for Phoenix to find it. You also need to ask the compiler to output to the standard output so Phoenix is able to evaluate the result. For example, use CoffeeScript to write your configuration:
#!/usr/bin/env coffee -p
Key.on 's', [ 'control', 'shift' ], ->
App.launch('Safari').focus()Or use Babel to use ECMAScript 6 JavaScript in macOS versions prior to Sierra:
#!/usr/bin/env babel
Key.on('s', [ 'control', 'shift' ], () => {
App.launch('Safari').focus();
});macOS has two commonly used coordinate systems: for higher level elements the origo (0, 0) is situated in the bottom-left corner of the screen, on the contrary for lower level elements the origo is situated in the top-left corner of the screen (flipped). This API has no distinction between these systems — Points can represent both cases. The larger part of the API uses a flipped top-left based origin, unless otherwise is stated.
To log messages, use Phoenix.log. The messages are delivered to the Console (app). You can filter logs by process by searching for “Phoenix”. You can also follow the logs from a terminal by running log stream --process Phoenix.
To debug your configuration, use Safari’s Web Inspector. You can attach to the context from Safari’s “Develop” menu under your devices name. Read a more comprehensive instruction to get started. In the Web Inspector’s Console you can also see messages outputted with console.log.
All valid keys for binding are as follows:
- Modifiers:
command(cmd),option(alt),control(ctrl) andshift(case insensitive) - Keys: case insensitive character or case sensitive special key including function keys, arrow keys, keypad keys etc. as listed below
- You can bind any key on your local keyboard layout, for instance an
å-character if your keyboard has one - If you use multiple keyboard layouts, Phoenix will use the active layout when the context is loaded
- Action:
return,tab,space,delete,escape,help,home,pageUp,forwardDelete,end,pageDown,left,right,downandup - Function:
f1–f19 - Keypad:
keypad.,keypad*,keypad+,keypadClear,keypad/,keypadEnter,keypad-,keypad=,keypad0,keypad1,keypad2,keypad3,keypad4,keypad5,keypad6,keypad7,keypad8andkeypad9
Phoenix supports the following (case sensitive) events:
didLaunchtriggered once when Phoenix has launched and the context is readywillTerminatetriggered when Phoenix will terminate, use this event to perform any tasks before the application terminates
screensDidChangetriggered when screens (i.e. displays) are added, removed, or dynamically reconfigured
spaceDidChangetriggered when the active space has changed
All of the following mouse events receive the corresponding Point-object as the first argument for the callback function. This object is also enhanced with a modifiers array which contains the key modifiers pressed when the mouse event is triggered.
mouseDidMovetriggered when the mouse has movedmouseDidLeftClicktriggered when the mouse did left clickmouseDidRightClicktriggered when the mouse did right clickmouseDidLeftDragtriggered when the mouse did left dragmouseDidRightDragtriggered when the mouse did right drag
All of the following app events receive the corresponding App-instance as the first argument for the callback function.
appDidLaunchtriggered when an app has launchedappDidTerminatetriggered when an app has terminatedappDidActivatetriggered when an app has activatedappDidHidetriggered when an app becomes hiddenappDidShowtriggered when an app is shown (becomes unhidden)
All of the following window events receive the corresponding Window-instance as the first argument for the callback function.
windowDidOpentriggered when a window has openedwindowDidClosetriggered when a window has closedwindowDidFocustriggered when a window was focused within an appwindowDidMovetriggered when a window has movedwindowDidResizetriggered when a window has resizedwindowDidMinimiseorwindowDidMinimizetriggered when a window has minimisedwindowDidUnminimiseorwindowDidUnminimizetriggered when a window has unminimised
Phoenix supports the following (case sensitive) preferences:
daemon(boolean): if settruePhoenix will run completely in the background, this also removes the status bar menu, defaults tofalseopenAtLogin(boolean): if settruePhoenix will automatically open at login, defaults tofalseif no value has been previously set
Set the preferences using the Phoenix-object — for example:
Phoenix.set({
daemon: true,
openAtLogin: true
});You can modularise your configuration using the require-function. It will load the referenced JavaScript-file and reload it if any changes are detected. If the path is relative, it is resolved relatively to the absolute location of the primary configuration file. If this file is a symlink, it will be resolved before resolving the location of the required file. If the file does not exist, require will throw an error.
require('path/to/file.js');Use the Phoenix-object for API-level tasks.
class Phoenix
static void reload()
static void set(Map<String, AnyObject> preferences)
static void log(AnyObject... arguments)
static void notify(String message)
endreload()manually reloads the context and any changes in the configuration filesset(Map<String, AnyObject> preferences)sets the preferences from the given key–value map, any previously set preferences with the same key will be overriddenlog(AnyObject... arguments)logs the arguments to the Console (app)notify(String message)delivers the message to the Notification Center
Use the Storage-object to store values across reloads and reboots as JSON.
class Storage
static void set(String key, AnyObject value)
static AnyObject get(String key)
static void remove(String key)
endset(String key, AnyObject value)stores the value for the key, any previously set value with the same key will be overriddenget(String key)retrieves and returns the value for the key (undefinedif no value has been set)remove(String key)removes the key and the value associated with it
A simple point object for 2D-coordinates.
class Point
property double x
property double y
endA simple 2D-size object.
class Size
property double width
property double height
endA 2D-rectangle representation of a Point and Size.
class Rectangle
property double x
property double y
property double width
property double height
endObjects that implement Identifiable can be identified and compared.
interface Identifiable
int hash()
boolean isEqual(AnyObject object)
endhash()returns the hash value for the objectisEqual(AnyObject object)returnstrueif the given object is equal with this object
Objects that implement Iterable can be traversed relatively to the current object.
interface Iterable
Object next()
Object previous()
endnext()returns the next object or the first object when on the last oneprevious()returns the previous object or the last object when on the first one
Use the Key-object to construct keys, access their properties, and enable or disable them. You can have multiple handlers for a single key combination, however only one can be enabled at a time. Enabling a key combination that has been exclusively registered by another app will fail.
class Key implements Identifiable
static int on(String key, Array<String> modifiers, Function callback)
static void once(String key, Array<String> modifiers, Function callback)
static void off(int identifier)
property String key
property Array<String> modifiers
constructor Key Key(String key, Array<String> modifiers, Function callback)
boolean isEnabled()
boolean enable()
boolean disable()
endon(String key, Array<String> modifiers, Function callback)constructs a managed handler for a key and returns the identifier for the handler, for arguments seenew Key(...)once(String key, Array<String> modifiers, Function callback)constructs a managed handler for a key that is by default only triggered one time and then disabled, for more control you can explicitly returnfalsefrom the callback function and the handler will not be disabled until you return something else, for arguments seenew Key(...)off(int identifier)disables the managed handler for a key with the given identifierkeyread-only property for the key character in lower case or case sensitive special keymodifiersread-only property for the key modifiers in lower casenew Key(String key, Array<String> modifiers, Function callback)constructs and binds the key character with the specified modifiers (can be an empty list) to a callback function and returns the handler, you must keep a reference to the handler in order for your callback to get called, you can have multiple handlers for a single key combination, only one can be enabled at a time, any previous handler for the same key combination will automatically be disabled, the callback function receives its handler as the first argument and as the second argument a boolean that indicates if the key was repeated (key combination is held down)isEnabled()returnstrueif the key handler is enabled, by defaulttrueenable()enables the key handler, any previous handler for the same key combination will automatically be disabled, returnstrueif successfuldisable()disables the key handler, returnstrueif successful
Use the Event-object to construct events, access their properties or to disable them. You can have multiple handlers for a single event.
class Event implements Identifiable
static int on(String event, Function callback)
static void once(String event, Function callback)
static void off(int identifier)
property String name
constructor Event Event(String event, Function callback)
void disable()
endon(String event, Function callback)constructs a managed handler for an event and returns the identifier for the handler, for arguments seenew Event(...)once(String event, Function callback)constructs a managed handler for an event that is by default only triggered one time and then disabled, for more control you can explicitly returnfalsefrom the callback function and the handler will not be disabled until you return something else, for arguments seenew Event(...)off(int identifier)disables the managed handler for an event with the given identifiernameread-only property for the event namenew Event(String event, Function callback)constructs and binds an event to a callback function and returns the handler, you must keep a reference to the handler in order for your callback to get called, you can have multiple handlers for a single event, the callback function receives its handler as the last argument, for any additional arguments see eventsdisable()disables the event handler
Use the Timer-object to construct and control timers. A timer can fire only once or be repeating.
class Timer implements Identifiable
static int after(double interval, Function callback)
static int every(double interval, Function callback)
static void off(int identifier)
constructor Timer Timer(double interval, boolean repeats, Function callback)
void stop()
endafter(double interval, Function callback)constructs a managed handler for a timer that fires only once and returns the identifier for the handler, for arguments seenew Timer(...)every(double interval, Function callback)constructs a managed handler for a timer that fires repeatedly and returns the identifier for the handler, for arguments seenew Timer(...)off(int identifier)disables the managed handler for a timer with the given identifiernew Timer(double interval, boolean repeats, Function callback)constructs a timer that fires the callback once or repeatedly until stopped with the given interval (in seconds) and returns the handler, you must keep a reference to the handler in order for your callback to get called, the callback function receives its handler as the only argumentstop()stops the timer immediately
Use the Task-object to construct tasks, access their properties or to terminate them. Beware that some task properties are only set after the task has completed.
class Task implements Identifiable
static int run(String path, Array arguments, Function callback)
static void terminate(int identifier)
property int status
property String output
property String error
constructor Task Task(String path, Array arguments, Function callback)
void terminate()
endrun(String path, Array arguments, Function callback)constructs a managed handler for a task and returns the identifier for the handler, for arguments seenew Task(...)terminate(int identifier)terminates the managed handler for a task with the given identifierstatusread-only property for the termination statusoutputread-only property for the standard outputerrorread-only property for the standard errornew Task(String path, Array arguments, Function callback)constructs a task that asynchronously executes an absolute path with the given arguments and returns the handler, you must keep a reference to the handler in order for your callback to get called, the callback function receives its handler as the only argumentterminate()terminates the task immediately
Use the Image-object to construct images.
class Image implements Identifiable
static Image fromFile(String path)
endfromFile(String path)loads an image from the given path, the path is resolved before attempting to load the image, returnsundefinedif unsuccessful
Use the Modal-object to display content as modal windows (in front of all other windows). Modals can be used to display icons and/or text for visual cues. Properties defined as dynamic can be altered while the modal is displayed.
class Modal implements Identifiable
static Modal build(Map<String, AnyObject> properties)
property Point origin
property double duration
property double animationDuration
property double weight
property String appearance
property Image icon
property String text
constructor Modal Modal()
Rectangle frame()
void show()
void close()
endbuild(Map<String, AnyObject> properties)builds a modal with the specified properties and returns it,originshould be a function that receives the frame for the modal as the only argument and returns aPoint-object which will be set as the origin for the modalorigindynamic property for the origin of the modal, the enclosed properties are read-only so you must pass an object for this property, bottom-left based origin, by default(0, 0)durationproperty for the duration (in seconds) before automatically closing the modal, if the duration is set to0the modal will remain open until closed, by default0animationDurationproperty for the animation duration (in seconds) for showing and closing the modal, if the duration is set to0the animation will be disabled, by default0.2weightdynamic property for the weight of the modal (in points), by default24appearanceproperty for the appearance of the modal (dark|light|transparent), by defaultdarkicondynamic property for the icon displayed in the modaltextdynamic property for the text displayed in the modalnew Modal()constructs and returns a new modalframe()returns the frame for the modal, the frame is adjusted for the current content, therefor you must first set the weight, icon and text to get an accurate frame, bottom-left based originshow()shows the modal, you must set at least an icon or text for the modal to be displayedclose()closes the modal
Use the Screen-object to access frame sizes and other screens on a multi-screen setup. Beware that a screen can get stale if you keep a reference to it and it is for instance disconnected while you do so.
class Screen implements Identifiable, Iterable
static Screen main()
static Array<Screen> all()
String identifier()
Rectangle frame()
Rectangle visibleFrame()
Rectangle flippedFrame()
Rectangle flippedVisibleFrame()
Space currentSpace() // macOS 10.11+
Array<Space> spaces() // macOS 10.11+
Array<Window> windows(Map<String, AnyObject> optionals)
endmain()returns the screen containing the window with the keyboard focusall()returns all screens, the first screen in this array corresponds to the primary screen for the systemidentifier()returns the UUID for the screenframe()returns the whole frame for the screen, bottom-left based originvisibleFrame()returns the visible frame for the screen subtracting the Dock and Menu from the frame when visible, bottom-left based originflippedFrame()returns the whole frame for the screen, top-left based originflippedVisibleFrame()returns the visible frame for the screen subtracting the Dock and Menu from the frame when visible, top-left based origincurrentSpace()returns the current space for the screen (macOS 10.11+, returnsundefinedotherwise)spaces()returns all spaces for the screen (macOS 10.11+, returns an empty list otherwise)windows(Map<String, AnyObject> optionals)returns all windows for the screen if no optionals are given
visible(boolean): if settruereturns all visible windows for the screen, if setfalsereturns all hidden windows for the screen
Use the Space-object to control spaces. These features are only supported on El Capitan (10.11) and upwards. A single window can be in multiple spaces at the same time. To move a window to a different space, remove it from any existing spaces and add it to a new one. You can switch to a space by focusing on a window in that space. Beware that a space can get stale if you keep a reference to it and it is for instance closed while you do so.
class Space implements Identifiable, Iterable
static Space active() // macOS 10.11+
static Array<Space> all() // macOS 10.11+
boolean isNormal()
boolean isFullScreen()
Array<Screen> screens()
Array<Window> windows(Map<String, AnyObject> optionals)
void addWindows(Array<Window> windows)
void removeWindows(Array<Window> windows)
endactive()returns the space containing the window with the keyboard focus (macOS 10.11+, returnsundefinedotherwise)all()returns all spaces, the first space in this array corresponds to the primary space (macOS 10.11+, returns an empty list otherwise)isNormal()returnstrueif the space is a normal spaceisFullScreen()returnstrueif the space is a full screen spacescreens()returns all screens to which the space belongs towindows(Map<String, AnyObject> optionals)returns all windows for the space if no optionals are givenaddWindows(Array<Window> windows)adds the given windows to the spaceremoveWindows(Array<Window> windows)removes the given windows from the space
visible(boolean): if settruereturns all visible windows for the space, if setfalsereturns all hidden windows for the space
Use the Mouse-object to control the cursor.
class Mouse
static Point location()
static boolean move(Point point)
endlocation()returns the cursor positionmove(Point point)moves the cursor to a given position, returnstrueif successful
Use the App-object to control apps. Beware that an app can get stale if you keep a reference to it and it is for instance terminated while you do so, refer to isTerminated().
class App implements Identifiable
static App get(String appName)
static App launch(String appName, Map<String, AnyObject> optionals)
static App focused()
static Array<App> all()
int processIdentifier()
String bundleIdentifier()
String name()
Image icon()
boolean isActive()
boolean isHidden()
boolean isTerminated()
Window mainWindow()
Array<Window> windows(Map<String, AnyObject> optionals)
boolean activate()
boolean focus()
boolean show()
boolean hide()
boolean terminate(Map<String, AnyObject> optionals)
endget(String appName)returns the running app with the given name, returnsundefinedif the app is not currently runninglaunch(String appName, Map<String, AnyObject> optionals)launches and returns the app with the given name, returnsundefinedif unsuccessfulfocused()returns the focused appall()returns all running appsprocessIdentifier()returns the process identifier (PID) for the app, returns-1if the app does not have a PIDbundleIdentifier()returns the bundle identifier for the appname()returns the name for the appicon()returns the icon for the appisActive()returnstrueif the app is currently frontmostisHidden()returnstrueif the app is hiddenisTerminated()returnstrueif the app has been terminatedmainWindow()returns the main window for the appwindows(Map<String, AnyObject> optionals)returns all windows for the app if no optionals are givenactivate()activates the app and brings its windows forward, returnstrueif successfulfocus()activates the app and brings its windows to focus, returnstrueif successfulshow()shows the app, returnstrueif successfulhide()hides the app, returnstrueif successfulterminate(Map<String, AnyObject> optionals)terminates the app, returnstrueif successful
focus(boolean): if settruethe app will automatically be focused on launch, by default the app launches to the background
visible(boolean): if settruereturns all visible windows for the app, if setfalsereturns all hidden windows for the app
force(boolean): if settrueforce terminates the app
Use the Window-object to control windows. Every screen (i.e. display) combines to form a large rectangle. Every window lives within this rectangle and their position can be altered by giving coordinates inside this rectangle. To position a window to a specific display, you need to calculate its position within the large rectangle. To figure out the coordinates for a given screen, use the functions in Screen. Beware that a window can get stale if you keep a reference to it and it is for instance closed while you do so.
class Window implements Identifiable
static Window focused()
static Window at(Point point)
static Array<Window> all(Map<String, AnyObject> optionals)
static Array<Window> recent()
Array<Window> others(Map<String, AnyObject> optionals)
String title()
boolean isMain()
boolean isNormal()
boolean isFullScreen()
boolean isMinimised() // or isMinimized()
boolean isVisible()
App app()
Screen screen()
Array<Space> spaces() // macOS 10.11+
Point topLeft()
Size size()
Rectangle frame()
boolean setTopLeft(Point point)
boolean setSize(Size size)
boolean setFrame(Rectangle frame)
boolean setFullScreen(boolean value)
boolean maximise() // or maximize()
boolean minimise() // or minimize()
boolean unminimise() // or unminimize()
Array<Window> neighbours(String direction) // or neighbors(...)
boolean raise()
boolean focus()
boolean focusClosestNeighbour(String direction) // or focusClosestNeighbor(...)
boolean close()
endfocused()returns the focused window for the currently active app, can beundefinedif no window is focused currentlyat(Point point)returns the topmost window at the specified point, can beundefinedif no window is present at the given positionall(Map<String, AnyObject> optionals)returns all windows in screens if no optionals are givenrecent()returns all visible windows in the order as they appear on the screen (from front to back), essentially returning them in the most-recently-used orderothers(Map<String, AnyObject> optionals)returns all other windows on all screens if no optionals are giventitle()returns the title for the windowisMain()returnstrueif the window is the main window for its appisNormal()returnstrueif the window is a normal windowisFullScreen()returnstrueif the window is a full screen windowisMinimised()orisMinimized()returnstrueif the window is minimisedisVisible()returnstrueif the window is a normal and unminimised window that belongs to an unhidden appapp()returns the app for the windowscreen()returns the screen where most or all of the window is currently presentspaces()returns the spaces where the window is currently present (macOS 10.11+, returns an empty list otherwise)topLeft()returns the top left point for the windowsize()returns the size for the windowframe()returns the frame for the windowsetTopLeft(Point point)sets the top left point for the window, returnstrueif successfulsetSize(Size size)sets the size for the window, returnstrueif successfulsetFrame(Rectangle frame)sets the frame for the window, returnstrueif successfulsetFullScreen(boolean value)sets whether the window is full screen, returnstrueif successfulmaximise()ormaximize()resizes the window to fit the whole visible frame for the screen, returnstrueif successfulminimise()orminimize()minimises the window, returnstrueif successfulunminimise()orunminimize()unminimises the window, returnstrueif successfulneighbours(String direction)orneighbors(...)returns windows to the direction (west|east|north|south) of the windowraise()makes the window the frontmost window of its app (but does not focus the app itself), returnstrueif successfulfocus()focuses the window, returnstrueif successfulfocusClosestNeighbour(String direction)orfocusClosestNeighbor(...)focuses the closest window to the direction (west|east|north|south) of the window, returnstrueif successfulclose()closes the window, returnstrueif successful
visible(boolean): if settruereturns all visible windows in screens, if setfalsereturns all hidden windows in screens
visible(boolean): if settruereturns visible windows, if setfalsereturns hidden windowsscreen(Screen): returns all other windows on the specified screen