streamdeck

package module
v0.0.0-...-2468f2f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 15 Imported by: 0

README

streamdeck-go

streamdeck-go is an independent, unofficial project. It is not affiliated with, endorsed by, or sponsored by Elgato or Corsair. Stream Deck, Elgato, and Corsair are trademarks of their respective owners.

streamdeck-go is a CGO-free Go library for controlling Elgato Stream Deck hardware directly over USB HID on Linux, Windows, and macOS. Linux includes ARM and ARM64.

The API supports the Original, Mini, 15-key Classic/MK.2, XL, Neo, Pedal, Plus, Plus XL, Studio, and known module variants. It covers key, dial, and touch input; native images and GIF animation; brightness and sleep settings; full LCD and window images; firmware information; hot-plug watching; and reconnection. See CAPABILITIES.md for the detailed boundary between direct hardware control and Elgato's desktop/plugin platform.

Install

go get github.com/NJannasch/streamdeck-go

The library requires Go 1.22 or newer.

The API is currently pre-v1 and may change while additional physical models are validated.

See USAGE.md for device selection, event handling, reconnects, capability checks, images, settings, and platform setup. The precise hardware and desktop-software boundary is documented in CAPABILITIES.md.

Example

package main

import (
	"fmt"
	"log"

	streamdeck "github.com/NJannasch/streamdeck-go"
)

func main() {
	deck, err := streamdeck.Open()
	if err != nil {
		log.Fatal(err)
	}
	defer deck.Close()

	if err := deck.SetBrightness(30); err != nil {
		log.Fatal(err)
	}

	for event := range deck.Events() {
		fmt.Printf("key %d pressed=%t\n", event.Key, event.Pressed)
	}
}

List attached supported devices without opening them:

go run ./examples/list

Open the first device and read its firmware information:

go run ./examples/deviceinfo

Draw generated smiley faces on all keys, or only key 4:

go run ./examples/smiley
go run ./examples/smiley -key 4

Display a PNG, JPEG, or GIF frame on one key, or tile it across the panel:

go run ./examples/image -key 0 ./photo.png
go run ./examples/image -panel ./wallpaper.jpg
go run ./examples/image -lcd ./wallpaper.jpg
go run ./examples/image -window ./status.png

Play an animated GIF until it completes or Ctrl-C is pressed:

go run ./examples/animated -key 0 ./animation.gif

Print keys, dials, and touch gestures, or watch USB hot-plug changes:

go run ./examples/controls
go run ./examples/hotplug

Keep a long-running controller attached to the same serial after disconnects:

go run ./examples/reconnect -serial SERIAL_NUMBER

Run the MK.2 hardware diagnostic (changes brightness and leaves a generated test card visible):

go run ./examples/hardwaretest

For high-rate updates, call PrepareGIF once and replay its native frames with PlayAnimation, or pass already transformed and encoded JPEG/BMP bytes to SetKeyImageData.

Only one application can own a Stream Deck at a time. Close Elgato Stream Deck software and other controllers before opening it.

Linux permissions

Grant the logged-in desktop user access to Elgato HID devices:

SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", TAG+="uaccess"

Place that rule in /etc/udev/rules.d/60-streamdeck.rules, reload the rules, and reconnect the device.

Verify in Docker

The Docker build tests the Go 1.22 minimum, then runs race detection, vet, and CGO-disabled compile checks with Go 1.27 for Linux ARM, Linux ARM64, Windows AMD64/ARM64, and macOS AMD64/ARM64:

docker build -t streamdeck-go-check .

The same check is available as make verify; make check runs tests, race detection, and vet on the host.

The GitHub Actions CI workflow runs tests with Go 1.22 and Go 1.27 and builds every package for Linux AMD64/ARM/ARM64, Windows AMD64/ARM64, and macOS AMD64/ARM64. It runs on pushes, pull requests, and manual dispatches.

Docker verifies builds but does not access the USB device. Hardware tests should run on the host or in a container with the relevant /dev/hidraw* device passed through.

Hardware validation

The Stream Deck MK.2 (0fd9:0080) has been discovered, opened, and queried on real Linux hardware. Other model protocols are implemented from Elgato's HID documentation and the Python library's device definitions and are covered by packet-level tests, but still need confirmation on their physical devices.

Contributing

Use the structured templates for bug reports, feature requests, and hardware compatibility reports. Development and protocol contribution guidance is in CONTRIBUTING.md.

License

streamdeck-go is available under the MIT License. Third-party dependency attributions are listed in THIRD_PARTY_NOTICES.md.

Documentation

Overview

Package streamdeck controls Elgato Stream Deck hardware directly over USB HID without requiring CGO or the Elgato desktop software.

