app.window - Window Management API

List, query, move, resize, and control macOS application windows.

Permission: L2 sensitive, requires system Accessibility permission. The system will prompt for authorization on first use.

Methods

app.window.list(opts?)

Get the list of all visible windows.

Parameters:

  • opts (table, optional):
    • app (string) - filter by application name (supports partial matching)

Returns: array<table> - each item contains:

  • title (string) - window title
  • app (string) - application name
  • bundleId (string) - application Bundle ID
  • pid (number) - process ID
  • x (number) - X coordinate of the top-left corner (points)
  • y (number) - Y coordinate of the top-left corner (points)
  • width (number) - window width (points)
  • height (number) - window height (points)
  • isMinimized (boolean) - whether the window is minimized
  • isFullscreen (boolean) - whether the window is in fullscreen mode
-- Get all windows
local windows = app.window.list()
for _, w in ipairs(windows) do
    app.log.info(w.app .. ": " .. w.title)
end

-- Get only Safari windows
local safariWindows = app.window.list({app = "Safari"})

app.window.focused()

Get the currently focused (frontmost) window.

Returns: table|nil - window information (same fields as list() items), or nil if no window is focused

local win = app.window.focused()
if win then
    app.log.info("Focused window: " .. win.title .. " (" .. win.app .. ")")
end

app.window.move(window, x, y)

Move a window to the specified position.

Parameters:

  • window - window identifier, supports three forms:
    • {app = "Safari", title = "Window Title"} - match by app name and title
    • {pid = 1234, title = "Window Title"} - match by process ID and title
    • "Window Title" - match by title only (uses first match)
  • x (number) - target X coordinate (points)
  • y (number) - target Y coordinate (points)

Returns: boolean, error

local ok, err = app.window.move({app = "Safari"}, 0, 0)
if not ok then
    app.log.error("Move failed: " .. (err or ""))
end

-- Using process ID
app.window.move({pid = 1234, title = "index.html"}, 100, 100)

-- Match by title only
app.window.move("Untitled", 200, 200)

app.window.resize(window, w, h)

Resize a window.

Parameters:

  • window - window identifier (same as move())
  • w (number) - target width (points)
  • h (number) - target height (points)

Returns: boolean, error

local ok, err = app.window.resize({app = "Finder"}, 800, 600)

app.window.setFrame(window, x, y, w, h)

Set both the position and size of a window in one call.

Parameters:

  • window - window identifier (same as move())
  • x (number) - X coordinate
  • y (number) - Y coordinate
  • w (number) - width
  • h (number) - height

Returns: boolean, error

-- Set Safari to the left half of the screen
local screen = app.screen.mainScreen()
local ok, err = app.window.setFrame(
    {app = "Safari"},
    0, 0,
    screen.width / 2, screen.height
)

app.window.minimize(window)

Minimize a window.

Parameters:

  • window - window identifier (same as move())

Returns: boolean, error

app.window.minimize({app = "Safari"})

app.window.maximize(window)

Maximize a window (zoom to fill available screen area).

Parameters:

  • window - window identifier (same as move())

Returns: boolean, error

app.window.maximize({app = "Safari"})

app.window.close(window)

Close a window.

Parameters:

  • window - window identifier (same as move())

Returns: boolean, error

app.window.close({app = "TextEdit", title = "Untitled"})

app.window.focus(window)

Bring a window to the front and focus it.

Parameters:

  • window - window identifier (same as move())

Returns: boolean, error

app.window.focus({app = "Finder"})

AX Elements (1.3.0)

The app.window.element.* namespace exposes the Accessibility element tree — use it to read control state, locate buttons/text fields, simulate clicks, and write values. Shares the window permission and Accessibility system authorization.

An element handle looks like {__ax_element_id, __type = "AXElement", role, title, identifier, value, children}. Lua can read the fields directly, but must pass the handle back to click/value/setValue as-is.

app.window.element.tree(window, opts?)

Recursively read the AX element tree for a window (since 1.3.0).

