Skip to content

feat: support classic Stream Deck models, starting with the MK.2 - #1

Open
Micha0827 wants to merge 7 commits into
m-a-b-u:mainfrom
Micha0827:feat/classic-stream-deck-support
Open

feat: support classic Stream Deck models, starting with the MK.2#1
Micha0827 wants to merge 7 commits into
m-a-b-u:mainfrom
Micha0827:feat/classic-stream-deck-support

Conversation

@Micha0827

@Micha0827 Micha0827 commented Jul 23, 2026

Copy link
Copy Markdown

What this does

Adds support for Stream Deck models beyond the Neo, with the MK.2 as the concrete target. Instead of assuming a Neo, the device layer derives everything it needs from the control definitions the connected device already reports through @elgato-stream-deck/node:

  • drawable keys — buttons whose feedbackType is lcd (those carry a pixelSize)
  • page controls — buttons without a display, which is exactly what the Neo touch points are
  • the InfoBar surface — the lcd-segment control, if the device has one

Nothing is keyed off a model name, so the Original, Original V2, MK.2, MK.2 Scissor, XL, Mini, and the 6/15/32 modules fall out of the same derivation. Devices that report no drawable keys, such as the Pedal, are skipped rather than opened.

The InfoBar on decks without an LCD segment

This was the one part that needed a design decision. My first attempt rendered the existing 248×58 strip across a run of keys and sliced it into tiles — but the physical gaps between keys cut words in half (ALL AG|ENTS, OPE|N), which looked broken on hardware.

What is in the PR instead: a run of adjacent infobar keys in one row each render a self-contained block of the current page. The first names the page, the rest carry its metrics. Tapping any tile advances the rotation, replacing what the touch points do on the Neo. On the 15-key default this maps cleanly — the four pages each have a heading plus up to three metrics, and the bottom row has exactly five keys for four tiles plus the page indicator.

docs/images/15-key-preview.png in this branch shows the result.

Backwards compatibility

This mattered more to me than the new feature, so it is verified rather than asserted:

  • The Neo render path is byte-identical. I rendered npm run preview before and after the change and compared checksums — same hash. Key modules keep a fixed 96×96 design canvas and are scaled to whatever pixel size the device reports, so the Neo simply renders at 1:1 as before.
  • All 46 existing tests pass unmodified. Every new parameter is optional and defaults to Neo behaviour: renderDeckBuffers(…, profile = NEO_PROFILE), actionForControl(…, profile = NEO_PROFILE), and the key renderers default to a 96×96 size.
  • Existing config.json files keep working. An 8-key layout stays valid; if it does not match the connected device, the recommended layout for that device is used instead.
  • Collectors, state.ts, and dashboard.ts are untouched — this is purely device, render, and input.

18 new tests cover profile derivation from synthetic control definitions, layout fitting, InfoBar spans, 15 keys rendered at 72×72 with no LCD buffer, per-tile content, and paging from any tile. npm run check is green at 64 tests.

Tested on hardware

Run on an actual Stream Deck MK.2 on macOS: device detection, all 15 keys at 72×72, the InfoBar tiles, and paging through all four pages from any tile. One thing that only showed up on the device: at 72px the scaled-down canvas was readable but tight, so where the smallest hint line is dropped the freed space now goes to the rows that carry information (last commit).

A note on scope

I am aware this widens what a project called Neo Agent Deck does, and that this is your call rather than a technical question. The name is untouched, the Neo behaviour is unchanged, and all device knowledge lives in one new file (src/device.ts), so it should be straightforward to reject or reshape. Happy to split this into smaller PRs, drop the typography commit, or take a different approach to the InfoBar if you had something else in mind.

Commits

  1. feat(device) — derive deck geometry from control definitions
  2. feat(render) — scale modules to the device and put the InfoBar on keys
  3. feat(deck) — drive any deck that reports drawable keys
  4. feat(scripts)npm run preview:15key and multi-device doctor
  5. test — cover the 15-key layout
  6. docs — document support for classic models
  7. fix(render) — enlarge type on keys too small for the full layout

Each commit builds and tests green on its own.

Add a device layer that reads key count, key resolution, grid position,
input-only buttons, and the LCD segment from the control definitions a
device reports, instead of assuming a Neo.

Config gains an "infobar" key module and a layout preset per key count;
the length of the keys array identifies the deck family, so an 8-key Neo
layout and a 15-key MK.2 layout are both accepted while any other length
still falls back to the default.

Setup asks which deck a configuration is for and edits its keys row by
row, or takes --keys=<n> to skip the question.
Key modules keep one 96x96 design canvas and are rendered at whatever
pixel size the device reports. Below 80 pixels the two smallest hint
lines are dropped rather than drawn illegibly.

Decks without an LCD segment show the InfoBar across a run of adjacent
keys. Slicing the 248x58 strip would cut words at the physical gaps, so
each key instead carries one self-contained block of the current page:
the first names the page, the rest carry its metrics.

renderDeckBuffers now returns the keys in device index order followed by
the InfoBar strip only when the device has an LCD, which leaves the Neo
output byte-identical.
Open the first attached device whose controls describe drawable keys and
render through its profile; pedals and docks report none and drop out on
their own. Key writes follow the reported key indices and fillLcd runs
only when an LCD segment exists.

Page controls come from the profile's input-only buttons, which are the
Neo touch points, so on a keypad deck indices 8 and 9 are ordinary keys
and the InfoBar tiles take over paging.
npm run preview:15key renders the 15-key layout from the existing sample
data, so the result can be checked without hardware. doctor now lists
every Stream Deck it detects instead of only reporting on a Neo.
Profile derivation from synthetic control definitions, layout fitting and
InfoBar spans, 15 keys rendered at 72x72 with no LCD buffer, distinct
InfoBar tiles per page, paging from any tile, and config acceptance of a
15-key layout.
Verified on a Stream Deck MK.2: at 72px the scaled-down canvas was
readable but tight. Where the hint line is dropped the freed space now
goes to the rows that carry information, and a long InfoBar heading
breaks at its space instead of shrinking to fit one line.

The summary labels stay at their original size because the two sit side
by side and collide above 8px. The Neo path renders byte-identically.
@Micha0827
Micha0827 force-pushed the feat/classic-stream-deck-support branch from 9d4755b to eefd27b Compare July 23, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant