DisplayNet Logo
Skip to main content

kvm streamdeck

The kvm streamdeck command binds an Elgato Stream Deck button to an action, turning the deck into a server-side control surface. The deck plugs into a remote station's (RX seat's) USB; pressing a key fires a server action directly, with no host PC involved. Button bindings use the same action types as macros: builtin, command, script, and sendkeys (see kvm macro and, for sendkeys, kvm sendkeys).

Bound keys are also painted with a tile (an icon, a text label, and colors) rendered on the deck's LCD keys when the binding is created (and on every deck mount). Built-in actions get a default icon and label; the optional icon= / label= / color= / bg= / labelcolor= arguments override them.

On a Stream Deck + family deck, the rotary encoders and the touch strip above them are bound separately. See kvm streamdeck dial.

Command TypeDisplayNet
Minimum Version5.0

Every subcommand addresses the deck by its RX seat (<remote_id>), the receiver it is plugged into. Button maps belong to the seat, and whatever deck is plugged in drives them; the deck's USB serial does not appear in the API.

Key images require recent RX firmware

Key-image rendering needs the receiver's AVP firmware at 2.4.0.110 or newer. On older firmware, buttons still bind and fire, but the keys stay blank.

Scope​

Stream Deck bindings live inside seat configurations alongside keyboard macros, and a seat resolves to exactly one configuration, most-specific wins:

rx (a specific remote seat) > global (all seats).

A bind lands in the configuration that seat resolves, so a seat's buttons and its keyboard macros always end up in the same place, and a new binding takes effect immediately. On a seat with nothing assigned to its rx scope that is the Global configuration; on a seat with its own configuration it is that one. unbind searches the same place.

Resolution is whole-configuration replacement, not a merge. See kvm config for the full model.

To author into a configuration a seat does not currently resolve, whether building one before assigning it or editing one that is inactive, add config=<name> to the command.

Binding does not require the seat to be present. bind, unbind, and the dial binding verbs store configuration, so they accept a receiver that is powered off or not yet racked. Name it by its device id while it is absent (see Configuring a seat before it exists). The subcommands that talk to the deck (reset, repaint, status) require it online.

Usage​

kvm streamdeck bind <remote_id> <button> [<builtin|command|script|sendkeys> <action...>] [icon=<name>] [label=<text>|-] [color=<rrggbb>] [bg=<rrggbb>] [labelcolor=<rrggbb>] [notify[=<name>]] [model=<key>] [config=<name>]
kvm streamdeck unbind <remote_id> <button> [model=<key>] [config=<name>]
kvm streamdeck list [<remote_id>]
kvm streamdeck status [<remote_id>]
kvm streamdeck brightness <remote_id> <0-100>|default
kvm streamdeck reset <remote_id>
kvm streamdeck repaint <remote_id>
kvm streamdeck icons <search_term>
kvm streamdeck infobar <remote_id> on|off|default
kvm streamdeck model list
kvm streamdeck model show <key>
kvm streamdeck model add pid=<product_id> [like=<key>] [key=<key>] [name=<text>] [keys=<n>] [cols=<n>] [pixels=<n>] [flip=none|h|v|hv] [rotation=0|90|180|270] [strip=<w>x<h>|none] [touchstrip=true|false] [dials=<n>] [packet=<n>] [header=<n>]
kvm streamdeck model update <key> <field>=<value> ...
kvm streamdeck model delete <key>
kvm streamdeck model rename <key> <new_key>

The dial subcommand has its own page. See kvm streamdeck dial.

Subcommands​