It supports the Original, Mini, Classic/MK.2, XL, Neo, Pedal, Plus, Plus XL, Studio, and known module variants. Call Devices to discover hardware, then Open, OpenBySerial, or OpenDevice to obtain an exclusively locked Deck.

A Deck starts its input reader when opened. Drain the event channels while it is in use, inspect Errors for a terminal USB read failure, and call Close to release the device. Done closes when the input reader has stopped. Display and settings methods serialize their USB writes, so independent goroutines may update different controls.

Key and dial indexes are zero based. Key zero is the top-left key and indexes proceed from left to right, then top to bottom. Model fields describe which optional controls and displays are available. Unsupported model-specific operations return an error.

Only one process can own a device at a time. The Elgato desktop application must release a Stream Deck before this package can open it.

Index

Constants

View Source
const VendorID uint16 = 0x0fd9

VendorID is Elgato's USB vendor ID.

Variables

View Source
var (
	// ErrNoDevice is returned when no supported Stream Deck matches a request.
	ErrNoDevice = errors.New("streamdeck: no matching device found")
	// ErrClosed is returned when an operation is attempted on a closed deck.
	ErrClosed = errors.New("streamdeck: device is closed")
)
View Source
var (
	StreamDeckOriginal   = gen1Model("Stream Deck Original", 0x0060, 3, 5, 72, 72)
	StreamDeckOriginalV2 = gen2Model("Stream Deck Original (2019)", 0x006d, 3, 5, 72, 72)
	StreamDeckMK2        = gen2Model("Stream Deck MK.2", 0x0080, 3, 5, 72, 72)
	StreamDeckMK2Module  = gen2Model("Stream Deck MK.2 Module", 0x00b9, 3, 5, 72, 72)
	StreamDeckScissor    = gen2Model("Stream Deck Scissor Keys", 0x00a5, 3, 5, 72, 72)

	StreamDeckMini        = miniModel("Stream Deck Mini", 0x0063)
	StreamDeckMiniMK2     = miniModel("Stream Deck Mini MK.2", 0x0090)
	StreamDeckMiniModule  = miniModel("Stream Deck Mini Module", 0x00b8)
	StreamDeckMiniDiscord = miniModel("Stream Deck Mini Discord", 0x00b3)

	StreamDeckXL       = xlModel("Stream Deck XL", 0x006c)
	StreamDeckXLMK2    = xlModel("Stream Deck XL (2022)", 0x008f)
	StreamDeckXLModule = xlModel("Stream Deck XL Module", 0x00ba)

	StreamDeckNeo    = Model{Name: "Stream Deck Neo", ProductID: 0x009a, Rows: 2, Columns: 4, KeyWidth: 96, KeyHeight: 96, KeyFormat: ImageFormatJPEG, TouchKeyCount: 2, ScreenWidth: 248, ScreenHeight: 58, LCDWidth: 480, LCDHeight: 320, /* contains filtered or unexported fields */}
	StreamDeckPedal  = Model{Name: "Stream Deck Pedal", ProductID: 0x0086, Rows: 1, Columns: 3, /* contains filtered or unexported fields */}
	StreamDeckPlus   = plusModel("Stream Deck +", 0x0084, 2, 4, 120, 120, 4, 800, 100, 0)
	StreamDeckPlusXL = plusModel("Stream Deck + XL", 0x00c6, 4, 9, 112, 112, 6, 1200, 100, 90)
	StreamDeckStudio = Model{Name: "Stream Deck Studio", ProductID: 0x00aa, Rows: 2, Columns: 16, KeyWidth: 80, KeyHeight: 120, KeyFormat: ImageFormatJPEG, DialCount: 2, /* contains filtered or unexported fields */}
)

Functions

func Reconnect

func Reconnect(ctx context.Context, serial string, interval time.Duration) <-chan Connection

Reconnect keeps opening a device after disconnects. An empty serial selects the first supported deck. Callers use each emitted Deck until its Errors channel closes, then wait for the next Connection.

func WatchDevices

func WatchDevices(ctx context.Context, interval time.Duration) <-chan DeviceChange

WatchDevices polls HID discovery and reports hot-plug changes until ctx ends.

Types

type Animation

type Animation struct {
	Frames [][]byte
	Delays []time.Duration
	Loops  int // zero loops forever; positive values are total play counts
}

Animation contains device-native encoded frames that can be replayed without repeatedly resizing and encoding them.

type Connection

type Connection struct {
	Deck *Deck
	Err  error
}

Connection is emitted by Reconnect whenever a matching device is opened or an open attempt fails. A non-nil Deck replaces the previous connection.

type Deck

