streamdeck

package module
v0.0.0-...-6586ce7 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2024 License: MIT Imports: 14 Imported by: 0

README

Stream Deck

Library for directly interacting and controlling an Elgato Stream Deck on Linux.

This library is designed to take exclusive control over a Stream Deck using USB HID, if you are an end-user looking for software just to control your Stream Deck, this is not what you are looking for. If you are looking to build your own software whether it be a CLI or GUI app to control your Stream Deck, you have come to the right place.

This library was inspired by many of the other Go streamdeck libraries. I created this library because all the other libraries I found either didn't work, didn't support the features I wanted, required CGO, or were difficult to use.

The internal hid package was heavily-based on https://github.com/zserge/hid with some improvements from https://github.com/rafaelmartins/usbfs.

Features

  • Native Linux support (No CGO)
    • Caveat: This library does not support Windows or MacOS, and will not for the conceivable future.
  • Supports GIFs
    • The most uselessful feature
  • Easy to use
  • Performant
Missing
  • Tests
  • More in-depth examples and documentation
  • Probably some other things I had no idea existed

Example

package main

import (
	"context"
	"embed"
	"fmt"
	"image"
	"image/gif"
	"log"
	"os"
	"os/signal"
	"path/filepath"

	"golang.org/x/sys/unix"

	"github.com/matthewpi/streamdeck"
	"github.com/matthewpi/streamdeck/button"
	"github.com/matthewpi/streamdeck/view"
)

//go:embed .embed/*.png .embed/*.gif
var embedFs embed.FS

func main() {
	if err := start(context.Background()); err != nil {
		panic(err)
		return
	}
}

func start(ctx context.Context) error {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	sd, err := streamdeck.New(ctx)
	if err != nil {
		return fmt.Errorf("failed to find or connect to a streamdeck: %w", err)
	}
	if sd == nil {
		return fmt.Errorf("no streamdeck devices found: %w", err)
	}
	defer func(ctx context.Context, sd *streamdeck.StreamDeck) {
		if err := sd.Close(ctx); err != nil {
			log.Printf("an error occurred while closing the streamdeck: %v\n")
		}
	}(ctx, sd)

	if err := sd.SetBrightness(ctx, 25); err != nil {
		return fmt.Errorf("failed to set streamdeck brightness: %w", err)
	}

	buttons, err := view.NewButtons(sd)
	if err != nil {
		return fmt.Errorf("failed to create button view: %w", err)
	}

	sd.SetHandler(func(ctx context.Context, index int) error {
		switch index {
		case 0:
			fmt.Println("you pressed a button!")
		case 1:
			fmt.Println("you pressed another button!")
		}
		return nil
	})

	buttons.Set(1, button.NewImage(mustGetImage(sd, "spotify_play.png")))
	buttons.Set(2, button.NewGIF(sd, mustGetGIF("peepoDance.gif")))

	ctx3, cancel3 := context.WithCancel(ctx)
	defer cancel3()
	if err := buttons.Apply(ctx3); err != nil {
		return fmt.Errorf("failed to update streamdeck buttons: %w", err)
	}

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt, unix.SIGTERM)
	<-ch
	log.Println("shutting down")
	return nil
}

func getImage(sd *streamdeck.StreamDeck, filename string) ([]byte, error) {
	f, err := embedFs.Open(filepath.Join(".embed", filename))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	img, _, err := image.Decode(f)
	if err != nil {
		return nil, err
	}
	return sd.ProcessImage(img)
}

func mustGetImage(sd *streamdeck.StreamDeck, filename string) []byte {
	img, err := getImage(sd, filename)
	if err != nil {
		panic(err)
		return nil
	}
	return img
}

func getGIF(filename string) (*gif.GIF, error) {
	f, err := embedFs.Open(filepath.Join(".embed", filename))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	g, err := gif.DecodeAll(f)
	if err != nil {
		return nil, err
	}
	return g, nil
}

func mustGetGIF(filename string) *gif.GIF {
	img, err := getGIF(filename)
	if err != nil {
		panic(err)
		return nil
	}
	return img
}

Design

Device

Device is the lowest-level API that is exposed. A Device allows sending raw data as well as providing some convenience functions, it's main purpose is to provide a base that the StreamDeck structure interacts with in order to expose a more user-friendly API.