SubcommandDescription
bindBinds a button on the given seat to an action, and paints its tile. See bind.
unbindRemoves the binding for a given seat and button. Removes the any-deck binding unless model= names a per-model layout.
listReturns the bound buttons recorded in the database (not the decks currently attached). With a <remote_id> argument, filters to that seat.
statusReports the deck currently attached at each seat: model, key count and resolution, and which features it has. list reads configuration; status reads hardware. See status.
dialBinds the rotary encoders on a Stream Deck + family deck. Documented separately. See kvm streamdeck dial.
brightnessSets the seat's display brightness, 0 to 100. A stored per-seat setting: it is applied to a mounted deck immediately, re-applied on every mount, and accepts a seat that is not present. Pass default to remove the stored value.
resetResets the deck mounted at the given seat to its default state. Requires the deck to be currently connected.
repaintForces a full reset-and-repaint of the deck at the given seat, covering every bound key and the info-bar strip. Use it as insurance after a firmware upgrade or if a key image was dropped in transit. Requires the deck to be currently connected.
iconsSearches the icon library (built-in icons plus the bundled MDI set) by name and returns matching icon names to use with icon=. Does not require a deck.
infobarTurns the DisplayNet branding on the deck's info-bar strip on or off, or resets it to the default. See infobar.
modelLists the deck models the server knows, and defines or corrects one without a software update. See Deck models.

bind​

Binds a button on the given seat (<remote_id>) to an action, and paints its tile. Lands in the configuration that seat resolves, unless config=<name> names another. The trailing tile arguments are optional and may appear in any order after the action:

