OmniWM

OmniWM IPC & CLI Reference

This document covers the OmniWM automation surface. For the docs hub, see Documentation Home. For internal architecture, see ARCHITECTURE.md. For contribution process, see the Contribution Guide.

Table of Contents


Architecture

OmniWM’s IPC system is split across three Swift modules:

┌──────────────────────────────────────────────────────────────────────────┐
│  OmniWMCtl (CLI binary)                                                  │
│  CLIEntry → CLIRuntime → CLIParser → IPCClient                           │
│  CLIRenderer, CLICompletionGenerator                                     │
│  Depends on: OmniWMIPC only                                             │
└────────────────────────────┬─────────────────────────────────────────────┘
                             │ Unix domain socket (NDJSON)
┌────────────────────────────┴─────────────────────────────────────────────┐
│  OmniWMIPC (shared library)                                              │
│  IPCModels, IPCWire, IPCSocketPath, IPCAutomationManifest                │
│  IPCRuleValidator                                                        │
│  No dependencies                                                         │
└────────────────────────────┬─────────────────────────────────────────────┘
                             │
┌────────────────────────────┴─────────────────────────────────────────────┐
│  OmniWM (app)                                                            │
│  IPCServer → IPCConnection → IPCApplicationBridge                        │
│  IPCCommandRouter, IPCQueryRouter, IPCRuleRouter, IPCEventBroker         │
│  Depends on: OmniWMIPC, AppKit, SkyLight, etc.                          │
└──────────────────────────────────────────────────────────────────────────┘

Request flow:

omniwmctl command focus left
    │
    ▼
CLIEntry.main()
    │
    ▼
CLIRuntime.run()
    ├─ local commands: help / completion
    ▼
CLIParser.parse()  ──▶  IPCRequest model
    │
    ▼
IPCClientConnection.send()
    │
    ▼
IPCWire.encodeRequestLine()  ──▶  Unix socket  ──▶  IPCServer
                                           │
                                           ▼
                                   IPCConnection (actor)
                                           │
                                           ▼
                                   IPCApplicationBridge
                                     ├─ auth check
                                     ├─ version check
                                     └─ route to IPCCommandRouter
                                           │
                                           ▼
                                   WMController.commandHandler
                                     (same path as hotkey commands)
                                           │
                                           ▼
                                   ExternalCommandResult
                                           │
                                           ▼
IPCWire.decodeResponse()  ◀──  IPCResponse (JSON)
    │
    ▼
CLIRenderer  ──▶  stdout

Local commands such as help, --help, -h, and completion never open the IPC socket. watch uses the same subscribe request path, then stays client-side to launch one child process per received event.


Installation

CLI Binary Location

The omniwmctl binary is bundled inside the OmniWM app at:

OmniWM.app/Contents/MacOS/omniwmctl

Installing to PATH

Use the OmniWM status bar menu: Install CLI to PATH. OmniWM chooses the first writable directory already on PATH inside your home directory. If none is available, it falls back to ~/.local/bin, then ~/bin.

The menu also shows current CLI status:

Enabling IPC

IPC is disabled by default. Enable it via:

Turning Enable IPC on starts the server immediately and creates the Unix socket plus the authorization secret file. Turning it off stops the server and removes both files.


IPC Protocol

Protocol version: 3

Socket & Authorization

Item Path
Socket ~/Library/Caches/com.barut.OmniWM/ipc.sock
Secret ~/Library/Caches/com.barut.OmniWM/ipc.sock.secret

The socket path can be overridden with the OMNIWM_SOCKET environment variable. The secret file path is always <socket-path>.secret. For custom socket paths, prefer a private same-user directory such as $TMPDIR/omniwm/ipc.sock after creating the parent directory with mode 0700. Avoid shared directories such as /tmp.

The authorization token is a random UUID generated each time the IPC server starts. Clients must include this token in every request. The CLI reads it automatically from the secret file.

Wire Format

The protocol uses newline-delimited JSON (NDJSON) — one JSON object per line, terminated by 0x0A.

Examples in this document are pretty-printed for readability. The actual wire format is compact single-line JSON with the same field names.

