/* * VTX — VT Export Protocol * * This header defines the shared memory and socket protocol used between * a VTX server (e.g., kmscon) and VTX clients (e.g., BRLTTY). * * The protocol uses two communication channels: * - A shared memory segment for screen cell data (read-only for clients) * - A Unix domain socket for control operations and event notifications * * Both channels use TLV (type-length-value) framing with a unified type * numbering space. The same parsing code handles shm header fields and * socket messages. All multi-byte values use native byte order. * Unknown TLV types must be silently ignored by both sides. * * See vtx-protocol.md for the full design document. * * Implementors wishing to extend the protocol should coordinate new * TLV type assignments to avoid conflicts. Please submit proposals to * the VTX maintainers rather than allocating types independently. * * Copyright (c) 2026 Nicolas Pitre * SPDX-License-Identifier: MIT */ #ifndef VTX_PROTOCOL_H #define VTX_PROTOCOL_H #include /* * Protocol version. Only changes if the preamble or TLV framing format * itself changes. Adding new TLV types does not require a version bump. */ #define VTX_PROTOCOL_VERSION 1 /* * Magic number identifying a VTX shm segment. * ASCII "VTXs" in little-endian. */ #define VTX_SHM_MAGIC 0x73585456 /* * Well-known socket directory. * Owned by root:vtx, mode 0750. Sockets within are mode 0660, group vtx. * Socket naming is server-specific (e.g., kmscon_seat0.sock). * The socket uses AF_UNIX SOCK_SEQPACKET for message-boundary * preservation (each sendmsg/recvmsg is one atomic message). */ #define VTX_SOCKET_DIR "/run/vtx" /* ------------------------------------------------------------------ */ /* Shared memory layout */ /* ------------------------------------------------------------------ */ /* * The shm segment starts with a fixed preamble, followed by a * variable-length TLV header, followed by the cell array. */ struct vtx_shm_preamble { uint32_t magic; /* VTX_SHM_MAGIC */ uint16_t version; /* VTX_PROTOCOL_VERSION */ uint16_t header_size; /* Total header size (preamble + TLV) */ uint32_t shm_size; /* Total shm segment size in bytes */ }; /* ------------------------------------------------------------------ */ /* TLV framing */ /* ------------------------------------------------------------------ */ /* * Each TLV entry has a 4-byte header (type + length) followed by the * value. The value is padded to a 4-byte boundary. The length field * contains the actual data size; the walker rounds up to find the next * entry. */ struct vtx_tlv_entry { uint16_t type; uint16_t length; /* followed by value bytes, padded to 4-byte alignment */ }; /* Sentinel marking end of TLV area in the shm header */ #define VTX_TLV_END 0x0000 /* Round a length up to the next 4-byte boundary */ #define VTX_TLV_PADDED_LEN(len) (((len) + 3) & ~3) /* Advance past a TLV entry to the next one */ #define VTX_TLV_NEXT(tlv) \ ((struct vtx_tlv_entry *)((uint8_t *)(tlv) + \ sizeof(struct vtx_tlv_entry) + \ VTX_TLV_PADDED_LEN((tlv)->length))) /* ------------------------------------------------------------------ */ /* Type ranges */ /* ------------------------------------------------------------------ */ /* * 0x0001-0x00FF: Shared data types, used in both the shm header (as a * full state snapshot) and socket messages (as incremental * updates). * 0x0100-0x01FF: Server -> client control messages (socket only). * 0x0200-0x02FF: Client -> server control messages (socket only). */ /* --- Shared data types (0x0001-0x00FF) --- */ /* uint16_t cols, uint16_t rows */ #define VTX_TLV_SCREEN_DIMENSIONS 0x0001 /* uint16_t col, uint16_t row */ #define VTX_TLV_CURSOR_POSITION 0x0002 /* uint32_t flags (see VTX_STATE_* below) */ #define VTX_TLV_TERMINAL_STATE 0x0003 /* uint16_t col, uint16_t row */ #define VTX_TLV_MOUSE_POSITION 0x0004 /* uint16_t number */ #define VTX_TLV_ACTIVE_SESSION 0x0005 /* * uint32_t offset, uint32_t count, uint16_t stride, uint16_t format_version * offset: byte offset from the start of the shm segment to the cell array */ #define VTX_TLV_CELL_DESCRIPTOR 0x0006 /* * uint32_t offset, uint32_t size * offset: byte offset from the start of the shm segment to the overflow area * size: size of the overflow area in bytes */ #define VTX_TLV_OVERFLOW_DESCRIPTOR 0x0007 /* --- Server -> client control types (0x0100-0x01FF) --- */ /* 0x0100-0x010F: screen/shm messages */ /* * Screen update notification. * Payload: uint32_t sequence_number, uint32_t changes. * The changes bitfield indicates what has changed in the shm since the * last acknowledged update. Bits are cumulative — if the client is slow * to ack, multiple changes are OR'd together. The client reads the * relevant parts of the shm based on the bits set. */ #define VTX_MSG_SCREEN_UPDATED 0x0100 /* VTX_MSG_SCREEN_UPDATED change bits */ #define VTX_CHANGE_CELLS (1 << 0) /* cell content changed */ #define VTX_CHANGE_CURSOR_POS (1 << 1) /* cursor position moved */ #define VTX_CHANGE_TERMINAL_STATE (1 << 2) /* terminal flags changed */ #define VTX_CHANGE_MOUSE_POS (1 << 3) /* mouse pointer moved */ /* * New shm segment. Payload: uint32_t map_size, uint32_t flags. * Shm fd passed out-of-band via SCM_RIGHTS. The client must remap * using the new fd and map_size. Flags indicate the reason(s) for * the new segment. There is always a valid shm segment; if no * terminal session is active the cell count is 0. */ #define VTX_MSG_SHM_UPDATE 0x0101 /* VTX_MSG_SHM_UPDATE flag bits */ #define VTX_SHM_INITIAL (1 << 0) /* first shm after connect */ #define VTX_SHM_RESIZE (1 << 1) /* screen dimensions changed */ #define VTX_SHM_SESSION (1 << 2) /* active session changed */ #define VTX_SHM_OVERFLOW (1 << 3) /* overflow area grew */ /* no payload — the terminal bell was rung */ #define VTX_MSG_BELL 0x0102 /* 0x0110-0x011F: keyboard messages */ /* * Key event offer. * Normal: 7 bytes — uint16_t keycode, uint8_t value, uint32_t modifiers * End-of-offers: 0 bytes — server has stopped offering key events * (either because the client disabled key offers, or because the * client timed out on acknowledgement) * * The client must send Key event accepted (0x0210) after receiving one * or more key offers, no later than 1 second after the first unacked * offer. If the client fails to ack in time, the server automatically * disables key offers for that client and sends an empty key offer * (length=0) to signal this. The client must re-enable key offers * (0x0211) if it wishes to resume interception. */ #define VTX_MSG_KEY_OFFER 0x0110 /* Key offer timeout in milliseconds */ #define VTX_KEY_OFFER_TIMEOUT_MS 1000 /* --- Client -> server control types (0x0200-0x02FF) --- */ /* 0x0200-0x020F: screen/update messages */ /* uint32_t sequence_number — acknowledges receipt of screen update */ #define VTX_MSG_UPDATE_ACK 0x0200 /* uint16_t left, uint16_t top, uint16_t right, uint16_t bottom */ #define VTX_MSG_HIGHLIGHT 0x0201 /* no payload */ #define VTX_MSG_UNHIGHLIGHT 0x0202 /* uint16_t number */ #define VTX_MSG_SWITCH_SESSION 0x0203 /* no payload — request mouse position updates in VTX_CHANGE_MOUSE_POS */ #define VTX_MSG_ENABLE_MOUSE_TRACKING 0x0204 /* no payload */ #define VTX_MSG_DISABLE_MOUSE_TRACKING 0x0205 /* 0x0210-0x021F: keyboard messages */ /* no payload */ #define VTX_MSG_KEY_ACCEPTED 0x0210 /* no payload */ #define VTX_MSG_ENABLE_KEY_OFFERS 0x0211 /* no payload */ #define VTX_MSG_DISABLE_KEY_OFFERS 0x0212 /* 0x0220-0x022F: input injection */ /* uint16_t keycode, uint8_t value, uint8_t padding, uint32_t modifiers */ #define VTX_MSG_KEY_EVENT_INJECT 0x0220 /* uint32_t codepoint (UCS-4) */ #define VTX_MSG_CHAR_INJECT 0x0221 /* uint16_t col, uint16_t row, uint8_t button */ #define VTX_MSG_MOUSE_CLICK 0x0222 /* ------------------------------------------------------------------ */ /* Terminal state flags (TLV type 0x0003) */ /* ------------------------------------------------------------------ */ #define VTX_STATE_CURSOR_VISIBLE (1 << 0) #define VTX_STATE_CURSOR_BLINKING (1 << 1) #define VTX_STATE_CURSOR_SHAPE_MASK (3 << 2) #define VTX_STATE_CURSOR_SHAPE_DEFAULT (0 << 2) #define VTX_STATE_CURSOR_SHAPE_BLOCK (1 << 2) #define VTX_STATE_CURSOR_SHAPE_UNDERLINE (2 << 2) #define VTX_STATE_CURSOR_SHAPE_BAR (3 << 2) #define VTX_STATE_BRACKETED_PASTE (1 << 4) #define VTX_STATE_MOUSE_MODE (1 << 5) #define VTX_STATE_GRAPHICS_MODE (1 << 6) /* no text content */ /* ------------------------------------------------------------------ */ /* Cell format */ /* ------------------------------------------------------------------ */ /* * Each cell is 12 bytes, naturally aligned. * The cell array in shm is laid out as rows * cols cells in row-major * order. */ struct vtx_shm_cell { uint32_t codepoint; /* Unicode codepoint (UCS-4) */ uint16_t flags; /* Width + attribute flags */ uint8_t fr, fg, fb; /* Foreground color (RGB) */ uint8_t br, bg, bb; /* Background color (RGB) */ }; #define VTX_CELL_FORMAT_VERSION 1 /* Cell flags: bits 0-1 encode character width */ #define VTX_CELL_WIDTH_MASK 0x0003 #define VTX_CELL_SINGLE_WIDTH 0x0001 /* width=1 */ #define VTX_CELL_DOUBLE_WIDTH 0x0002 /* width=2 */ /* 0x0000 = continuation */ /* Cell flags: bits 2-7 encode text attributes */ #define VTX_CELL_BOLD (1 << 2) #define VTX_CELL_ITALIC (1 << 3) #define VTX_CELL_UNDERLINE (1 << 4) #define VTX_CELL_BLINK (1 << 5) #define VTX_CELL_INVERSE (1 << 6) /* * Combining codepoint / grapheme cluster support * * A grapheme cluster (what the user sees as one character) may consist * of a base codepoint plus one or more combining codepoints. The cell * format handles this in three layers: * * 1. Common case: The cell's codepoint field holds a single UCS-4 * value. No combining marks. * * 2. Double-width characters: The continuation cell(s) (width=0) can * hold additional codepoints for combining marks. The reader * collects codepoints from the primary cell and its continuation * cells to form the full cluster. * * 3. Overflow: When more codepoints are needed than fit in available * cells (e.g., single-width base with combiners, or more combiners * than continuation cells), the cell's codepoint field is set to * a special value: 0xFF000000 | offset. The offset is a byte offset * from the start of the shm segment to the overflow entry. * The overflow entry contains the full codepoint sequence as: * uint32_t count; (number of codepoints, uint32 for alignment; * longest known natural-language cluster is * about 9 codepoints) * uint32_t codepoints[]; (the codepoint sequence) * * The overflow area is described by TLV type VTX_TLV_OVERFLOW_DESCRIPTOR. * Its location and size are part of the shm segment whose total size * is given in the preamble's shm_size field. * * The overflow area is allocated on demand. If no combining codepoints * are present, the TLV entry is absent and no memory is used. The shm * segment is allocated in page-sized increments; the gap between * shm_size (actual data extent) and the mapping size (sent with the fd) * is free space for overflow growth. When overflow entries are written, * shm_size is updated and a normal screen update notification is sent. * If the mapping is exhausted, a new shm segment is created. */ #define VTX_CELL_OVERFLOW_MARKER 0xFF000000 #define VTX_CELL_IS_OVERFLOW(cp) (((cp) & 0xFF000000) == 0xFF000000) #define VTX_CELL_OVERFLOW_OFFSET(cp) ((cp) & 0x00FFFFFF) /* ------------------------------------------------------------------ */ /* Key event encoding */ /* ------------------------------------------------------------------ */ /* * Key events (types VTX_MSG_KEY_OFFER and VTX_MSG_KEY_EVENT_INJECT) * use the Linux input subsystem encoding: * * - keycode: KEY_* constants from * - value: 0 = release, 1 = press, 2 = repeat * - modifiers: bitmask of active modifiers (see VTX_MOD_* below) */ #define VTX_MOD_SHIFT (1 << 0) #define VTX_MOD_CAPSLOCK (1 << 1) #define VTX_MOD_CONTROL (1 << 2) #define VTX_MOD_ALT (1 << 3) #define VTX_MOD_LOGO (1 << 4) #endif /* VTX_PROTOCOL_H */