-
Notifications
You must be signed in to change notification settings - Fork 9
How It Works
A technical overview of FXCommands for contributors and curious users.
Stream Deck (hardware/software)
|
| KeyDown / KeyUp events via Stream Deck SDK
v
FXCommandAction (src/actions/fx-command.ts)
|
| Parses delay syntax, manages button state
v
ConnectionManager (src/connection-manager.ts)
|
| TCP socket to localhost:29200
v
FiveM / RedM Console
The plugin runs as a Node.js process managed by the Stream Deck software. It communicates with the Stream Deck via the Elgato Stream Deck SDK 2.0 and with the game via a raw TCP connection.
FiveM and RedM expose a local console server on port 29200 that accepts commands via a binary protocol called CMND (IceCon-style).
Each command is wrapped in a binary frame:
Offset Size Field Value
0 4 Magic 0x43 0x4D 0x4E 0x44 ("CMND")
4 2 Protocol 0x00 0xD3 (211, big-endian)
6 4 Length Command byte length + 1 (big-endian uint32)
10 2 Padding 0x00 0x00
12 N Command UTF-8 encoded string + newline (\n)
12+N 1 Terminator 0x00
The ConnectionManager builds this frame and writes it to the TCP socket. The game processes the command as if it were typed into the console.
-
Pre-connect on appear - When a button appears on the Stream Deck (profile load, app start), the plugin opens a TCP connection to
localhost:29200so the first key press sends immediately with no connection delay. -
Lazy reconnect - If the connection drops (game closed, network reset), the next
send()call attempts to reconnect before sending. -
Persistent socket - A single TCP connection is reused across all button presses. The socket uses
TCP_NODELAYfor minimal latency andkeepAlive(10s interval) to detect dead connections. -
Graceful cleanup - When a button disappears (profile switch, plugin unload), its state is cleaned up from memory.
A plain string like e wave is sent directly as a single CMND frame.
A string like e sit;me relaxes is sent as-is in a single frame. FiveM natively supports ; as a command separator, so the game handles the splitting.
The plugin splits the input into tokens:
Input: "e think;me thinking;{2000ms};e c"
Tokens: [
{ type: "cmd", value: "e think;me thinking" },
{ type: "delay", ms: 2000 },
{ type: "cmd", value: "e c" }
]
Each command token is sent as a separate CMND frame, with await sleep(ms) between delay tokens. Single ; within a command token is preserved (passed to FiveM for native chaining).
Each button instance tracks its own current stage (0-4). The state machine works as follows:
- Key down - Read the current stage, execute that stage's On Press command
- Key up - Execute the current stage's On Release command, then advance to the next stage
-
Stage wrap -
nextState = (currentState + 1) % desiredStates - Visual update - The button icon updates to reflect the new stage (Stream Deck supports up to 5 visual states)
State is stored both in-memory (for fast access) and in the Stream Deck settings (for persistence across restarts).
The settings UI (PluginActionPI.html) communicates with the plugin via the Stream Deck WebSocket bridge:
- Plugin → PI: Settings are sent when the PI opens
- PI → Plugin: Settings are sent whenever the user changes a value
- The PI dynamically shows/hides command fields based on the Number of States setting
- Contributing - Development setup and workflow
- Command Syntax - User-facing syntax reference
FXCommands
Developers