Tile argumentDescription
icon=<name>An icon from the built-in set or the bundled MDI library. Use the icons subcommand to search names. An unknown name is rejected.
label=<text>The text row under the icon. Use label=- for an icon-only tile (no text). When omitted, a built-in action uses its default label.
color=<rrggbb>The icon color, as a 6-digit hex value (a leading # is allowed).
bg=<rrggbb>The tile background color, replacing the default brand gradient ground.
labelcolor=<rrggbb>The label color, independent of color=. Without it the label uses the palette neutral, so tinting an icon does not drag its text along.

Only these tile keys, the notify modifier (below), and config= are consumed as trailing modifiers, so a command payload that contains its own key=value arguments is safe.

The button can also be written into a specific configuration:

ModifierDescription
config=<name>Bind into <name> instead of the configuration the seat resolves. Use it to build a configuration before assigning it, or to edit one that is currently inactive. The configuration must already exist. On unbind, it names the configuration to remove from. Not valid on kvm config button add, where the configuration is already the first argument.

The button can also announce its press to API clients:

ModifierDescription
notifyOn press, broadcast a streamdeck_notify event (see Notifications) with an empty name.
notify=<name>Same, with <name> as a semantic label an API client keys on.

When notify is present the action is optional: a button bound with only notify announces and takes no other action; combined with an action it does both on the same press. This also works on kvm config button add.

Per-model layouts​

model=<key> makes a binding specific to one deck model, so a seat can hold a 15-key MK.2 layout and an 8-key Neo layout at the same time. The attached hardware picks:

kvm streamdeck bind Editor1 0 builtin focus_next # any deck
kvm streamdeck bind Editor1 12 command preset apply Wide model=mk2 # only on an MK.2

Swapping the deck repaints it with the matching layout.

ModifierDescription
model=<key>Makes this binding specific to one deck model. Omit it and the binding applies to any deck. Valid keys are listed below; status reports the attached deck's key as model_key.

A model layout overrides the buttons it names and inherits the seat's any-deck bindings for every button it does not. A seat with a full generic layout plus a single model=neo override on button 0 gets the generic layout on a Neo, with button 0 replaced. A seat with only model-specific bindings gives a deck of any other model nothing.

KeyDeckGridKeys
neoStream Deck Neo4 × 28
mk2Stream Deck MK.25 × 315
scissorStream Deck MK.2 (Scissor Keys)5 × 315
originalv2Stream Deck Original V25 × 315
originalStream Deck Original5 × 315
xlStream Deck XL8 × 432
xlv2Stream Deck XL V28 × 432
plusStream Deck Plus4 × 28
plusxlStream Deck + XL9 × 436
miniStream Deck Mini3 × 26
mini2022Stream Deck Mini (2022)3 × 26

Grids are stated columns × rows. Two models with the same grid take different keys; a generic binding covers both.

Further models can be defined on the server, and are valid as model= keys once they are. kvm streamdeck model list reports every key a given server accepts. See Deck models.

The model is part of a binding's identity:

  • Re-binding button 0 with model=neo does not overwrite button 0's any-deck binding.
  • unbind <seat> 0 removes the any-deck binding and leaves the model layouts alone; name one with model= to remove it. When nothing matches, the error lists which models are bound on that button.
  • bind ... 12 model=neo is rejected (a Neo has 8 keys). A binding with no model is not range-checked.

model= works on kvm config button add / remove too, so a configuration can be built with per-model layouts before it is assigned to anything.

Deck models​

A deck model tells the server how to draw a key: how many keys the deck has, their pixel size, the grid, and the orientation the images are uploaded in. Models can be defined and corrected on the server, so a deck the shipped set does not cover, or one whose values are wrong, does not require a software update.

kvm streamdeck model list reports every model the server accepts. Each carries a source:

SourceMeaning
built_inShipped with the server.
inferredNot in the shipped set. The values are borrowed from the closest model whose name the deck's USB product name resembles. See status.
field_definedDefined on this server with model add or model update.

Defining a model​

like=<key> copies an existing model, so only the differences need stating:

kvm streamdeck model add pid=0x00A5 like=mk2 key=scissorkeys name="Stream Deck MK.2 Scissor Keys"

pid= is the deck's USB product id, which status reports as product_id. It accepts hexadecimal (0x00A5) or decimal.

Without like=, a definition needs keys, cols and pixels:

kvm streamdeck model add pid=0x0F01 key=acme name="Acme Control Pad" keys=6 cols=3 pixels=80

The remaining fields default to 1024-byte packets with an 8-byte header, no flip, no rotation, no strip and no dials.

key= is optional when a shipped model already claims that product id, in which case the definition takes that model's key and replaces it. Otherwise it is required.

like= is resolved when the command runs; a later change to the copied model does not change the definition.

Correcting a model​

model update changes named fields and leaves the rest alone:

kvm streamdeck model update scissorkeys rotation=0 flip=none

Orientation and key size cannot be read from the deck; verify them on the hardware and correct with model update. An update keeps the model's key, so bindings that reference it keep working. model rename changes the key itself and repoints those bindings in the same operation.

model update also accepts a shipped model's key. Doing so creates a local definition of that model, which then takes precedence:

kvm streamdeck model update plus rotation=0

The response says a definition was created and names the command that reverts it. model delete removes a local definition; the product id then resolves to the shipped model, or is inferred, or falls back to buttons only.

Precedence​

A deck resolves to the first of these that matches:

  1. A model defined on this server for the deck's product id.
  2. A model shipped with the server for that product id.
  3. A model inferred from the deck's USB product name.

A local definition of a product id the server also ships is reported as shadows_built_in by model list, model show and status. It continues to win after a server update; the flag marks a definition a later release has superseded.

Fields​

FieldDescription
pidUSB product id, hexadecimal (0x00A5) or decimal. One definition per product id.
keyThe key this model answers to as model=<key>. Letters, digits, underscore and hyphen.
likeAn existing model key to copy every value from. add only.
nameThe model's display name, normally the deck's USB product name.
keysTotal keys. Must divide evenly by cols.
colsKeys per row. Rows are derived from keys and cols.
pixelsKey image edge, in pixels, from 1 to 1024.
flipPre-upload flip: none, h, v, or hv.
rotationPre-upload rotation: 0, 90, 180, or 270.
stripStrip geometry as <width>x<height>, or none for a deck with no strip.
touchstriptrue for an interactive touch strip, false for a display-only info bar. Requires strip.
dialsRotary encoders on the deck.
packetImage packet size in bytes. Must exceed header.
headerHeader bytes at the front of each image packet.

An unrecognised field name is rejected, as is a value that cannot produce a drawable key, such as a key count that does not divide evenly by the column count. Defined models use the JPEG key-image format; the older bitmap format cannot be defined.

status​

Reports the control surface currently attached at each seat, from the device descriptor rather than from configuration. With no argument it lists every seat with a deck mounted; with a <remote_id> it reports that seat, including when no deck is present.

It reports the deck's grid as key_cols, key_rows and key_pixels; model_key, the short key bind model= takes; and the seat's brightness with brightness_is_set saying whether that is a stored override or the default.

It reports product, the deck's own USB product name, and model_source saying where the model's values came from: built_in, field_defined, or inferred. See Deck models.

A deck whose product id is in no model, shipped or defined, is matched against the model whose name its product name resembles. A match borrows that model's values, and status reports model_source as inferred along with inferred_from, the key it borrowed from. If the keys draw wrongly, the inference is wrong; model add replaces it.

A deck that matches no model name is reported with model as "unknown", the raw product_id, and interrupt_out_max_packet. Its buttons work and run the seat's any-deck layout; its keys stay blank. Those three fields are what model add needs.

infobar​

Turns the DisplayNet branding on the deck's info-bar strip (Elgato Neo) on or off. The setting is per-seat and persists across mounts; it applies to whatever deck is attached. on paints the DisplayNet logo, off paints a blank strip. Takes effect immediately when a strip-capable deck is mounted.

Pass default to remove the stored value so the seat falls back to the shipped default (branding on). This is one of the five per-seat settings, and kvm workstation show reports it with a HasOverride flag.

infobar stores the setting and, when a strip-capable deck is mounted, paints it, so it accepts a seat that is not present. The response's applied field says which happened: true when a deck was repainted, false when the setting was stored for later.

Diagnostic subcommands

testimage, setfeature, teststrip, teststrippartial, and testinference are internal hardware-diagnostic verbs used during control-surface bring-up. They are not part of the supported API surface and may change or disappear without notice.

Arguments​

ArgumentDescription
remote_idThe device name or ID of the remote HID extender, the RX seat where the Stream Deck is plugged in. Every kvm streamdeck subcommand except icons uses this; the deck's USB serial is never needed. Matched case-insensitively.
buttonA 0-based key index on a Stream Deck. Key 0 is the top-left key; indices increase left-to-right, top-to-bottom across the device's keys.
modelA short deck-model key on bind / unbind. See Per-model layouts for the list. Matched case-insensitively. Omitting it means the binding applies to any deck.
icon / label / color / bgOptional trailing tile arguments on bind (see bind). icon= is an icon name (validate with icons); label= is the text row, or - for icon-only; color= and bg= are 6-digit rrggbb hex values (a leading # is allowed).
search_termA substring matched (case-insensitively) against icon names for icons. Up to 25 matches are returned.

Examples​

Bind buttons to built-in, command, and sendkeys actions with tile styling
kvm streamdeck bind 6cdffb00f077 0 builtin focus_next
kvm streamdeck bind 6cdffb00f077 1 builtin fullscreen_toggle icon=fullscreen color=33cccc
kvm streamdeck bind 6cdffb00f077 2 command preset apply NightShift label=Night bg=203040
kvm streamdeck bind 6cdffb00f077 3 sendkeys "gui+r, type:notepad, enter" icon=console label=Run
Announce a button press to API clients (notify)
kvm streamdeck bind 6cdffb00f077 4 notify=PlayCam1
kvm streamdeck bind 6cdffb00f077 5 command connect HDMI Cam1 Wall notify=Cam1Live
One seat, two deck models — the attached hardware picks
kvm streamdeck bind Editor1 0 builtin focus_next
kvm streamdeck bind Editor1 1 builtin fullscreen_toggle
kvm streamdeck bind Editor1 12 command preset apply Wide label=Wide model=mk2
kvm streamdeck unbind Editor1 12 model=mk2
Search the icon library
kvm streamdeck icons monitor
List a seat's bound buttons
kvm streamdeck list 6cdffb00f077
Set the deck's brightness
kvm streamdeck brightness 6cdffb00f077 60
Turn on the info-bar branding
kvm streamdeck infobar 6cdffb00f077 on
Report the deck attached at a seat
kvm streamdeck status 6cdffb00f077

Return value​

kvm streamdeck list​

Each entry is one bound button. RemoteId carries the seat id the binding belongs to, the same value as the <remote_id> argument. ActionType may be builtin, command, script, sendkeys, or none for a button bound with only the notify modifier, which announces its press and takes no action. Model, Icon, Label, IconColor, Background, and NotifyName appear only when set on the binding. An absent Model means the binding applies to any deck (see Per-model layouts). For a sendkeys binding, any type: text in ActionPayload is masked (type:••••) so typed strings never appear in output. Reports bound buttons from the database, not decks currently attached.

kvm streamdeck list
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_list": [
{
"RemoteId": "6CDFFB00F077",
"Page": "default",
"Button": 1,
"ActionType": "builtin",
"ActionPayload": "fullscreen_toggle",
"Enabled": true,
"Icon": "fullscreen",
"IconColor": "33cccc"
},
{
"RemoteId": "6CDFFB00F077",
"Page": "default",
"Model": "mk2",
"Button": 12,
"ActionType": "command",
"ActionPayload": "preset apply Wide",
"Enabled": true,
"Label": "Wide"
}
]
},
"error": null
}

