IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
-October 2014
+September 2017
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
in the IPC API is done which breaks compatibility (we hope that we don’t need
to do that).
-Currently implemented message types are the following:
-
-COMMAND (0)::
- The payload of the message is a command for i3 (like the commands you
- can bind to keys in the configuration file) and will be executed
- directly after receiving it.
-GET_WORKSPACES (1)::
- Gets the current workspaces. The reply will be a JSON-encoded list of
- workspaces (see the reply section).
-SUBSCRIBE (2)::
- Subscribes your connection to certain events. See <<events>> for a
- description of this message and the concept of events.
-GET_OUTPUTS (3)::
- Gets the current outputs. The reply will be a JSON-encoded list of outputs
- (see the reply section).
-GET_TREE (4)::
- Gets the layout tree. i3 uses a tree as data structure which includes
- every container. The reply will be the JSON-encoded tree (see the reply
- section).
-GET_MARKS (5)::
- Gets a list of marks (identifiers for containers to easily jump to them
- later). The reply will be a JSON-encoded list of window marks (see
- reply section).
-GET_BAR_CONFIG (6)::
- Gets the configuration (as JSON map) of the workspace bar with the
- given ID. If no ID is provided, an array with all configured bar IDs is
- returned instead.
-GET_VERSION (7)::
- Gets the version of i3. The reply will be a JSON-encoded dictionary
- with the major, minor, patch and human-readable version.
-GET_BINDING_MODES (8)::
- Gets a list of currently configured binding modes.
+.Currently implemented message types
+[options="header",cols="^10%,^20%,^20%,^50%"]
+|======================================================
+| Type (numeric) | Type (name) | Reply type | Purpose
+| 0 | +RUN_COMMAND+ | <<_command_reply,COMMAND>> | Run the payload as an i3 command (like the commands you can bind to keys).
+| 1 | +GET_WORKSPACES+ | <<_workspaces_reply,WORKSPACES>> | Get the list of current workspaces.
+| 2 | +SUBSCRIBE+ | <<_subscribe_reply,SUBSCRIBE>> | Subscribe this IPC connection to the event types specified in the message payload. See <<events>>.
+| 3 | +GET_OUTPUTS+ | <<_outputs_reply,OUTPUTS>> | Get the list of current outputs.
+| 4 | +GET_TREE+ | <<_tree_reply,TREE>> | Get the i3 layout tree.
+| 5 | +GET_MARKS+ | <<_marks_reply,MARKS>> | Gets the names of all currently set marks.
+| 6 | +GET_BAR_CONFIG+ | <<_bar_config_reply,BAR_CONFIG>> | Gets the specified bar configuration or the names of all bar configurations if payload is empty.
+| 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
+| 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
+| 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
+| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
+| 11 | +SYNC+ | <<_sync_reply,SYNC>> | Sends an i3 sync event with the specified random value to the specified window.
+|======================================================
So, a typical message could look like this:
--------------------------------------------------
The following reply types are implemented:
COMMAND (0)::
- Confirmation/Error code for the COMMAND message.
+ Confirmation/Error code for the RUN_COMMAND message.
WORKSPACES (1)::
Reply to the GET_WORKSPACES message.
SUBSCRIBE (2)::
Reply to the GET_VERSION message.
BINDING_MODES (8)::
Reply to the GET_BINDING_MODES message.
+GET_CONFIG (9)::
+ Reply to the GET_CONFIG message.
+TICK (10)::
+ Reply to the SEND_TICK message.
+[[_command_reply]]
=== COMMAND reply
The reply consists of a list of serialized maps for each command that was
[{ "success": true }]
-------------------
+[[_workspaces_reply]]
=== WORKSPACES reply
The reply consists of a serialized list of workspaces. Each workspace has the
]
-------------------
+[[_subscribe_reply]]
=== SUBSCRIBE reply
The reply consists of a single serialized map. The only property is
{ "success": true }
-------------------
+[[_outputs_reply]]
=== OUTPUTS reply
The reply consists of a serialized list of outputs. Each output has the
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
active (boolean)::
Whether this output is currently active (has a valid mode).
+primary (boolean)::
+ Whether this output is currently the primary output.
current_workspace (string)::
The name of the current workspace that is visible on this output. +null+ if
the output is not active.
"y": 0,
"width": 1280,
"height": 1024
- },
+ }
}
]
-------------------
+[[_tree_reply]]
=== TREE reply
The reply consists of a serialized tree. Each node in the tree (representing
This field is set to null for split containers or otherwise empty
containers. This ID corresponds to what xwininfo(1) and other
X11-related tools display (usually in hex).
+window_properties (map)::
+ X11 window properties title, instance, class, window_role and transient_for.
urgent (bool)::
- Whether this container (window or workspace) has the urgency hint set.
+ Whether this container (window, split container, floating container or
+ workspace) has the urgency hint set, directly or indirectly. All parent
+ containers up until the workspace container will be marked urgent if they
+ have at least one urgent child.
focused (bool)::
Whether this container is currently focused.
+focus (array of integer)::
+ List of child node IDs (see +nodes+, +floating_nodes+ and +id+) in focus
+ order. Traversing the tree by following the first entry in this array
+ will result in eventually reaching the one node with +focused+ set to
+ true.
+nodes (array of node)::
+ The tiling (i.e. non-floating) child containers of this node.
+floating_nodes (array of node)::
+ The floating child containers of this node. Only non-empty on nodes with
+ type +workspace+.
Please note that in the following example, I have left out some keys/values
which are not relevant for the type of the node. Otherwise, the example would
"y": 0,
"width": 1280,
"height": 0
- },
+ }
},
{
"width": 1280,
"height": 782
},
+ "window_properties": {
+ "class": "Evince",
+ "instance": "evince",
+ "title": "Properties",
+ "transient_for": 52428808
+ },
"floating_nodes": [],
"nodes": [
}
------------------------
+[[_marks_reply]]
=== MARKS reply
The reply consists of a single array of strings for each container that has a
If no window has a mark the response will be the empty array [].
+[[_bar_config_reply]]
=== BAR_CONFIG reply
This can be used by third-party workspace bars (especially i3bar, but others
}
--------------
+[[_version_reply]]
=== VERSION reply
The reply consists of a single JSON dictionary with the following keys:
}
-------------------
+[[_binding_modes_reply]]
=== BINDING_MODES reply
The reply consists of an array of all currently configured binding modes.
["default", "resize"]
---------------------
+[[_config_reply]]
+=== CONFIG reply
+
+The config reply is a map which currently only contains the "config" member,
+which is a string containing the config file as loaded by i3 most recently.
+
+*Example:*
+-------------------
+{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
+-------------------
+
+[[_tick_reply]]
+=== TICK reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the tick event has been written to all IPC connections which subscribe
+to tick events. UNIX sockets are usually buffered, but you can be certain that
+once you receive the tick event you just triggered, you must have received all
+events generated prior to the +SEND_TICK+ message (happened-before relation).
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
+
+[[_sync_reply]]
+=== SYNC reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
+responded to.
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
+
== Events
[[events]]
mouse
shutdown (6)::
Sent when the ipc shuts down because of a restart or exit by user command
+tick (7)::
+ Sent when the ipc client subscribes to the tick event (with +"first":
+ true+) or when any ipc client sends a SEND_TICK message (with +"first":
+ false+).
*Example:*
--------------------------------------------------------------------
This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init",
-"empty", "urgent"). A +current (object)+ property will be present with the
-affected workspace whenever the type of event affects a workspace (otherwise,
-it will be +null).
+"empty", "urgent", "reload", "rename", "restored", "move"). A
++current (object)+ property will be present with the affected workspace
+whenever the type of event affects a workspace (otherwise, it will be +null).
When the change is "focus", an +old (object)+ property will be present with the
previous workspace. When the first switch occurs (when i3 focuses the
=== binding event
This event consists of a single serialized map reporting on the details of a
-binding that ran a command because of user input. The +change (sring)+ field
+binding that ran a command because of user input. The +change (string)+ field
indicates what sort of binding event was triggered (right now it will always be
+"run"+ but may be expanded in the future).
}
---------------------------
+=== tick event
+
+This event is triggered by a subscription to tick events or by a +SEND_TICK+
+message.
+
+*Example (upon subscription):*
+--------------------------------------------------------------------------------
+{
+ "first": true,
+ "payload": ""
+}
+--------------------------------------------------------------------------------
+
+*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
+--------------------------------------------------------------------------------
+{
+ "first": false,
+ "payload": "arbitrary string"
+}
+--------------------------------------------------------------------------------
+
== See also (existing libraries)
[[libraries]]
C++::
* https://github.com/drmgc/i3ipcpp
Go::
- * https://github.com/proxypoke/i3ipc
+ * https://github.com/mdirkse/i3ipc-go
+ * https://github.com/i3/go-i3
JavaScript::
* https://github.com/acrisci/i3ipc-gjs
Lua::
* https://github.com/badboy/i3-ipc (not maintained)
Rust::
* https://github.com/tmerr/i3ipc-rs
+OCaml::
+ * https://github.com/Armael/ocaml-i3ipc
+
+== Appendix A: Detecting byte order in memory-safe languages
+
+Some programming languages such as Go don’t offer a way to serialize data in the
+native byte order of the machine they’re running on without resorting to tricks
+involving the +unsafe+ package.
+
+The following technique can be used (and will not be broken by changes to i3) to
+detect the byte order i3 is using:
+
+1. The byte order dependent fields of an IPC message are message type and
+ payload length.
+
+ * The message type +RUN_COMMAND+ (0) is the same in big and little endian, so
+ we can use it in either byte order to elicit a reply from i3.
+
+ * The payload length 65536 + 256 (+0x00 01 01 00+) is the same in big and
+ little endian, and also small enough to not worry about memory allocations
+ of that size. We must use payloads of length 65536 + 256 in every message
+ we send, so that i3 will be able to read the entire message regardless of
+ the byte order it uses.
+
+2. Send a big endian encoded message of type +SUBSCRIBE+ (2) with payload `[]`
+ followed by 65536 + 256 - 2 +SPACE+ (ASCII 0x20) bytes.
+
+ * If i3 is running in big endian, this message is treated as a noop,
+ resulting in a +SUBSCRIBE+ reply with payload `{"success":true}`
+ footnote:[A small payload is important: that way, we circumvent dealing
+ with UNIX domain socket buffer sizes, whose size depends on the
+ implementation/operating system. Exhausting such a buffer results in an i3
+ deadlock unless you concurrently read and write, which — depending on the
+ programming language — makes the technique much more complicated.].
+
+ * If i3 is running in little endian, this message is read in its entirety due
+ to the byte order independent payload length, then
+ https://github.com/i3/i3/blob/d726d09d496577d1c337a4b97486f2c9fbc914f1/src/ipc.c#L1188[silently
+ discarded] due to the unknown message type.
+
+3. Send a byte order independent message, i.e. type +RUN_COMMAND+ (0) with
+ payload +nop byte order detection. padding:+, padded to 65536 + 256 bytes
+ with +a+ (ASCII 0x61) bytes. i3 will reply to this message with a reply of
+ type +COMMAND+ (0).
+
+ * The human-readable prefix is in there to not confuse readers of the i3 log.
+
+ * This messages serves as a synchronization primitive so that we know whether
+ i3 discarded the +SUBSCRIBE+ message or didn’t answer it yet.
+
+4. Receive a message header from i3, decoding the message type as big endian.
+
+ * If the message’s reply type is +COMMAND+ (0), i3 is running in little
+ endian (because the +SUBSCRIBE+ message was discarded). Decode the message
+ payload length as little endian, receive the message payload.
+
+ * If the message’s reply type is anything else, i3 is running in big endian
+ (because our big endian encoded +SUBSCRIBE+ message was answered). Decode
+ the message payload length in big endian, receive the message
+ payload. Then, receive the pending +COMMAND+ message reply in big endian.
+
+5. From here on out, send/receive all messages using the detected byte order.
+
+Find an example implementation of this technique in
+https://github.com/i3/go-i3/blob/master/byteorder.go