Security Model

  1. Socket permissions: 0600 (owner-only read/write)
  2. Socket directory permissions: newly created socket directories are created with 0700
  3. Secret file permissions: 0600 (owner-only read/write)
  4. Peer UID check: server verifies connecting client is the same user via getpeereid()
  5. Authorization token: every request must carry the authorization token stored in plaintext at <socket-path>.secret
  6. Session-scoped window IDs: opaque IDs embed a separate internal session token and are invalidated across restarts — format: ow_ + base64url(sessionToken:pid:windowId)
  7. FD_CLOEXEC: server-side listening and accepted socket file descriptors are not inherited by child processes
  8. SO_NOSIGPIPE: prevents SIGPIPE crashes on broken connections
  9. Stale socket cleanup: server tests existing sockets before overwriting

The trust boundary is the local macOS user account, not individual client processes. Any process running as the same user can read the secret file and use the IPC API once IPC is enabled.

If OMNIWM_SOCKET points into an existing directory, OmniWM reuses that directory as-is instead of re-permissioning it. For custom socket paths, prefer a private directory owned by the same user and avoid shared locations such as /tmp.


CLI Reference

omniwmctl <command> [arguments...] [--format json|table|tsv|text] [--json]

Top-Level Commands

Command Type Description
ping remote Verify IPC reachability and return pong
version remote Return the OmniWM app version and IPC protocol version
command remote Execute window manager commands through the IPC command surface
query remote Query OmniWM state, registries, and protocol capabilities
rule remote Manage persisted window rules and reapply them to windows
workspace remote Perform workspace actions such as focusing by workspace name
window remote Perform window actions using session-scoped opaque window IDs
subscribe remote Stream the subscribe handshake plus live event envelopes as JSON
watch remote Consume subscription events and run a child command once per event
help, --help, -h local Print CLI usage text without connecting to IPC
completion <zsh\|bash\|fish> local Emit a shell completion script without connecting to IPC

Remote commands require IPC to be enabled. Local commands work even when the IPC server is disabled.

Global Flags

Flag Description
--format <format> Output format: json, table, tsv, text
--json Alias for --format json

Global flags must appear before --exec in watch commands.

Exit Codes

Code Name Meaning
0 success Command completed successfully
1 rejected Server rejected the request
2 transportFailure Could not connect to IPC socket
3 invalidArguments CLI argument parsing failed
4 internalError Unexpected internal error

Commands

Execute window manager commands. These invoke the same code path as hotkey-bound commands.

omniwmctl command <command-path> [arguments...]

Focus

Command Arguments Layout Description
command focus <left\|right\|up\|down> shared Focus a neighboring window
command focus previous niri Focus the previously focused window
command focus down-or-left niri Traverse backward through the active Niri workspace
command focus up-or-right niri Traverse forward through the active Niri workspace
command focus-column <number> niri Focus a Niri column by one-based index
command focus-column first niri Focus the first Niri column
command focus-column last niri Focus the last Niri column

Move

Command Arguments Layout Description
command move <left\|right\|up\|down> shared Move the focused window in the given direction

Workspace Switching

Command Arguments Layout Description
command switch-workspace <number> shared Switch to a workspace by numeric workspace ID on the current monitor
command switch-workspace next shared Switch to the next workspace
command switch-workspace prev shared Switch to the previous workspace
command switch-workspace back-and-forth shared Switch to the previously active workspace
command switch-workspace anywhere <number> shared Focus a workspace by numeric workspace ID across all monitors

Move to Workspace

Command Arguments Layout Description
command move-to-workspace <number> shared Move focused window to a workspace by numeric workspace ID
command move-to-workspace up shared Move focused window to the adjacent workspace above
command move-to-workspace down shared Move focused window to the adjacent workspace below
command move-to-workspace on-monitor <number> <left\|right\|up\|down> shared Move focused window to a workspace already assigned to the requested adjacent monitor

Workspace IDs are positive numeric strings. Direct hotkeys stay limited to 1-9, but the workspace UI and IPC/CLI both support 10+.

Monitor Focus

Command Arguments Layout Description
command focus-monitor prev shared Move focus to the previous monitor
command focus-monitor next shared Move focus to the next monitor
command focus-monitor last shared Move focus back to the previous monitor
command swap-workspace-with-monitor <left\|right\|up\|down> shared Swap active workspace with the workspace on an adjacent monitor

Column Operations (Niri)

Command Arguments Layout Description
command move-column <left\|right\|up\|down> niri Move the focused Niri column
command move-column-to-workspace <number> niri Move focused column to workspace by index
command move-column-to-workspace up niri Move focused column to the adjacent workspace above
command move-column-to-workspace down niri Move focused column to the adjacent workspace below
command toggle-column-tabbed niri Toggle tabbed mode for the focused column
command toggle-column-full-width niri Toggle full-width mode for the focused column
command cycle-column-width forward shared Cycle column width presets forward
command cycle-column-width backward shared Cycle column width presets backward

Dwindle Operations

Command Arguments Layout Description
command move-to-root dwindle Move the selected window to the root split
command toggle-split dwindle Toggle the active split orientation
command swap-split dwindle Swap the active split
command resize <left\|right\|up\|down> <grow\|shrink> dwindle Resize the selected window
command preselect <left\|right\|up\|down> dwindle Set the preselection direction
command preselect clear dwindle Clear the preselection

Layout & Sizing

Command Arguments Layout Description
command balance-sizes shared Balance layout sizes in the active workspace
command toggle-workspace-layout shared Toggle the workspace between Niri and Dwindle
command set-workspace-layout <default\|niri\|dwindle> shared Set the workspace layout explicitly
command toggle-fullscreen shared Toggle OmniWM-managed fullscreen
command toggle-native-fullscreen shared Toggle native macOS fullscreen

Window Management

Command Arguments Layout Description
command toggle-focused-window-floating shared Toggle focused window between tiled and floating
command raise-all-floating-windows shared Raise all visible floating windows
command rescue-offscreen-windows shared Clamp tracked floating windows back onto their visible monitors
command scratchpad assign shared Assign the focused window to the scratchpad
command scratchpad toggle shared Show or hide the scratchpad window

UI Toggles

Command Arguments Layout Description
command open-command-palette shared Toggle the command palette
command open-menu-anywhere shared Open the menu surface
command toggle-workspace-bar shared Toggle workspace bar visibility
command toggle-hidden-bar shared Toggle the hidden bar surface
command toggle-quake-terminal shared Toggle the configured Quake terminal
command toggle-overview shared Toggle the overview surface

Layout compatibility:

Commands sent to an incompatible layout return layout_mismatch.


Queries

omniwmctl query <name> [selectors...] [--fields <field1,field2,...>] [--format json|table|tsv|text]

Default output format for queries is json.

Query Selectors

Selectors filter query results. Value selectors take an argument; boolean selectors are flags.

Value selectors:

Selector Description
--window <id> Filter by session-scoped opaque window ID
--workspace <name> Filter by workspace raw name, display name, or ID
--display <name> Filter by display name or display ID
--app <name> Filter by application display name
--bundle-id <id> Filter by application bundle identifier

Boolean selectors:

Selector Description
--focused Only the focused item
--visible Only visible items
--floating Only floating windows
--scratchpad Only the scratchpad window
--current Only the current/interaction item
--main Only the main display

Query Fields

Use --fields with a comma-separated list to limit returned fields.

Field tokens are part of the CLI contract. Returned JSON still uses the payload schema’s field names, so the selected token may not be byte-for-byte identical to the JSON key. For example, window-counts selects the workspace payload’s counts field.

Window fields: id, pid, workspace, display, app, title, frame, mode, layout-reason, manual-override, is-focused, is-visible, is-scratchpad, hidden-reason

Workspace fields: id, raw-name, display-name, number, layout, display, is-focused, is-visible, is-current, window-counts, focused-window-id

Display fields: id, name, is-main, is-current, frame, visible-frame, has-notch, orientation, active-workspace

Query Reference

Query Selectors Fields Description
workspace-bar Workspace bar projection for every monitor
active-workspace Current interaction monitor and active workspace
focused-monitor Current interaction monitor and its active workspace
apps Managed app summary
focused-window Focused managed window snapshot
focused-window-decision Focused window rule/debug decision snapshot
reconcile-debug Reconcile runtime snapshot and recent trace dump for debugging
windows --window, --workspace, --display, --focused, --visible, --floating, --scratchpad, --app, --bundle-id window fields Managed windows
workspaces --workspace, --display, --current, --visible, --focused workspace fields Configured workspaces with occupancy
displays --display, --main, --current display fields Connected displays with geometry
rules Persisted user window rules
rule-actions Rule action registry
queries Query registry
commands Automation action registry for command, workspace, and window surfaces
subscriptions Subscription registry
capabilities Full protocol capabilities

Examples:

# List all windows on workspace "main"
omniwmctl query windows --workspace main

# Get focused window in table format
omniwmctl query focused-window --format table

# List visible floating windows, only return id and title
omniwmctl query windows --visible --floating --fields id,title

# Get the active workspace on the current interaction monitor
omniwmctl query workspaces --current

# Check server capabilities
omniwmctl query capabilities

# Debug why a window was tiled/floated
omniwmctl query focused-window-decision

# Dump the reconcile runtime snapshot and recent trace
omniwmctl query reconcile-debug

reconcile-debug returns diagnostic text fields: snapshot, trace, and traceLimit.


Window Actions

Operate on specific windows by their session-scoped opaque ID.

omniwmctl window <action> <opaque-id>
Action Description
focus Focus a managed window by opaque ID
navigate Navigate to a managed window (switches workspace if needed)
summon-right Summon a window to the right of the currently focused window

Window IDs are session-scoped. They become stale after OmniWM restarts. Obtain IDs from query results (e.g., omniwmctl query windows).


Workspace Actions

omniwmctl workspace focus-name <name>
Action Arguments Description
focus-name <name> Focus a workspace by raw workspace ID or unambiguous configured display name

Numeric inputs are resolved as raw workspace IDs first. Display-name lookup is a convenience path and fails when multiple workspaces share the same display name.


Rules

Manage persisted window rules that control how windows are tiled, floated, or assigned to workspaces.

omniwmctl rule <action> [arguments...] [options...]

Rule Options

Option Value Description
--bundle-id <bundle-id> Application bundle identifier (required for add/replace)
--app-name-substring <text> Match app name containing this substring
--title-substring <text> Match window title containing this substring
--title-regex <pattern> Match window title against this regex
--ax-role <role> Match accessibility role
--ax-subrole <subrole> Match accessibility subrole
--layout <auto\|tile\|float> Layout action (auto = default behavior)
--assign-to-workspace <raw-name> Assign matching windows to this workspace raw name
--min-width <points> Minimum window width in points
--min-height <points> Minimum window height in points

Bundle IDs must match the pattern: ^[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*$

Rule Actions

Add a rule:

omniwmctl rule add --bundle-id <bundle-id> [options...]

Appends a new rule to the end of the rule list.

Replace a rule:

omniwmctl rule replace <rule-id> --bundle-id <bundle-id> [options...]

Replaces a rule in-place by its UUID. The rule ID is preserved.

Remove a rule:

omniwmctl rule remove <rule-id>

Removes a rule by its UUID.

Move a rule:

omniwmctl rule move <rule-id> <position>

Moves a rule to a new one-based position in the rule list.

Apply rules:

omniwmctl rule apply [--focused | --window <opaque-id> | --pid <pid>]

Re-evaluates the current rule set against the target. Defaults to --focused if no target is specified.

Target Description
--focused Apply to the currently focused window (default)
--window <id> Apply to a specific window by opaque ID
--pid <pid> Apply to all managed windows for a process

Examples:

# Float all Finder windows
omniwmctl rule add --bundle-id com.apple.finder --layout float

# Tile Safari and assign to workspace 2
omniwmctl rule add --bundle-id com.apple.Safari --layout tile --assign-to-workspace 2

# Float windows with "Preferences" in the title
omniwmctl rule add --bundle-id com.apple.Safari --title-substring Preferences --layout float

# Remove a rule
omniwmctl rule remove 550e8400-e29b-41d4-a716-446655440000

# Reapply rules to all windows of a specific app
omniwmctl rule apply --pid 12345

Subscriptions

Subscribe to real-time state change events from OmniWM.

Delivery Pipeline

IPCServer.start() attaches IPCApplicationBridge to WMController. Controller state changes publish channel snapshots through the bridge, and IPCConnection expands the requested channels for each client, sends the initial subscribe response, starts per-channel stream tasks, and emits initial snapshots unless --no-send-initial is set.

Initial snapshots are best-effort seed state, not a strict ordering barrier. If state changes during subscription setup, a live update can race with the initial snapshot.

Subscription channels are coalesced state streams, not a lossless event log. Slow consumers may only observe the newest buffered update for a channel.

Workspace bar and layout refresh work is only produced when the UI or IPC currently has active consumers.

Channels

Channel Result Type Description
focus focused-window Focused window snapshot updates
workspace-bar workspace-bar Workspace bar projection updates
active-workspace active-workspace Interaction monitor and active workspace updates
focused-monitor focused-monitor Focused monitor updates
windows-changed windows Managed window inventory updates
display-changed displays Display state updates
layout-changed workspaces Workspace layout updates

subscribe

Stream the subscribe response and subsequent events to stdout as JSON.

omniwmctl subscribe <channels> [--no-send-initial]
omniwmctl subscribe --all [--no-send-initial]

Channels are specified as a comma-separated list or with --all for all channels.

Flag Description
--all Subscribe to all channels
--no-send-initial Skip sending initial state snapshot

Output is always JSON. Stdout begins with a single pretty-printed IPCResponse envelope with kind: "subscribe" and status: "subscribed". After that, OmniWM emits a best-effort initial state snapshot for each subscribed channel unless --no-send-initial is used, followed by live IPCEventEnvelope updates as they occur.

Examples:

# Watch focus changes
omniwmctl subscribe focus

# Watch all events
omniwmctl subscribe --all

# Watch workspace and window changes without initial state
omniwmctl subscribe active-workspace,windows-changed --no-send-initial

watch

Subscribe to events and execute a command for each event received. The event data is passed to the child process on stdin.

omniwmctl watch <channels> [--no-send-initial] --exec <command> [args...]
omniwmctl watch --all [--no-send-initial] --exec <command> [args...]

The --exec flag is required and marks the boundary between watch flags and the child command. Everything after --exec is the child command and its arguments.

watch consumes the subscribe handshake client-side instead of printing it. It runs one child process per event, waits for that child to finish before handling the next event, writes exactly one NDJSON event line to the child’s stdin, and reports non-zero child exits to stderr without terminating the watcher.

Environment variables passed to child process:

Variable Description
OMNIWM_EVENT_CHANNEL Subscription channel name (e.g., focus)
OMNIWM_EVENT_KIND Event result kind
OMNIWM_EVENT_ID Event ID

The child process inherits the parent’s stdout, stderr, and environment. Bare executable names are resolved through PATH; use an absolute executable path when you want a fixed command target. The event JSON is written to the child’s stdin.

If you persist event streams, prefer a per-user directory such as ~/Library/Logs/OmniWM/ and restrictive permissions such as umask 077.

Examples:

# Log focus changes to a file
mkdir -p ~/Library/Logs/OmniWM
umask 077 && omniwmctl watch focus --exec tee -a ~/Library/Logs/OmniWM/focus.ndjson

# Run a script on workspace changes
omniwmctl watch active-workspace --exec ./on-workspace-change.sh

# Process all events with jq
omniwmctl watch --all --exec jq '.result'

Shell Completion

Generate shell completion scripts for omniwmctl.

omniwmctl completion <zsh|bash|fish>

Setup:

# Zsh — add to ~/.zshrc
eval "$(omniwmctl completion zsh)"

# Bash — add to ~/.bashrc
eval "$(omniwmctl completion bash)"

# Fish — add to ~/.config/fish/config.fish
omniwmctl completion fish | source

Completions are context-aware: query names, selectors, field names, command paths, channel names, rule actions, and argument values are all completed dynamically based on the automation manifest.


Wire Protocol Details

Request Format

{
  "version": 3,
  "id": "<uuid>",
  "kind": "<ping|version|command|query|rule|workspace|window|subscribe>",
  "authorizationToken": "<token>",
  "payload": { ... }
}

Payload varies by kind:

Command:

{
  "name": "focus",
  "arguments": {
    "direction": "left"
  }
}

Query:

{
  "name": "windows",
  "selectors": {
    "workspace": "main",
    "visible": true
  },
  "fields": ["id", "title", "app"]
}

Rule (add):

{
  "name": "add",
  "arguments": {
    "rule": {
      "bundleId": "com.apple.finder",
      "layout": "float"
    }
  }
}

Subscribe:

{
  "channels": ["focus", "active-workspace"],
  "allChannels": false,
  "sendInitial": true
}

Workspace:

{
  "name": "focus-name",
  "workspaceName": "main"
}

Window:

{
  "name": "focus",
  "windowId": "ow_..."
}

Response Format

{
  "version": 3,
  "id": "<request-id>",
  "ok": true,
  "kind": "<ping|version|command|query|rule|workspace|window|subscribe>",
  "status": "<success|executed|ignored|error|subscribed>",
  "code": null,
  "result": {
    "kind": "<pong|version|workspace-bar|active-workspace|focused-monitor|apps|focused-window|windows|workspaces|displays|rules|rule-actions|queries|commands|subscriptions|capabilities|focused-window-decision|reconcile-debug|subscribed>",
    "payload": { ... }
  }
}

Authorization, protocol, validation, and routing failures keep the originating response kind. For example:

{
  "version": 3,
  "id": "<request-id>",
  "ok": false,
  "kind": "query",
  "status": "error",
  "code": "unauthorized"
}

Malformed or oversized request lines fail before routing and are reported as kind: "error" with code: "invalid_request" and an empty request id.

Event Envelope Format

Events are sent on subscription connections after the initial response.

{
  "version": 3,
  "id": "<event-id>",
  "kind": "event",
  "channel": "focus",
  "ok": true,
  "status": "success",
  "result": {
    "kind": "focused-window",
    "payload": { ... }
  }
}

The result type corresponds to the channel’s result kind (see Channels).

CLI-Local JSON Errors

When JSON output is active and omniwmctl fails before or outside the IPC request/response path, it emits a client-side failure envelope instead of an IPCResponse. This is used for argument parsing failures, transport failures, and unexpected internal CLI errors. query and subscribe default to JSON output even without an explicit --json flag.

{
  "ok": false,
  "source": "cli",
  "status": "error",
  "code": "<invalid_arguments|transport_failure|internal_error>",
  "message": "<human-readable error>",
  "exitCode": 3
}

This envelope is produced locally by the CLI, so it does not include IPC fields like version, id, kind, or result. The exitCode matches the CLI-local failure class: 2 for transport failures, 3 for invalid arguments, and 4 for internal errors.


Error Codes

Code Meaning
invalid_request Malformed, oversized, or unparseable request
invalid_arguments Bad arguments for the command/rule
protocol_mismatch Client/server protocol version mismatch
ignored_disabled Window manager is disabled
ignored_overview Overview surface is open
layout_mismatch Command incompatible with the active workspace layout
unauthorized Missing or invalid authorization token
stale_window_id Window ID is from a previous session or no longer valid
not_found Target window, workspace, or rule not found
internal_error Unexpected server-side error

Output Formats

Format Description Default for
json Pretty-printed JSON queries, subscribe
table Aligned columns with headers
tsv Tab-separated values
text Simple human-readable text commands, ping, version

Table output example (windows):

ID    PID    APP       TITLE         WORKSPACE  DISPLAY   MODE     FOCUSED  VISIBLE  SCRATCHPAD
ow_…  1234   Terminal  ~             main       Built-in  tiling   yes      yes      no
ow_…  5678   Safari    GitHub        web        Built-in  tiling   no       yes      no

Environment Variables

Variable Description
OMNIWM_SOCKET Override the default IPC socket path
OMNIWM_EVENT_CHANNEL (watch child) Subscription channel name
OMNIWM_EVENT_KIND (watch child) Event result kind
OMNIWM_EVENT_ID (watch child) Event ID

Aliases

The CLI accepts these aliases transparently:

Alias Resolves to
query monitors query displays
query --monitor query --display
command focus-monitor previous command focus-monitor prev
command switch-workspace previous command switch-workspace prev
command switch-workspace back command switch-workspace back-and-forth