Subscribe to audio: [0x30][bitrate_kbps:2]
Audio is per-compositor (one mixed stream), not per-surface.
The server begins sending S2C_AUDIO_FRAME.
bitrate_kbps: 0 = server default.
Client feature/capability advertisement: [0x2D][payload:N]
Currently defined payload bytes:
[0] codec_support — bitmask of CODEC_SUPPORT_* flags the client can
decode. 0 = accept anything (legacy).
Sent once after connection when capability probing completes. The
message is extensible: the server ignores trailing bytes it doesn’t
understand, and missing bytes default to 0.
Copy text from a range of absolute row/col positions in scrollback + viewport:
[0x1B][nonce:2][pty_id:2][start_tail:4][start_col:2][end_tail:4][end_col:2][flags:1]
start_tail/end_tail: physical row distance from the bottom (0 = last row).
start is the earlier position (closer to top), so start_tail >= end_tail.
flags: reserved (0 for now).
Server responds with S2C_TEXT using the same nonce.
Generic create: [0x18][nonce:2][rows:2][cols:2][features:1][tag_len:2][tag:N][…optional fields]
Features: bit 0 = has src_pty_id (2 bytes after tag), bit 1 = has command (remaining bytes after length-prefixed cwd if present), bit 2 = has cwd ([len:2][utf8])
Server responds with S2C_CREATED_N using the same nonce.
Mouse event: [0x06][pty_id:2][type:1][button:1][col:2][row:2]
type: 0=down, 1=up, 2=move
button: 0=left, 1=mid, 2=right, 3=release, 64=wheel_up, 65=wheel_down
The server generates the correct escape sequence based on mouse_mode and mouse_encoding.
Application-level keepalive: [0x08]. No payload.
Sent periodically by the client; the server treats it as a no-op
(but its arrival resets any server-side receive timeout).
Read text from a PTY’s scrollback + viewport: [0x19][nonce:2][pty_id:2][offset:4][limit:4][flags:1]
offset: number of lines to skip from the top (oldest = 0), or from the end if READ_TAIL is set
limit: max lines to return (0 = all)
flags: bit 0 = include ANSI styling, bit 1 = offset counts from the end
Server responds with S2C_TEXT using the same nonce.
Desired viewport size(s): [0x01][pty_id:2][rows:2][cols:2]…
Clients may batch multiple PTY resize entries in one message. The server
mediates these per-client desired sizes into each PTY’s effective size.
A rows, cols pair of 0, 0 clears this client’s desired size for that PTY.
Pointer motion/button for a Wayland surface: [0x21][surface_id:2][type:1][button:1][x:2][y:2]
type: 0=down, 1=up, 2=move
x,y: pixel coordinates relative to the surface origin
Composed text input for a Wayland surface (UTF-8):
[0x2F][surface_id:2][text:N]
The server synthesises the corresponding evdev key sequences (US-QWERTY)
for ASCII characters. Non-ASCII characters are delivered via
zwp_text_input_v3 commit_string when available.
An encoded audio frame (Opus) from the compositor’s mixed output:
[0x30][timestamp:4][flags:1][data:N]
timestamp: sample offset in 48 kHz ticks from an arbitrary epoch.
flags: bits 1-2 = codec (0 = Opus). Other bits reserved.
The PTY’s subprocess has exited but the terminal state is retained.
Clients can still read/scroll the last frame. Send C2S_CLOSE to dismiss.
Wire: [0x08][pty_id:2][exit_status:4]
exit_status: WEXITSTATUS if normal exit, negative signal number if signalled,
EXIT_STATUS_UNKNOWN if not yet collected.
Application-level keepalive: [0x0B]. No payload.
Sent periodically by the server so clients can detect dead connections
even when no other traffic is flowing (e.g. idle terminal, WebRTC).
Sent after the initial burst (HELLO, LIST, TITLE*, EXITED*) is complete.
Clients can use this to know when the initial state has been fully transmitted.
Screenshot of a surface: [0x27][surface_id:2][width:4][height:4][image_data:N]
image_data is PNG or AVIF depending on the request format.
If the surface was not found or has no buffer, width=0 and height=0 with empty data.
A new Wayland toplevel surface was created:
[0x20][surface_id:2][parent_id:2][width:2][height:2][title_len:2][title:N][app_id_len:2][app_id:N]
parent_id: 0 = no parent (top-level), non-zero = dialog/child of that surface
Encoder backend for a surface: [0x2A][surface_id:2][name:N]
name is a short ASCII string like “h264-nvenc”, “h264-vaapi”, “h264-software”, etc.
Sent when a new encoder is created for a surface (initial subscribe or resize).
An encoded video frame for a Wayland surface:
[0x22][surface_id:2][timestamp:4][flags:1][width:2][height:2][data:N]
flags: bit 0 = keyframe, bits 1-2 = codec (0 = H.264, 1 = AV1).
timestamp: milliseconds since compositor session start.
List of all compositor surfaces:
[0x26][count:2] repeated{ [surface_id:2][parent_id:2][width:2][height:2][title_len:2][title:N][app_id_len:2][app_id:N] }
Text response: [0x0A][nonce:2][pty_id:2][total_lines:4][offset:4][text:N]
nonce: echoed from C2S_READ request
total_lines: total available lines (scrollback + viewport rows)
offset: the offset that was requested
text: UTF-8 text, lines separated by \n
Build S2C_SURFACE_ENCODER: [0x2A][surface_id:2][name\0codec_string].
The codec_string is the WebCodecs codec string (e.g. “av01.2.05M.08”)
appended after a NUL separator. Old clients that don’t split on NUL
will just display the full string as the encoder name, which is fine.
scale_120 is the device-pixel-ratio in 1/120th units, matching
Wayland’s fractional_scale_v1 convention: 120 = 1×, 180 = 1.5×,
240 = 2×. A value of 0 means “unspecified” (server defaults to 1×).
Scaled surface subscribe: ask the server to encode this surface at
exactly width × height for this client, bypassing surface-size
mediation. Intended for side-panel thumbnails and any viewer that
wants a fixed-size stream independent of the compositor’s native
surface size and of other clients’ view sizes.