Windows tray utility that mirrors live OpenAI Codex activity (VS Code extension or CLI) onto the SteelSeries Apex 5 OLED (128×40 monochrome) through the local SteelSeries GG GameSense server.
Row 1: task state + elapsed time. Row 2: repository · what Codex is doing right now.
Row 3: plan usage left (5-hour window / weekly, from Codex's own token_count events).
All of it read-only — Codex files are never written and no conversation content is ever
displayed.
The tray icon uses the same color language: green = working, blue ✓ = done, amber ! = waiting for you, red ! = failed, gray × = interrupted.
Codex appends a JSONL rollout file per session under
%USERPROFILE%\.codex\sessions\YYYY\MM\DD\rollout-*.jsonl; VS Code extension sessions land
there too (originator: codex_vscode). ACS watches that tree with ReadDirectoryChangesW,
tails new bytes incrementally (byte offsets, partial-line safe, never re-reading whole
files), and folds the event stream into a small state machine:
codex events → normalized state → 128×40 framebuffer → GameSense frame (loopback POST)
Updates are event-driven: a 1 Hz tick redraws, the framebuffer is hashed, and identical
frames are never transmitted. See docs/FINDINGS.md for the empirically verified event
mapping and docs/ARCHITECTURE.md for the package design.
ACS binds a GameSense handler for the 128×40 screen class (screened-128x40) and
renders for that canvas. It works with every keyboard GG reports in this class —
Apex 5, Apex 7, Apex 7 TKL, Apex Pro, Apex Pro TKL — with no per-device setup. Verified
on an Apex 5; the frame format is byte-identical across the class.
Other GameSense screens are a different class and stay untouched (nothing appears on them):
| screen | devices | supported |
|---|---|---|
| 128×40 | Apex 5 / 7 / 7 TKL / Pro / Pro TKL | yes |
| 128×36 | Arctis Nova Pro, GameDAC Gen 2 | not yet |
| 128×52 | newer Apex Pro generations | not yet |
The GameSense API allows several resolutions in a single frame (image-data-128x36,
-128x40, -128x48, -128x52 in one payload — GG picks what the connected device
needs), so multi-screen support means binding the extra handlers and adding renderer
layout variants per resolution (see docs/ARCHITECTURE.md). Contributions welcome.
- Windows 10/11
- SteelSeries GG running (GameSense local API; the keyboard connected through GG as usual)
- OpenAI Codex VS Code extension or CLI (sessions under
~\.codex\sessions) - Nothing else: single Go binary, no admin rights, loopback traffic only
Prebuilt: grab ACS.exe (plus
SHA256SUMS.txt) from Releases — unsigned, so SmartScreen may ask for confirmation on
first run. To build instead, install Go 1.22+ and run:
go build -ldflags "-s -w -H=windowsgui -X codexconnector/internal/app.Version=v0.3.0" -o bin/codexconnector.exe ./cmd/codexconnector
-H=windowsgui keeps the console hidden for the tray app. The Go module keeps the early
working name codexconnector; everything user-facing is ACS.
| command | what it does |
|---|---|
codexconnector.exe |
run the tray app (default) |
codexconnector.exe --console [--debug] |
run the pipeline visibly, log state changes |
codexconnector.exe --doctor |
diagnose GG endpoint, Codex sessions, renderer |
codexconnector.exe --preview [--state exec] [--out DIR] |
render layout PNGs, enlarged |
codexconnector.exe --testframe |
put a known test sequence on the OLED (hardware check) |
codexconnector.exe --inspect [--duration 30s] [--debug] |
tail live sessions, print events + state |
codexconnector.exe --inspect --replay FILE.jsonl |
print the timeline of a recorded session |
codexconnector.exe --version |
print version |
First run: --doctor (everything should be [OK]), then --testframe and compare the
card on the keyboard (border, corner triangles in all four corners, readable text), then
run a Codex task in VS Code and watch the OLED.
Right-click the icon for: status header, Pause display, OLED preview (PNG of the current frame), Diagnostics, Open config, Autostart (per-user Run key, no elevation), About, Exit. Left-click opens the same menu. The icon color follows the state (green / blue ✓ / amber ! / red ! / gray × / dim).
By default ACS owns the OLED only while VS Code (or a watched process) is running:
- VS Code closes -> ACS removes its game from GG immediately (remove_game), so the OLED returns to GG's own configured content instantly - no lag, no stale frames. ACS itself keeps running: session monitoring, the state machine and the tray continue as usual (dim icon, "ACS: standby (VS Code closed)").
- VS Code starts -> ACS re-registers with GameSense and the live frame is back within a second, exactly where things left off.
The gate is polled every 5 s (microsecond-cost process snapshot). It is configurable - watch_processes decides which programs keep ACS engaged (add the ChatGPT desktop app if you use it), and require_vscode: false disables gating entirely for CLI-only setups.
%APPDATA%\CodexConnector\config.json (defaults shown; created on first save; display
settings hot-reload):
{
"done_hold_seconds": 12,
"fail_hold_seconds": 25,
"idle_blank_minutes": 10,
"show_clock_when_idle": true,
"poll_interval_ms": 2000,
"require_vscode": true,
"watch_processes": ["code.exe", "code - insiders.exe"]
}done_hold_seconds / fail_hold_seconds are parsed for compatibility but unused —
terminal states persist. After a task finishes the OLED keeps the whole frame
(repository, last action, rates, frozen task time) with DONE instead of WORKING,
until new Codex activity starts. idle_blank_minutes blanks the display only in plain
IDLE (burn-in protection); a paused display shows a blank frame. With gating enabled
the OLED is released to GG whenever no watched process is running.
| row | content | source |
|---|---|---|
| 1 (large) | WORKING, INPUT, DONE, FAIL, STOP, IDLE + elapsed |
turn lifecycle events |
| 2 | repository · EXEC EDIT READ TEST SEARCH THINK WORK WAIT |
live tool-call classification |
| 3 | 5H: n% · W: n% (usage left) |
token_count rate limits |
Detection confidence: DONE / STOP / INPUT certain (explicit events), EXEC / EDIT
high (pending exec_command / apply_patch calls), READ / TEST / SEARCH medium
(command heuristics), FAIL medium — current Codex builds emit no explicit task-failure
event (verified across ~100k rollout lines), so FAIL is inferred from a failed command
followed by a quiet turn. If Codex adds an explicit error event it will be picked up
automatically; unknown payload types are logged in debug mode.
Elapsed time is real (turn start → now, or Codex's own duration_ms). Progress counts come
only from Codex's own plan/todo updates — percentages are never invented.
GameSense deactivates a game that sends no events for its deinitialize window (default
15 s) and the device then shows GG's own configured content. ACS therefore registers with
the maximum deinitialize_timer_length_ms (60 s), sends a game_heartbeat every 5 s,
force-refreshes the current frame every 30 s even when unchanged, and re-registers on any
failed POST, GG restart (port re-discovered from coreProps.json), or 5 minutes after the
last registration. While ACS runs, it owns the OLED — including static DONE/FAIL/STOP
frames.
Measured on the development machine, idle tray mode: 0.00 s CPU over 60 s (below measurement noise), ≈20 MB working set, ~7.7 MB executable. No polling loops; the only recurring traffic is the 5 s heartbeat plus at most one frame per second when something actually changed.
- Nothing on the OLED —
--doctor. Endpoint missing → start SteelSeries GG; ACS re-discovers the port automatically after GG restarts. - Garbled frame — run
--testframeand compare: full border, corner triangles in all four corners, unmirrored text. - Stuck on WORK — an open turn with nothing pending is abandoned after 3 minutes of
silence (crashed editor); long-running commands legitimately keep
EXECfor hours. - Sessions not found — run any Codex task once; files must exist under
~\.codex\sessions. - Still stuck —
--console --debugprints every state transition; tray mode logs to%APPDATA%\CodexConnector\app.log(256 KB, rotated).
Local-only. No telemetry, no network beyond loopback to SteelSeries GG. Reads only the
Codex session store, GG's coreProps.json, and its own config/log. The OLED shows
operational metadata only — never prompts, answers, or thread names.
MIT. All code and the bitmap font are original. The GameSense wire format comes from the official gamesense-sdk documentation.



