Overview
You can communicate with the running niri instance over an IPC socket using the niri msg command. This allows you to:
Query compositor state (outputs, workspaces, windows)
Perform actions (focus windows, move workspaces, etc.)
Subscribe to real-time events
Control output configuration
Basic Usage
Check available commands:
The --json flag prints responses in JSON format rather than human-readable:
If you’re getting parsing errors from niri msg after upgrading niri, make sure you’ve restarted niri itself. You might be running a newer niri msg against an older niri compositor.
Available Commands
Here are all the niri msg subcommands:
Query Commands
outputs List connected outputs niri msg outputs
niri msg --json outputs
windows List open windows niri msg windows
niri msg --json windows
layers List open layer-shell surfaces
keyboard-layouts Get configured keyboard layouts niri msg keyboard-layouts
focused-output Print information about the focused output
focused-window Print information about the focused window niri msg --json focused-window
overview-state Print the overview state (open/closed)
version Print the version of the running niri instance
Interactive Commands
pick-window Pick a window with the mouse and print its information # Get window ID interactively
WINDOW_ID = $( niri msg --json pick-window | jq -r .id )
pick-color Pick a color from the screen with the mouse niri msg --json pick-color
Action Commands
Perform any niri action via IPC:
niri msg action < ACTIO N > [OPTIONS]
Window Management Actions
# Focus a window by ID
niri msg action focus-window --id 12345
# Close the focused window
niri msg action close-window
# Close a specific window
niri msg action close-window --id 12345
# Toggle fullscreen
niri msg action fullscreen-window
# Toggle windowed (fake) fullscreen
niri msg action toggle-windowed-fullscreen
# Focus navigation
niri msg action focus-column-left
niri msg action focus-column-right
niri msg action focus-window-down
niri msg action focus-window-up
# Move windows
niri msg action move-window-down
niri msg action move-window-up
niri msg action move-column-left
niri msg action move-column-right
# Focus workspace by index
niri msg action focus-workspace 3
# Focus workspace by name
niri msg action focus-workspace "Development"
# Navigate workspaces
niri msg action focus-workspace-down
niri msg action focus-workspace-up
niri msg action focus-workspace-previous
# Move window to workspace
niri msg action move-window-to-workspace 2
niri msg action move-window-to-workspace "Browser"
# Move without following focus
niri msg action move-window-to-workspace --no-focus 5
# Move entire column
niri msg action move-column-to-workspace 3
# Set/unset workspace name
niri msg action set-workspace-name "My Workspace"
niri msg action unset-workspace-name
# Focus monitors
niri msg action focus-monitor-left
niri msg action focus-monitor-right
niri msg action focus-monitor-up
niri msg action focus-monitor-down
# Move window to monitor
niri msg action move-window-to-monitor "HDMI-A-1"
niri msg action move-window-to-monitor-left
# Move workspace to monitor
niri msg action move-workspace-to-monitor "DP-1"
# Resize window
niri msg action set-window-height 800
niri msg action set-window-height +100
niri msg action set-window-height -- -50
niri msg action set-window-width 50%
# Reset window height
niri msg action reset-window-height
# Column display modes
niri msg action toggle-column-tabbed-display
niri msg action set-column-display normal
niri msg action set-column-display tabbed
# Center windows
niri msg action center-column
niri msg action center-window
# Maximize
niri msg action maximize-column
niri msg action maximize-window-to-edges
# Dynamic cast target
niri msg action set-dynamic-cast-window
niri msg action set-dynamic-cast-monitor
niri msg action clear-dynamic-cast-target
# Interactive window selection
niri msg action set-dynamic-cast-window --id $( niri msg --json pick-window | jq .id )
# Stop a screencast
niri msg action stop-cast --session-id 12345
# Open screenshot UI
niri msg action screenshot
# Screenshot the focused screen
niri msg action screenshot-screen
# Screenshot window
niri msg action screenshot-window
niri msg action screenshot-window --id 12345
# Custom save path
niri msg action screenshot-screen --path /tmp/screenshot.png
# Spawn commands
niri msg action spawn -- alacritty
niri msg action spawn-sh "notify-send Hello"
# Power management
niri msg action power-off-monitors
niri msg action power-on-monitors
# Compositor control
niri msg action quit
niri msg action quit --skip-confirmation
niri msg action load-config-file
niri msg action load-config-file --path /path/to/config.kdl
# Keyboard layout
niri msg action switch-layout next
niri msg action switch-layout prev
# Toggle floating
niri msg action toggle-window-floating
# Move to/from floating
niri msg action move-window-to-floating
niri msg action move-window-to-tiling
# Switch focus
niri msg action focus-floating
niri msg action focus-tiling
niri msg action switch-focus-between-floating-and-tiling
# Move floating window
niri msg action move-floating-window --x +100 --y -50
niri msg action move-floating-window --x 10% --y 20%
Output Configuration
Change output configuration temporarily (not saved to config):
niri msg output < OUTPUT_NAM E > < ACTIO N >
Power Control
Mode Settings
Scale & Transform
Position
VRR (Variable Refresh Rate)
# Turn output off
niri msg output HDMI-A-1 off
# Turn output on
niri msg output HDMI-A-1 on
Event Stream
Available since version 0.1.9
The event stream request makes niri continuously stream events into the IPC connection until it is closed. This is useful for implementing bars and indicators that update in real-time without polling.
Key Features
Complete state up-front : The event stream gives you the complete current state first, then follows up with updates
No polling needed : Events are pushed to you as they happen
Atomic updates : Most state updates are atomic (though not always)
Usage
# Debug view of events (human-readable)
niri msg event-stream
# JSON events for programmatic use
niri msg --json event-stream
Example: Monitor Workspaces
#!/bin/bash
niri msg --json event-stream | while IFS = read -r line ; do
event_type = $( echo " $line " | jq -r 'keys[0]' )
case " $event_type " in
WorkspacesChanged )
echo "Workspaces updated:"
echo " $line " | jq '.WorkspacesChanged.workspaces[] | "\(.idx): \(.name // "<unnamed>")"'
;;
WindowOpenedOrChanged )
window_title = $( echo " $line " | jq -r '.WindowOpenedOrChanged.window.title' )
echo "Window opened/changed: $window_title "
;;
WorkspaceActivated )
workspace_id = $( echo " $line " | jq -r '.WorkspaceActivated.id' )
echo "Workspace activated: $workspace_id "
;;
esac
done
Available Events
WorkspacesChanged Complete workspace configuration update
WorkspaceActivated A workspace was activated on an output
WindowsChanged Complete window configuration update
WindowOpenedOrChanged A window was opened or changed
WindowClosed A window was closed
WindowFocusChanged Window focus changed
KeyboardLayoutsChanged Keyboard layouts configuration changed
KeyboardLayoutSwitched Active keyboard layout switched
OverviewOpenedOrClosed Overview was opened or closed
ConfigLoaded Configuration was (re)loaded
ScreenshotCaptured A screenshot was captured
CastsChanged Screencasts configuration changed
Full Event Documentation View the complete list of events with detailed documentation
Programmatic Access
For more complex scripts and modules, you can access the socket directly instead of using niri msg --json.
Socket Protocol
Connect to Socket
Connect to the UNIX domain socket at $NIRI_SOCKET: echo $NIRI_SOCKET
# /run/user/1000/niri.wayland-1.2474.sock
Write Request
Write your request encoded in JSON on a single line, followed by a newline or by flushing/shutting down the write end.
Read Reply
Read the reply as JSON, also on a single line.
Testing with socat
# Simple request
$ socat STDIO " $NIRI_SOCKET "
"FocusedWindow"
{ "Ok" : { " FocusedWindow ":{" id ":12," title ":" Terminal "," app_id ":" Alacritty "," workspace_id ":6," is_focused ":true}}}
You can use socat to see how niri msg formats requests:
# Terminal 1: Create a test socket
$ socat STDIO UNIX-LISTEN:temp.sock
# Terminal 2: Send a command
$ env NIRI_SOCKET=./temp.sock niri msg action focus-workspace 2
# Terminal 1 shows:
{ "Action" : { " FocusWorkspace ":{" reference ":{" Index ":2}}}}
Python Example
import socket
import json
import os
def niri_request ( request ):
sock_path = os.environ[ 'NIRI_SOCKET' ]
sock = socket.socket(socket. AF_UNIX , socket. SOCK_STREAM )
sock.connect(sock_path)
# Send request
sock.sendall((json.dumps(request) + ' \n ' ).encode())
sock.shutdown(socket. SHUT_WR )
# Read response
response = b ''
while True :
chunk = sock.recv( 4096 )
if not chunk:
break
response += chunk
sock.close()
return json.loads(response)
# Get focused window
result = niri_request( "FocusedWindow" )
if 'Ok' in result and result[ 'Ok' ][ 'FocusedWindow' ]:
window = result[ 'Ok' ][ 'FocusedWindow' ]
print ( f "Focused window: { window[ 'title' ] } " )
# Perform an action
result = niri_request({
"Action" : {
"FocusWorkspace" : {
"reference" : { "Index" : 3 }
}
}
})
The reply is an Ok or an Err wrapping the same JSON object as you get from niri msg --json:
// Success
{ "Ok" :{ "FocusedWindow" :{ "id" : 12 , "title" : "Terminal" , ... }}}
// Error
{ "Err" : "window with id 99999 not found" }
API Documentation View the complete niri-ipc sub-crate documentation for all request and response types
Backwards Compatibility
JSON Output Stability
The JSON output should remain stable:
Existing fields and enum variants will not be renamed
Non-optional existing fields will not be removed
New fields and enum variants will be added. Handle unknown fields gracefully.
Human-Readable Output
The formatted output (without --json flag) is not considered stable. Always use --json for scripts.
Rust API
The niri-ipc sub-crate follows niri’s version and is not API-stable in terms of Rust semver. New struct fields and enum variants will be added in patch releases.
# Use exact version in Cargo.toml
[ dependencies ]
niri-ipc = "=25.11.0"