Makima State — Frontend Integration Guide
Overview
Makima writes /tmp/makima-state.json atomically after every relevant input event.
The file is updated via rename() so reads are always consistent — no partial writes.
Watch for changes using inotify on the directory, not the file itself (atomic rename creates a new inode each time, so watching the file directly loses events):
inotifywait -m -e moved_to /tmp/ 2>/dev/null | grep --line-buffered "makima-state.json" | while read _; do
python3 -c "
import json, datetime
d = json.load(open('/tmp/makima-state.json'))
t = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]
lp = d['trackpads']['lpad']; rp = d['trackpads']['rpad']
ls = d['sticks']['lstick']; rs = d['sticks']['rstick']
l2 = d['triggers']['l2']; r2 = d['triggers']['r2']
print(f\"{t} lpad({lp['x']:+.3f},{lp['y']:+.3f}{'T' if lp['touching'] else ' '}{'P' if lp['pressed'] else ' '}) rpad({rp['x']:+.3f},{rp['y']:+.3f}{'T' if rp['touching'] else ' '}{'P' if rp['pressed'] else ' '}) lstick({ls['x']:+.3f},{ls['y']:+.3f}{'*' if ls['active'] else ' '}) rstick({rs['x']:+.3f},{rs['y']:+.3f}{'*' if rs['active'] else ' '}) L2={l2['value']:.3f}{'P' if l2['pressed'] else ' '} R2={r2['value']:.3f}{'P' if r2['pressed'] else ' '}\")
" 2>/dev/null
done
Full Schema
{
"context": {
"config_stack": ["Steam Deck"],
"layout": 0,
"paused": false,
"held_modifiers": ["BTN_TL"],
"active_buttons": ["BTN_TL", "BTN_SOUTH"]
},
"bindings": {
"BTN_SOUTH": { "action": ["KEY_ENTER"], "origin": "Steam Deck" },
"BTN_TL-BTN_GRIPR2": { "action": ["KEY_LEFTCTRL", "KEY_PAGEDOWN"], "origin": "Steam Deck" }
},
"modifier_active": {
"BTN_GRIPR2": { "action": ["KEY_LEFTCTRL", "KEY_PAGEDOWN"], "origin": "Steam Deck" }
},
"last_event": {
"input": "BTN_SOUTH",
"action": ["KEY_ENTER"],
"kind": "remap",
"value": 1
},
"trackpads": {
"lpad": {
"mode": "trackpad",
"x": 0.170,
"y": 0.039,
"touching": true,
"pressed": false
},
"rpad": {
"mode": "trackpad",
"x": 0.0,
"y": 0.0,
"touching": false,
"pressed": false
}
},
"sticks": {
"lstick": {
"mode": "disabled",
"x": 0.023,
"y": 0.006,
"deadzone": 0.031,
"active": false
},
"rstick": {
"mode": "cursor",
"x": -0.002,
"y": 0.002,
"deadzone": 0.092,
"active": false
}
}
}
Fields
context
| Field | Type | Meaning |
|---|---|---|
config_stack |
[string] |
Active config name(s). Currently always one entry. |
layout |
number |
Active layout index (0–3). For future multi-layout support. |
paused |
bool |
Makima is paused — no output is emitted. Set when HUD opens. |
held_modifiers |
[string] |
Modifier buttons currently physically held (e.g. ["BTN_TL"]). Empty when no modifier is held. Use this to switch between normal and modifier view. |
active_buttons |
[string] |
All buttons currently physically held, including non-modifiers. Use this to highlight buttons on the gamepad layout. |
bindings
Complete map of all configured button actions for the current config.
Key format:
- "BTN_SOUTH" — plain binding, no modifier
- "BTN_TL-BTN_GRIPR2" — combo: BTN_TL held, BTN_GRIPR2 pressed
Value:
{ "action": ["KEY_ENTER"], "origin": "Steam Deck" }
action: list of output keys that get emittedorigin: config name this binding comes from
This map is static while the config doesn't change. Reload it when context.config_stack changes.
modifier_active
Subset of bindings — only the combos reachable with the currently held modifiers.
Key format: just the trigger button name (modifier prefix stripped), e.g. "BTN_GRIPR2".
Value: same shape as bindings.
Empty {} when no modifier is held.
Use this to replace bindings in the display when held_modifiers is non-empty.
last_event
The most recently processed input event and what makima actually emitted.
| Field | Type | Meaning |
|---|---|---|
input |
string |
Button that was pressed/released, e.g. "BTN_SOUTH" |
action |
[string] |
Output keys that were emitted, e.g. ["KEY_ENTER"] |
kind |
string |
"remap" / "command" / "passthrough" |
value |
number |
1 = press, 0 = release |
null until the first button is pressed after makima starts.
trackpads
Always present. Both lpad and rpad are always included regardless of mode.
All position values are normalized to −1.0 … +1.0, rounded to 3 decimal places.
| Field | Type | Meaning |
|---|---|---|
mode |
string |
"trackpad" or "disabled" — value of LPAD/RPAD config setting |
x |
float |
Horizontal position −1.0…+1.0. 0.0 when not touching. |
y |
float |
Vertical position −1.0…+1.0. 0.0 when not touching. Positive = up (hardware convention). |
touching |
bool |
true when finger is on the pad. |
pressed |
bool |
true when the pad is physically clicked. |
Updated on every trackpad position change and on every click event.
sticks
Always present. Both lstick and rstick are always included regardless of mode.
All position values are normalized to −1.0 … +1.0, rounded to 3 decimal places. The deadzone is expressed in the same normalized space — draw it directly as a circle radius.
| Field | Type | Meaning |
|---|---|---|
mode |
string |
"disabled" / "cursor" / "scroll" / "bind" |
x |
float |
Horizontal position −1.0…+1.0. |
y |
float |
Vertical position −1.0…+1.0. |
deadzone |
float |
Configured deadzone threshold in normalized space (0.0…1.0). Use as circle radius in the UI. |
active |
bool |
true when either axis exceeds the deadzone. Ready-to-use — no threshold math needed. |
Analog value notes
- All analog values (trackpads, sticks) use normalized floats, never raw hardware integers.
- Rounded to 3 decimal places — values only change when the rounded result differs from the previous write. The frontend receives no sub-threshold noise.
- Sticks:
x/yalways reflect raw hardware position, regardless ofmode. Even a"disabled"stick reports its physical position (useful for showing drift). - Trackpads:
x/yalways reflect raw hardware position, regardless ofmode. Position is reported even when mode is not"trackpad". - Triggers (L2/R2): no analog axis on Steam Deck — use
active_buttonsforBTN_TL2/BTN_TR2.
Frontend Logic
Which bindings to display
if held_modifiers is non-empty and modifier_active is non-empty:
display = modifier_active ← only combos reachable from current modifier
else:
display = bindings ← all bindings
Button highlighting
for each button in layout:
if button in active_buttons:
highlight(button, "held")
elif last_event.input == button and last_event.value == 1:
highlight(button, "just_pressed") ← optional, fades out
else:
unhighlight(button)
Stick visualization
// Draw deadzone circle with radius = stick.deadzone
// Place dot at (stick.x, stick.y)
// Tint ring or dot when stick.active == true
Modifier indicator
if held_modifiers contains "BTN_TL":
show_modifier_indicator("L1")
Paused state
if context.paused:
show_overlay("preview mode — no output")
Update Frequency
- On every button press and release
- On modifier state change (
held_modifierschanges) - On config switch (active window changes)
- On pause/resume via IPC socket
- On trackpad position change (when finger is on pad), rate-limited to ~60 Hz
- On trackpad touch/release (bypasses rate limit — always immediate)
- On trackpad click (press/release)
- On stick movement, rate-limited to ~60 Hz
Analog writes are skipped entirely if no rounded value has changed — the frontend will not receive spurious identical updates.
The file is not polled — only updated on events. Use inotify, not a timer.
IPC — Pause / Resume
To pause makima output (e.g. when HUD opens):
echo "pause" | socat - UNIX-CONNECT:/tmp/makima-control.sock
To resume:
echo "resume" | socat - UNIX-CONNECT:/tmp/makima-control.sock
The socket may not exist if makima is not running — handle gracefully (socat fails silently).
context.paused reflects the current state after each command.
Button Name Reference (Steam Deck)
bindings key |
Physical button |
|---|---|
BTN_SOUTH |
A |
BTN_EAST |
B |
BTN_NORTH |
X |
BTN_WEST |
Y |
BTN_TL |
L1 |
BTN_TR |
R1 |
BTN_TL2 |
L2 (digital only — no analog axis on Steam Deck) |
BTN_TR2 |
R2 (digital only — no analog axis on Steam Deck) |
BTN_THUMBL |
L3 (left stick click) |
BTN_THUMBR |
R3 (right stick click) |
BTN_SELECT |
Select / View |
BTN_START |
Start / Menu |
BTN_MODE |
Steam button |
BTN_GRIPL |
L5 (upper left back paddle) |
BTN_GRIPL2 |
L4 (lower left back paddle) |
BTN_GRIPR |
R5 (upper right back paddle) |
BTN_GRIPR2 |
R4 (lower right back paddle) |
BTN_DPAD_UP/DOWN/LEFT/RIGHT |
D-Pad |