StreamDeck

StreamDeck provides the user-friendly API that most integrations of this library will use, it allows the use of Views in order to provide an easy way of setting buttons and handling press events.

View

A View is used by a StreamDeck to set the images for all buttons, a View may optionally override the Stream Deck's default button press handler in order to provide a different API for handling button presses.

Example (refer to view/buttons.go)
Button

Button is used for buttons with static content, like a solid color or image button. If you need to change the content of a static button, you can either just create a new button and update the View, or implement a custom button that pulls its content from an external source and is capable of updating the View itself.

Example (refer to button/button.go)
Button (Animated)

Animated is an addon to the Button interface that allows a button to determine when it wants to update itself. A common example of this would be displaying a GIF which from my knowledge the Stream Deck does not natively support, meaning we need to wait the time required for each frame and keep updating the image displayed on the Stream Deck. This interface could also be useful for displaying dynamic content like Spotify Album art for example.

Example (refer to button/animated.go)

Documentation

Index

Constants

View Source
const (
	// BrightnessMin is the lowest brightness that can be set on a StreamDeck.
	BrightnessMin uint8 = 0
	// BrightnessFull is the highest brightness that can be set on a StreamDeck.
	BrightnessFull uint8 = 100
)
View Source
const (
	// ImageFlagFlipX flips an image horizontally.
	ImageFlagFlipX = 1 << iota
	// ImageFlagFlipY flips an image vertically.
	ImageFlagFlipY
	// ImageFlagRotate90 rotates an image 90 degrees counter-clockwise.
	ImageFlagRotate90
	// ImageFlagRotate180 rotates an image 180 degrees counter-clockwise.
	ImageFlagRotate180
)

Variables

This section is empty.

Functions

This section is empty.

Types

type BrightnessPacketFunc

type BrightnessPacketFunc func(brightness byte) []byte

BrightnessPacketFunc is a function that returns a packet used to change the brightness of a Device.

type Device

type Device struct {
	DeviceType
	// contains filtered or unexported fields
}

Device represents a Stream Deck Device.

func Open

func Open(ctx context.Context) (*Device, error)

Open attempts to open a connection to a Stream Deck Device.

func OpenPath

func OpenPath(ctx context.Context, path string) (*Device, error)

OpenPath attempts to open a connection to a Stream Deck Device at the given path.

func (*Device) Clear

func (d *Device) Clear(ctx context.Context) error

Clear clears all buttons on the Device.

func (*Device) Close

func (d *Device) Close(ctx context.Context) error

Close resets the Device and closes the USB HID connection to the Stream Deck.

func (*Device) Reset

func (d *Device) Reset(ctx context.Context) error

Reset resets the Device, restoring its initial state displaying the Elgato logo.

func (*Device) SetBrightness

func (d *Device) SetBrightness(ctx context.Context, brightness byte) error

SetBrightness sets the brightness of all buttons on the Device.

func (*Device) SetButton

func (d *Device) SetButton(ctx context.Context, btnIndex int, rawImage []byte) error

SetButton sets the image displayed by a specific button on the Device.

type DeviceType

type DeviceType struct {
	// Name of the Device Type.
	Name string

	// ProductID of the Device. This will be used to identify the DeviceType
	// alongside Elgato's Vendor ID.
	ProductID uint16

	// Rows of buttons on the Device.
	Rows int

	// Cols of buttons on the Device.
	Cols int

	// ImageFormat used to encode images displayed on the Device.
	ImageFormat ImageFormat

	// ImageSize to use to transform images for the Device.
	ImageSize int

	// ImageFlags to apply to images before displaying them on the Device.
	ImageFlags ImageFlags

	// ButtonOffset is the offset to used to detect what physical button on the
	// device was pressed. This offset value varies by generation, but is
	// usually either `1` or `4`.
	ButtonOffset int

	// BrightnessPacketFunc returns a packet to change the brightness on the
	// Device.
	BrightnessPacketFunc

	// ResetPacketFunc returns a packet to reset the display on the Device.
	ResetPacketFunc

	// ImageTextureFunc sets an image on the Device.
	ImageTextureFunc
}

DeviceType represents a type of Elgato Stream Deck.

func (DeviceType) ButtonCount

func (t DeviceType) ButtonCount() int

ButtonCount returns the total number of buttons on the Device.

func (DeviceType) EncodeImage

func (t DeviceType) EncodeImage(img image.Image) ([]byte, error)

EncodeImage encodes an image to be used with the Stream Deck.

func (DeviceType) GIFT

func (t DeviceType) GIFT() *gift.GIFT

GIFT returns the GIFT instance used to transform images for the Device.

type ImageFlags

type ImageFlags uint8

ImageFlags are used to apply translations to an image before displaying it on a Stream Deck.

func (ImageFlags) GIFT

func (f ImageFlags) GIFT(size int) *gift.GIFT

GIFT returns the GIFT instance created by the flags.

func (ImageFlags) Has

func (f ImageFlags) Has(v ImageFlags) bool

Has returns true if a specific image flag is set.

type ImageFormat

type ImageFormat string

ImageFormat represents an Image Format used by a Stream Deck Device.

const (
	// BMP is a BMP ImageFormat.
	BMP ImageFormat = "BMP"
	// JPEG is a JPEG ImageFormat.
	JPEG ImageFormat = "JPEG"
)

func (ImageFormat) Blank

func (f ImageFormat) Blank(x, y int) ([]byte, error)

Blank creates and encodes a blank image used to represent an empty button on a Stream Deck.

func (ImageFormat) Encode

func (f ImageFormat) Encode(img image.Image) ([]byte, error)

Encode encodes an image using a ImageFormat.

type ImageTextureFunc

type ImageTextureFunc func(
	ctx context.Context,
	w func(context.Context, []byte) (int, error),
	button byte,
	buffer []byte,
) error

ImageTextureFunc is a function that displays an image for the specified button on a Device.

type ResetPacketFunc

type ResetPacketFunc func() []byte

ResetPacketFunc is a function that returns a packet used to reset the Device.

type StreamDeck

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

StreamDeck represents an Elgato Stream Deck.

func New

func New(ctx context.Context) (*StreamDeck, error)

New opens a connection to a Stream Deck and provides a user-friendly wrapper that makes interacting with the Stream Deck easier and more convenient.

func NewFromDevice

func NewFromDevice(ctx context.Context, device *Device) (*StreamDeck, error)

NewFromDevice creates a new Stream Deck from an existing Device, most users should use the New function instead.

This function can be useful if you have a specific USB device you want to use like if you want to connect to multiple Stream Decks or use a specific device that is not auto-detected correctly.

func (*StreamDeck) Brightness

func (s *StreamDeck) Brightness() uint8

Brightness returns the target brightness of the Stream Deck. This will not return 0 if the Stream Deck is sleeping. To check if the Stream Deck is sleeping use StreamDeck#IsSleeping().

func (*StreamDeck) Close

func (s *StreamDeck) Close(ctx context.Context) error

Close stops the event listeners and closes the underlying connection to the Stream Deck device.

func (*StreamDeck) Device

func (s *StreamDeck) Device() *Device

Device returns the underlying Stream Deck device.

func (*StreamDeck) IsSleeping

func (s *StreamDeck) IsSleeping() bool

IsSleeping returns true if the Stream Deck is currently sleeping.

func (*StreamDeck) ProcessImage

func (s *StreamDeck) ProcessImage(img image.Image) ([]byte, error)

ProcessImage processes an image to be used with the Stream Deck.

func (*StreamDeck) SetBrightness

func (s *StreamDeck) SetBrightness(ctx context.Context, brightness uint8) error

SetBrightness sets the brightness of the Stream Deck.

func (*StreamDeck) SetHandler

func (s *StreamDeck) SetHandler(fn func(context.Context, int) error)

SetHandler sets the button press handler used by the end-user to handle press events.

func (*StreamDeck) SetSleeping

func (s *StreamDeck) SetSleeping(ctx context.Context, sleeping bool) error

SetSleeping sets whether the Stream Deck is sleeping or not.

func (*StreamDeck) ToggleSleep

func (s *StreamDeck) ToggleSleep(ctx context.Context) (bool, error)

ToggleSleep toggles the sleep state for the Stream Deck.

type View

type View interface {
	// Apply applies the View to a StreamDeck.
	Apply(context.Context) error
}

View represents a view capable of updating the images displayed on a StreamDeck.

Directories

Path Synopsis
internal
hid

Jump to

Keyboard shortcuts

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