kvm streamdeck status​

One entry per seat with a deck attached (or the single requested seat). key_cols × key_rows is the deck's grid (columns first), and model_key is the short key bind model= takes. model_source says where the model's values came from, and note carries any capability caveat.

model is "unknown" for a deck that matches no model. Such an entry carries product, product_id and interrupt_out_max_packet but no model_key, keys or grid.

kvm streamdeck status 6cdffb01f5ba
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_status": [
{
"remote_id": "6cdffb01f5ba",
"remote_name": "RokuTV",
"mounted": true,
"brightness": 75,
"brightness_is_set": true,
"product_id": "0x00C6",
"serial": "AD4MA61311ZB6G",
"product": "Stream Deck + XL",
"model": "Stream Deck + XL",
"model_key": "plusxl",
"model_source": "built_in",
"keys": 36,
"key_cols": 9,
"key_rows": 4,
"key_pixels": 112,
"info_bar": false,
"touch_strip": true,
"dials": 6,
"key_images": "supported"
}
]
},
"error": null
}

kvm streamdeck model​

list returns every model the server accepts, ordered by key. show, add, update and rename return the single affected model in the same shape.

kvm streamdeck model show scissorkeys
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_model": [
{
"key": "scissorkeys",
"name": "Stream Deck MK.2 Scissor Keys",
"product_id": "0x00A5",
"source": "field_defined",
"keys": 15,
"key_cols": 5,
"key_rows": 3,
"key_pixels": 72,
"flip_horizontal": true,
"flip_vertical": true,
"rotation": 0,
"image_report_length": 1024,
"image_report_header_length": 8,
"dials": 0
}
]
},
"error": null
}

strip_pixels_w, strip_pixels_h and strip_is_touch appear only on a model that has a strip. A model that shadows a shipped one also carries shadows_built_in and a note naming the command that reverts it. rename adds renamed_from and bindings_repointed, the number of bindings moved to the new key.

delete returns the removed key and what the product id now resolves to.

kvm streamdeck model delete scissorkeys
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_model_delete": [
{
"deleted": "scissorkeys",
"product_id": "0x00A5",
"note": "Product id 0x00A5 now resolves to the built-in 'scissor' model."
}
]
},
"error": null
}

kvm streamdeck icons​

An array of matching icon names (built-in names first, then MDI names).

kvm streamdeck icons monitor
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_icons": ["monitor", "monitor-multiple", "monitor-dashboard"]
},
"error": null
}

Deck control subcommands​

brightness, reset, repaint and infobar all return the same envelope.

Each returns a single-object array under its own key, echoing the seat id and the operation result.

kvm streamdeck brightness 6cdffb00f077 60
{
"status": "SUCCESS",
"request_id": null,
"result": {
"streamdeck_brightness": [
{ "remote_id": "6CDFFB00F077", "percent": 60, "is_set": true, "applied": false }
]
},
"error": null
}

For brightness, is_set says whether a stored override remains after the call (false after default), and applied whether a mounted deck was repainted (false when the setting was stored for an absent seat, mirroring infobar).

SubcommandResult keyFields
brightnessstreamdeck_brightnessremote_id, percent, is_set, applied
resetstreamdeck_resetremote_id, pushed
repaintstreamdeck_repaintremote_id, repainted
infobarstreamdeck_infobarremote_id, enabled, applied

Binding subcommands​

bind and unbind both return the same envelope.

kvm streamdeck bind 6cdffb00f077 0 builtin focus_next
{
"status": "SUCCESS",
"request_id": null,
"result": null,
"error": null
}

Errors​

Error response
{
"status": "ERROR",
"request_id": null,
"result": null,
"error": {
"message": "<description>",
"reason": "KVM API ERROR"
}
}

Common error conditions:

  • Unknown Stream Deck action type, or an invalid (negative or non-numeric) button index (bind)
  • An invalid keystroke sequence for a sendkeys action (bind, see kvm sendkeys)
  • Unknown icon= name, an empty tile-argument value, or an invalid color=/bg= hex value (bind)
  • Unknown model= key. The error lists every valid key (bind, unbind)
  • A button index the named model does not have, e.g. button 12 with model=neo (bind)
  • No action and no notify modifier. A binding must have at least one (bind)
  • No binding found for the given seat, button, and model. The error names which models are bound on that button (unbind)
  • No mounted control surface at the given seat (reset, repaint)
  • Empty search term (icons)
  • Unknown mode. Expected on, off, or default (infobar)

Notifications​

streamdeck_update​

Sent after every kvm streamdeck bind or unbind, so a button editor can refresh. It carries a compact entry per binding: the seat id (RemoteId), the Button index, the Page, and Model when the binding is model-specific. Clients re-query list for full binding detail.

Model distinguishes a change to a model layout from a change to the any-deck binding on the same button.

streamdeck_update notification
{
"status": "DN_NOTIFICATION",
"request_id": null,
"error": null,
"result": {
"streamdeck_update": [
{ "RemoteId": "6CDFFB00F077", "Button": 0, "Page": "default" },
{ "RemoteId": "6CDFFB00F077", "Button": 12, "Page": "default", "Model": "mk2" }
]
}
}

streamdeck_notify​

Sent on each press of a button bound with the notify modifier (see bind). It lets an external controller, a Q-SYS plugin for example, react to a Stream Deck button over the normal notification channel. A button bound with both an action and notify emits this event and runs its action on the same press.

RemoteId is the seat id, Button the 0-based key index, and Name the label from notify=<name> (empty for a bare notify).

streamdeck_notify — button press
{
"status": "DN_NOTIFICATION",
"request_id": null,
"error": null,
"result": {
"streamdeck_notify": [
{ "RemoteId": "6CDFFB00F077", "Button": 4, "Page": "default", "Name": "Cam1Live" }
]
}
}
Dial payloads on streamdeck_notify

A dial in notify mode broadcasts streamdeck_notify too, carrying Dial and Ticks or Pressed instead of Button and Page. A client cannot switch on the event name alone and must discriminate on which fields are present. See dial notifications for all three shapes.

REST API​

Endpoint typeDisplayNet API command
AddressPOST /api/displaynet/<operation>, or name the operation in the body of a POST /api/displaynet. See Sending commands.
RoleUser
SubcommandOperationParameters
streamdeckkvm_streamdeckaction [args]*
streamdeck_bindkvm_streamdeck_bindremote_id button [action_type] [payload] [icon] [label] [color] [bg] [labelcolor] [notify] [model] [config]
streamdeck_brightnesskvm_streamdeck_brightnessremote_id value
streamdeck_infobarkvm_streamdeck_infobarremote_id mode
streamdeck_listkvm_streamdeck_list[remote_id]
streamdeck_statuskvm_streamdeck_statusremote_id
streamdeck_unbindkvm_streamdeck_unbindremote_id button [model] [config]

A parameter in brackets is optional. A parameter marked * takes the remainder of the command line as one string. Pass the text exactly as you would type it on the TCI interface.

Every action except reset, repaint, setfeature, teststrip, testimage and icons has a typed operation taking named fields. The tile modifiers (model, icon, label, notify, config) are separate optional fields rather than key=value tokens, and a missing or unknown field is rejected before the command runs. Dial operations are listed on the kvm streamdeck dial page.

Those six run through kvm_streamdeck, which takes a raw grammar line: action and args, spelled as under Usage above.

See also​

  • kvm: core KVM session commands, including kvm sendkeys
  • kvm macro: keyboard hotkey macros using the same action types
  • kvm streamdeck dial: the rotary encoders and touch strip on a + family deck
  • kvm config: the seat-configuration scope model bindings resolve through
  • Workstations: what a workstation is and how its pieces fit together