type Deck struct {
	// contains filtered or unexported fields
}

Deck is an exclusively opened Stream Deck.

func Open

func Open() (*Deck, error)

Open opens the first supported attached Stream Deck with an exclusive lock.

func OpenBySerial

func OpenBySerial(serial string) (*Deck, error)

OpenBySerial opens the supported Stream Deck with the requested serial.

func OpenDevice

func OpenDevice(info DeviceInfo) (*Deck, error)

OpenDevice opens a device returned by Devices with an exclusive lock.

func (*Deck) Clear

func (d *Deck) Clear() error

Clear fills every key with black.

func (*Deck) ClearKey

func (d *Deck) ClearKey(key int) error

ClearKey fills one key with black.

func (*Deck) Close

func (d *Deck) Close() error

Close releases the device. It is safe to call more than once.

func (*Deck) DialEvents

func (d *Deck) DialEvents() <-chan DialEvent

DialEvents returns dial push, release, and rotation events. Events may be dropped when its buffer is full so unused control types cannot block keys.

func (*Deck) Done

func (d *Deck) Done() <-chan struct{}

Done closes after the input reader stops. It does not consume Errors and is useful for connection supervisors.

func (*Deck) Errors

func (d *Deck) Errors() <-chan error

Errors reports a terminal background read error. The channel closes with Deck.

func (*Deck) Events

func (d *Deck) Events() <-chan KeyEvent

Events returns key press and release events until the deck is closed. Events may be dropped when its buffer is full; callers should drain it continuously.

func (*Deck) FirmwareVersion

func (d *Deck) FirmwareVersion() (string, error)

FirmwareVersion returns the firmware version reported by the device.

func (*Deck) Info

func (d *Deck) Info() DeviceInfo

Info returns the identity and model of the opened device.

func (*Deck) PlayAnimation

func (d *Deck) PlayAnimation(ctx context.Context, key int, animation *Animation) error

PlayAnimation displays pre-encoded animation frames.

func (*Deck) PlayGIF

func (d *Deck) PlayGIF(ctx context.Context, key int, value *gif.GIF) error

PlayGIF prepares and plays a GIF until it finishes or ctx is canceled.

func (*Deck) PrepareGIF

func (d *Deck) PrepareGIF(value *gif.GIF) (*Animation, error)

PrepareGIF composites GIF disposal frames and encodes them for this deck.

func (*Deck) Reset

func (d *Deck) Reset() error

Reset asks the device to reset its display state.

func (*Deck) SerialNumber

func (d *Deck) SerialNumber() (string, error)

SerialNumber reads the serial number from the device firmware.

func (*Deck) SetBrightness

func (d *Deck) SetBrightness(percent int) error

SetBrightness changes display brightness, clamped to the range 0..100.

func (*Deck) SetEncoderColor

func (d *Deck) SetEncoderColor(encoder int, value color.Color) error

SetEncoderColor sets a Stream Deck Studio encoder knob LED.

func (*Deck) SetEncoderRing

func (d *Deck) SetEncoderRing(encoder int, colors []color.Color) error

SetEncoderRing sets all 24 segments around a Stream Deck Studio encoder. Missing colors are treated as black; extra colors are ignored.

func (*Deck) SetKeyColor

func (d *Deck) SetKeyColor(key int, value color.Color) error

SetKeyColor fills one key with a solid color.

func (*Deck) SetKeyImage

func (d *Deck) SetKeyImage(key int, img image.Image) error

SetKeyImage scales an image to the key, applies the model transform, encodes it as the native JPEG or BMP format, and displays it. Key zero is top-left.

func (*Deck) SetKeyImageData

func (d *Deck) SetKeyImageData(key int, encoded []byte) error

SetKeyImageData writes a pre-encoded JPEG or BMP in the model's native format. This avoids re-encoding frames in animation loops.

func (*Deck) SetLCDColor

func (d *Deck) SetLCDColor(value color.Color) error

SetLCDColor fills the complete LCD with a color using a feature command.

func (*Deck) SetLCDImage

func (d *Deck) SetLCDImage(img image.Image) error

SetLCDImage uploads one JPEG across the physical LCD beneath all keys.

func (*Deck) SetPanelImage

func (d *Deck) SetPanelImage(src image.Image) error

SetPanelImage displays one image across the complete key grid. The image is scaled to the logical key area; the physical gaps between keys remain visible.

func (*Deck) SetScreenImage

func (d *Deck) SetScreenImage(img image.Image) error

SetScreenImage sets the Neo status-bar display.

func (*Deck) SetSleepTimeout

func (d *Deck) SetSleepTimeout(duration time.Duration) error

SetSleepTimeout configures idle sleep. A zero duration disables sleep.