Parameters:

  • window - window descriptor (same as app.window.move())
  • opts (table, optional)
    • maxDepth (number) - recursion depth (default 10)
    • maxChildren (number) - children limit per level (default 200)

Returns: handle - root node with nested children array

local tree = app.window.element.tree({app = "Calculator"})
for _, btn in ipairs(tree.children) do
    app.log.info(btn.role .. " / " .. btn.title)
end

app.window.element.find(window, filter)

Search the element tree by filter, returning matching handles (since 1.3.0).

Parameters:

  • window - window descriptor
  • filter (table)
    • role (string, optional) - exact AXRole match (e.g. "AXButton")
    • title (string, optional) - title contains substring (case-insensitive)
    • identifier (string, optional) - exact AXIdentifier match
    • limit (number, optional) - max results (default 50)
    • maxDepth (number, optional) - search depth (default 15)

Returns: array<handle>, err

local buttons = app.window.element.find({app = "Calculator"}, {
    role = "AXButton", title = "1"
})

app.window.element.click(handle)

Perform the AXPress action on the element (since 1.3.0).

local ok = app.window.element.click(buttons[1])

app.window.element.value(handle)

Read the element’s AXValue (since 1.3.0). CGPoint/CGSize/CGRect/CFRange are converted to tables; other values stay as strings or numbers.

local v = app.window.element.value(handle)

app.window.element.setValue(handle, value)

Write the element’s AXValue (since 1.3.0). Primarily useful for text fields, sliders, and other writable controls.

app.window.element.setValue(inputField, "hello")

Description

  • All window operations require system Accessibility permission, which can be granted in System Settings → Privacy & Security → Accessibility
  • The title field in window identifiers supports partial matching; if multiple windows match, the first match is used
  • maximize() is equivalent to double-clicking the title bar zoom button, not the same as entering fullscreen mode
  • The coordinate system has its origin at the top-left corner of the main display, with positive directions going right and down (points, not pixels)
  • Minimized windows cannot be operated on via move()/resize(); call focus() first to restore them

Examples

Organize Window Layout

function MyPlugin:handleOrganizeWindows(context)
    local screen = app.screen.mainScreen()
    local w = screen.width / 2
    local h = screen.height

    -- Safari on the left half
    app.window.setFrame({app = "Safari"}, 0, 0, w, h)

    -- VS Code on the right half
    app.window.setFrame({app = "Code"}, w, 0, w, h)

    app.notification.show("Windows Organized", "Left: Safari, Right: VS Code")
end

List All Window Information

function MyPlugin:handleListWindows(context)
    local windows = app.window.list()

    local lines = {}
    for _, win in ipairs(windows) do
        local size = string.format("%dx%d @ (%d,%d)", win.width, win.height, win.x, win.y)
        table.insert(lines, string.format("[%s] %s — %s", win.app, win.title, size))
    end

    local result = table.concat(lines, "\n")
    app.dialog.alert("Windows (" .. #windows .. ")", result)
end
Developer Documentation
User Guide
Getting Started Script Menus FAQ
Script Development
Development Guide
Plugin Development
Quick Start Development Guide Example Plugins
API Reference
Overview API Query Plugin Info Logging Finder Context Plugin Settings Internationalization
UI & Interaction
Dialog Progress Notification Chooser WebView Status Bar Dock
Files & Paths
File Operations Path Utilities Finder Actions Trash Extended Attributes Metadata File Watcher
Data Formats
JSON Plist CSV XML PDF Image
Text & Encoding
String Regex Date & Time Color Crypto
System
Shell Commands Process Application System Info AppleScript Shortcuts
System Info
Network Power/Battery Screen/Appearance Audio Bluetooth Location
Network
HTTP WebSocket URL
Input & Clipboard
Keyboard Mouse Hotkey Clipboard Window
Storage
SQLite Keychain UserDefaults
Media
OCR QR Code
Utilities
Archive UTI Share Timer Wake Lock Thread