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.
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.
The omniwmctl binary is bundled inside the OmniWM app at:
OmniWM.app/Contents/MacOS/omniwmctl
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:
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.
Protocol version: 3
| 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.
The protocol uses newline-delimited JSON (NDJSON) — one JSON object per line, terminated by 0x0A.
camelCaseExamples in this document are pretty-printed for readability. The actual wire format is compact single-line JSON with the same field names.
0600 (owner-only read/write)07000600 (owner-only read/write)getpeereid()<socket-path>.secretow_ + base64url(sessionToken:pid:windowId)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.
omniwmctl <command> [arguments...] [--format json|table|tsv|text] [--json]
| 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.
| Flag | Description |
|---|---|
--format <format> |
Output format: json, table, tsv, text |
--json |
Alias for --format json |
Global flags must appear before --exec in watch commands.
| 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 |
Execute window manager commands. These invoke the same code path as hotkey-bound commands.
omniwmctl command <command-path> [arguments...]
| 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 |
| Command | Arguments | Layout | Description |
|---|---|---|---|
command move |
<left\|right\|up\|down> |
shared | Move the focused window in the given direction |
| 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 |
| 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+.
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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:
shared — works with any active layoutniri — only works when the active workspace uses the Niri layoutdwindle — only works when the active workspace uses the Dwindle layoutCommands sent to an incompatible layout return layout_mismatch.
omniwmctl query <name> [selectors...] [--fields <field1,field2,...>] [--format json|table|tsv|text]
Default output format for queries is json.
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 |
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 | 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.
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).
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.
Manage persisted window rules that control how windows are tiled, floated, or assigned to workspaces.
omniwmctl rule <action> [arguments...] [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]+)*$
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
Subscribe to real-time state change events from OmniWM.
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.
| 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 |
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
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'
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.
{
"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_..."
}
{
"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.
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).
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.
| 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 |
| 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
| 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 |
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 |