func (*Deck) SetTouchscreenImage

func (d *Deck) SetTouchscreenImage(x, y int, img image.Image) error

SetTouchscreenImage displays an image in a rectangular region of a Plus touchscreen. Width and height are inferred from the source image bounds.

func (*Deck) SetWindowImage

func (d *Deck) SetWindowImage(img image.Image) error

SetWindowImage sets the complete Neo status window or Plus touch window.

func (*Deck) ShowBackground

func (d *Deck) ShowBackground(index byte) error

ShowBackground displays a background previously uploaded with StoreBackground.

func (*Deck) SleepTimeout

func (d *Deck) SleepTimeout() (time.Duration, error)

SleepTimeout returns the configured idle sleep duration.

func (*Deck) StoreBackground

func (d *Deck) StoreBackground(index byte, img image.Image) error

StoreBackground uploads a reusable full-LCD background on Classic and XL family devices. The available index count depends on device firmware.

func (*Deck) TouchEvents

func (d *Deck) TouchEvents() <-chan TouchEvent

TouchEvents returns touchscreen tap, long-press, and drag events. Events may be dropped when its buffer is full so unused control types cannot block keys.

func (*Deck) UnitInfo

func (d *Deck) UnitInfo() (UnitInfo, error)

UnitInfo reads hardware geometry from main-protocol devices.

type DeviceChange

type DeviceChange struct {
	Kind DeviceChangeKind
	Info DeviceInfo
	Err  error
}

DeviceChange is emitted by WatchDevices. Existing devices are reported as connected when the watcher starts.

type DeviceChangeKind

type DeviceChangeKind uint8

DeviceChangeKind identifies a connected or disconnected device.

const (
	DeviceConnected DeviceChangeKind = iota + 1
	DeviceDisconnected
)

type DeviceInfo

type DeviceInfo struct {
	Path   string
	Serial string
	Model  Model
}

DeviceInfo identifies a supported attached Stream Deck.

func Devices

func Devices() ([]DeviceInfo, error)

Devices returns all attached Stream Decks supported by this package.

type DialEvent

type DialEvent struct {
	Dial    int
	Pressed bool
	Ticks   int
	Turn    bool
}

DialEvent reports a dial press transition or rotation. Ticks is signed and nonzero for rotations; Pressed is used for push/release events.

type ImageFormat

type ImageFormat string

ImageFormat is the image encoding accepted by a device display.

const (
	ImageFormatNone ImageFormat = ""
	ImageFormatJPEG ImageFormat = "JPEG"
	ImageFormatBMP  ImageFormat = "BMP"
)

type KeyEvent

type KeyEvent struct {
	Key     int
	Pressed bool
}

KeyEvent reports a physical key transition. Key zero is the top-left key.

type Model

type Model struct {
	Name      string
	ProductID uint16
	Rows      int
	Columns   int
	KeyWidth  int
	KeyHeight int
	KeyFormat ImageFormat

	DialCount         int
	TouchKeyCount     int
	TouchscreenWidth  int
	TouchscreenHeight int
	ScreenWidth       int
	ScreenHeight      int
	LCDWidth          int
	LCDHeight         int
	// contains filtered or unexported fields
}

Model describes the physical layout and capabilities of a Stream Deck.

func (Model) HasDisplay

func (m Model) HasDisplay() bool

HasDisplay reports whether the model has images behind its keys.

func (Model) InputCount

func (m Model) InputCount() int

InputCount includes Neo touch buttons in addition to its LCD keys.

func (Model) KeyCount

func (m Model) KeyCount() int

KeyCount returns the number of physical keys. A Pedal therefore returns 3.

type TouchEvent

type TouchEvent struct {
	Type       TouchType
	X, Y       int
	EndX, EndY int
}

TouchEvent reports touchscreen coordinates. EndX and EndY are set for drags.

type TouchType

type TouchType uint8

TouchType identifies a tap, long press, or drag on a touchscreen.

const (
	TouchTap TouchType = iota + 1
	TouchLongPress
	TouchDrag
)

type UnitInfo

type UnitInfo struct {
	Rows, Columns       int
	KeyWidth, KeyHeight int
	LCDWidth, LCDHeight int
	BitsPerPixel        int
	ColorScheme         int
	KeyGalleryImages    int
	LCDGalleryImages    int
	DemoFrames          int
}

UnitInfo is the geometry and image-gallery capacity reported by main-protocol firmware. Some firmware versions omit the trailing fields.

Directories

Path Synopsis
examples
animated command
controls command
deviceinfo command
hardwaretest command
hotplug command
image command
keypress command
list command
reconnect command
smiley command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL