From f332ee0e6c3959a14e35e692d282acd3b47a4008 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Wed, 29 Mar 2023 21:35:46 +0200 Subject: [PATCH 01/17] Add support for Stream Deck Plus (buttons and dial knobs only) --- README.md | 1 + streamdeck.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b48076..630db55 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="006c", MODE:="666" SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="006d", MODE:="666", GROUP="plugdev" SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0080", MODE:="666", GROUP="plugdev" SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0090", MODE:="666", GROUP="plugdev" +SUBSYSTEM=="usb", ATTRS{idVendor}=="0fd9", ATTRS{idProduct}=="0084", MODE:="666", GROUP="plugdev" ``` Make sure your user is part of the `plugdev` group and reload the rules with diff --git a/streamdeck.go b/streamdeck.go index 8618d54..aee74a6 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -31,6 +31,7 @@ const ( PID_STREAMDECK_MINI = 0x0063 PID_STREAMDECK_MINI_MK2 = 0x0090 PID_STREAMDECK_XL = 0x006c + PID_STREAMDECK_PLUS = 0x0084 ) // Firmware command IDs. @@ -62,6 +63,7 @@ type Device struct { firmwareOffset int keyStateOffset int translateKeyIndex func(index, columns uint8) uint8 + readKeys func(*Device) (chan Key, error) imagePageSize int imagePageHeaderSize int flipImage func(image.Image) image.Image @@ -89,8 +91,9 @@ type Device struct { // Key holds the current status of a key on the device. type Key struct { - Index uint8 - Pressed bool + Index uint8 + Pressed bool + NotHoldable bool } // Devices returns all attached Stream Decks. @@ -116,6 +119,7 @@ func Devices() ([]Device, error) { firmwareOffset: 5, keyStateOffset: 1, translateKeyIndex: translateRightToLeft, + readKeys: readKeysForButtonsOnlyInput, imagePageSize: 7819, imagePageHeaderSize: 16, imagePageHeader: rev1ImagePageHeader, @@ -139,6 +143,7 @@ func Devices() ([]Device, error) { firmwareOffset: 5, keyStateOffset: 1, translateKeyIndex: identity, + readKeys: readKeysForButtonsOnlyInput, imagePageSize: 1024, imagePageHeaderSize: 16, imagePageHeader: miniImagePageHeader, @@ -162,6 +167,7 @@ func Devices() ([]Device, error) { firmwareOffset: 6, keyStateOffset: 4, translateKeyIndex: identity, + readKeys: readKeysForButtonsOnlyInput, imagePageSize: 1024, imagePageHeaderSize: 8, imagePageHeader: rev2ImagePageHeader, @@ -185,6 +191,7 @@ func Devices() ([]Device, error) { firmwareOffset: 6, keyStateOffset: 4, translateKeyIndex: identity, + readKeys: readKeysForButtonsOnlyInput, imagePageSize: 1024, imagePageHeaderSize: 8, imagePageHeader: rev2ImagePageHeader, @@ -194,10 +201,34 @@ func Devices() ([]Device, error) { resetCommand: c_REV2_RESET, setBrightnessCommand: c_REV2_BRIGHTNESS, } + case d.VendorID == VID_ELGATO && d.ProductID == PID_STREAMDECK_PLUS: + dev = Device{ + ID: d.Path, + Serial: d.Serial, + Columns: 4, + Rows: 2, + Keys: 24, + Pixels: 120, + DPI: 124, + Padding: 16, + featureReportSize: 32, + firmwareOffset: 6, + keyStateOffset: 4, + translateKeyIndex: identity, + readKeys: readKeysForMultipleInputTypes, + imagePageSize: 1024, + imagePageHeaderSize: 8, + imagePageHeader: rev2ImagePageHeader, + flipImage: noFlipping, + toImageFormat: toJPEG, + getFirmwareCommand: c_REV2_FIRMWARE, + resetCommand: c_REV2_RESET, + setBrightnessCommand: c_REV2_BRIGHTNESS, + } } if dev.ID != "" { - dev.keyState = make([]byte, dev.Columns*dev.Rows) + dev.keyState = make([]byte, dev.Keys) dev.info = d dd = append(dd, dev) } @@ -253,6 +284,10 @@ func (d Device) Clear() error { // ReadKeys returns a channel, which it will use to emit key presses/releases. func (d *Device) ReadKeys() (chan Key, error) { + return d.readKeys(d) +} + +func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { kch := make(chan Key) keyBuffer := make([]byte, d.keyStateOffset+len(d.keyState)) go func() { @@ -294,6 +329,95 @@ func (d *Device) ReadKeys() (chan Key, error) { return kch, nil } +func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { + kch := make(chan Key) + inputBuffer := make([]byte, 12) + go func() { + const INPUT_TYPE_ID_BUTTON = uint8(0) + const INPUT_TYPE_ID_KNOB = uint8(3) + + const INPUT_KNOB_USAGE_PRESS = uint8(0) + const INPUT_KNOB_USAGE_DIAL = uint8(1) + const INPUT_KNOB_STATE_OFFSET = uint8(5) + const INPUT_KNOB_COUNT = uint8(4) + + const INPUT_POSITION_TYPE_IO = uint8(1) + const INPUT_POSITION_KNOB_USAGE_ID = uint8(4) + + for { + if _, err := device.device.Read(inputBuffer); err != nil { + close(kch) + return + } + + // don't trigger a key event if the device is asleep, but wake it + if device.asleep { + _ = device.Wake() + + // reset state so no spurious key events get triggered + for i := device.keyStateOffset; i < len(inputBuffer); i++ { + inputBuffer[i] = 0 + } + continue + } + + device.sleepMutex.Lock() + device.lastActionTime = time.Now() + device.sleepMutex.Unlock() + + inputType := inputBuffer[INPUT_POSITION_TYPE_IO] + + if inputType == INPUT_TYPE_ID_BUTTON { + for i := device.keyStateOffset; i < len(inputBuffer); i++ { + keyIndex := uint8(i - device.keyStateOffset) + if inputBuffer[i] != device.keyState[keyIndex] { + device.keyState[keyIndex] = inputBuffer[i] + kch <- Key{ + Index: device.translateKeyIndex(keyIndex, device.Columns), + Pressed: inputBuffer[i] == 1, + } + } + } + } else if inputType == INPUT_TYPE_ID_KNOB { + knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] + + for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+INPUT_KNOB_COUNT; i++ { + keyValue := inputBuffer[i] + + if knobUsage == INPUT_KNOB_USAGE_PRESS { + keyIndex := i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + + if keyValue != device.keyState[keyIndex] { + device.keyState[keyIndex] = keyValue + + kch <- Key{ + Index: keyIndex, + Pressed: keyValue == 1, + } + } + } else if knobUsage == INPUT_KNOB_USAGE_DIAL && inputBuffer[i] > 0 { + var keyIndex uint8 + + if int(keyValue)-128 > 0 { //left + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + INPUT_KNOB_COUNT + } else { //right + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*INPUT_KNOB_COUNT + } + + kch <- Key{ + Index: device.translateKeyIndex(keyIndex, device.Columns), + Pressed: true, + NotHoldable: true, + } + } + } + } + } + }() + + return kch, nil +} + // Sleep puts the device asleep, waiting for a key event to wake it up. func (d *Device) Sleep() error { d.sleepMutex.Lock() @@ -502,6 +626,11 @@ func toRGBA(img image.Image) *image.RGBA { return out } +// noFlipping returns the given image without any flipping. +func noFlipping(img image.Image) image.Image { + return img +} + // flipHorizontally returns the given image horizontally flipped. func flipHorizontally(img image.Image) image.Image { flipped := image.NewRGBA(img.Bounds()) From 0f65de562f831726b45f63b8f9eaa399f4029aa0 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Thu, 30 Mar 2023 02:19:06 +0200 Subject: [PATCH 02/17] Add support for Stream Deck Plus touch screen input --- streamdeck.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index aee74a6..c82e9ad 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -3,6 +3,7 @@ package streamdeck import ( "bytes" "context" + "encoding/binary" "fmt" "image" "image/color" @@ -331,9 +332,10 @@ func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { kch := make(chan Key) - inputBuffer := make([]byte, 12) + inputBuffer := make([]byte, 13) go func() { const INPUT_TYPE_ID_BUTTON = uint8(0) + const INPUT_TYPE_ID_TOUCH = uint8(2) const INPUT_TYPE_ID_KNOB = uint8(3) const INPUT_KNOB_USAGE_PRESS = uint8(0) @@ -341,8 +343,18 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { const INPUT_KNOB_STATE_OFFSET = uint8(5) const INPUT_KNOB_COUNT = uint8(4) + const INPUT_TOUCH_USAGE_SHORT = uint8(1) + const INPUT_TOUCH_USAGE_LONG = uint8(2) + const INPUT_TOUCH_USAGE_SWIPE = uint8(3) + const INPUT_TOUCH_SEGMENTS_COUNT = uint8(4) + const INPUT_POSITION_TYPE_IO = uint8(1) const INPUT_POSITION_KNOB_USAGE_ID = uint8(4) + const INPUT_POSITION_TOUCH_USAGE_ID = uint8(4) + const INPUT_POSITION_TOUCH_X_ID = uint8(6) + const INPUT_POSITION_TOUCH_Y_ID = uint8(8) + const INPUT_POSITION_TOUCH_X2_ID = uint8(10) + const INPUT_POSITION_TOUCH_Y2_ID = uint8(12) for { if _, err := device.device.Read(inputBuffer); err != nil { @@ -373,7 +385,7 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { if inputBuffer[i] != device.keyState[keyIndex] { device.keyState[keyIndex] = inputBuffer[i] kch <- Key{ - Index: device.translateKeyIndex(keyIndex, device.Columns), + Index: keyIndex, Pressed: inputBuffer[i] == 1, } } @@ -405,12 +417,44 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { } kch <- Key{ - Index: device.translateKeyIndex(keyIndex, device.Columns), + Index: keyIndex, Pressed: true, NotHoldable: true, } } } + } else if inputType == INPUT_TYPE_ID_TOUCH { + touchUsage := inputBuffer[INPUT_POSITION_TOUCH_USAGE_ID] + + x := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X_ID:]) + segment := uint8(math.Floor(float64(x / 200.0))) + + var keyIndex uint8 + + if touchUsage == INPUT_TOUCH_USAGE_SHORT { + keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + segment + + } else if touchUsage == INPUT_TOUCH_USAGE_LONG { + keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + INPUT_TOUCH_SEGMENTS_COUNT + segment + + } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { + x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) + startSegment := uint8(math.Floor(float64(x / 40.0))) + stopSegment := uint8(math.Floor(float64(x2 / 40.0))) + + if startSegment < stopSegment { //left to right + keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + 2*INPUT_TOUCH_SEGMENTS_COUNT + } else if startSegment > stopSegment { //right to left + keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + 2*INPUT_TOUCH_SEGMENTS_COUNT + 1 + } else { + continue + } + } + kch <- Key{ + Index: keyIndex, + Pressed: true, + NotHoldable: true, + } } } }() From b86510202abc1368431a323a221b484b5fd65583 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 31 Mar 2023 02:06:55 +0200 Subject: [PATCH 03/17] Add support for touch screen images --- streamdeck.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/streamdeck.go b/streamdeck.go index c82e9ad..5a8872d 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -626,6 +626,58 @@ func (d Device) SetImage(index uint8, img image.Image) error { return nil } +// SetTouchScreenImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image +// needs to be in the correct resolution for the device. The index starts with +// 0 to 3. +func (d Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) error { + + const TOUCHSCREEN_WIDTH = uint(800) + const TOUCHSCREEN_SEGMENT_WIDTH = uint(200) + const TOUCHSCREEN_HEIGHT = uint(100) + + imageBytes, err := d.toImageFormat(d.flipImage(img)) + + if err != nil { + return fmt.Errorf("cannot convert image data: %v", err) + } + + const MAX_PACKET_SIZE = 1024 + const PACKET_HEADER_LENGTH = 16 + const MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - PACKET_HEADER_LENGTH + + imageData := imageData{ + image: imageBytes, + //pageSize: d.imagePageSize - d.imagePageHeaderSize, + pageSize: MAX_PAYLOAD_SIZE, + } + + x := int(uint(segmentIndex) * TOUCHSCREEN_SEGMENT_WIDTH) + y := 0 + + data := make([]byte, MAX_PACKET_SIZE) + + var page int + var lastPage bool + for !lastPage { + var payload []byte + payload, lastPage = imageData.Page(page) + header := touchScreenImagePageHeader(page, x, y, TOUCHSCREEN_SEGMENT_WIDTH, TOUCHSCREEN_HEIGHT, len(payload), lastPage) + + copy(data, header) + copy(data[len(header):], payload) + + _, err := d.device.Write(data) + if err != nil { + return fmt.Errorf("cannot write image page %d of %d (%d image bytes) %d bytes: %v", + page, imageData.PageCount(), imageData.Length(), len(data), err) + } + + page++ + } + + return nil +} + // getFeatureReport from the device without worries about the correct payload // size. func (d Device) getFeatureReport(payload []byte) ([]byte, error) { @@ -820,6 +872,35 @@ func rev2ImagePageHeader(pageIndex int, keyIndex uint8, payloadLength int, lastP } } +// touchScreenImagePageHeader returns the image page header sequence used by Stream +// Deck Plus for the touch screen. +func touchScreenImagePageHeader(page int, x int, y int, width uint, height uint, payloadLength int, lastPage bool) []byte { + + var lastPageByte byte + if lastPage { + lastPageByte = 1 + } + + return []byte{ + 0x02, // 0 Elgato secret flag value #1 + 0x0c, // 1 Elgato secret flag value #2 + byte(x), // 2 x low byte + byte(x >> 8), // 3 x high byte + byte(y), // 4 y low byte + byte(y >> 8), // 5 y high byte + byte(width), // 6 width low byte + byte(width >> 8), // 7 width high byte + byte(height), // 8 height low byte + byte(height >> 8), // 9 height high byte + lastPageByte, // 10 last page + byte(page), // 12 page low byte + byte(page >> 8), // 11 page high byte + byte(payloadLength), // 14 payload length high byte + byte(payloadLength >> 8), // 13 payload length high byte + 0x00, // 15 padding + } +} + // imageData allows to access raw image data in a byte array through pages of a // given size. type imageData struct { From cc450eed0fd83c2df0c6ce5ca240b7d106950761 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 31 Mar 2023 23:36:31 +0200 Subject: [PATCH 04/17] Add configuration values for stream deck plus --- streamdeck.go | 103 +++++++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 5a8872d..bd5c62b 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -60,16 +60,25 @@ type Device struct { DPI uint Padding uint - featureReportSize int - firmwareOffset int - keyStateOffset int - translateKeyIndex func(index, columns uint8) uint8 - readKeys func(*Device) (chan Key, error) - imagePageSize int - imagePageHeaderSize int - flipImage func(image.Image) image.Image - toImageFormat func(image.Image) ([]byte, error) - imagePageHeader func(pageIndex int, keyIndex uint8, payloadLength int, lastPage bool) []byte + ScreenWidth uint + ScreenHeight uint + ScreenSegments uint8 + + Knobs uint8 + + featureReportSize int + firmwareOffset int + keyStateOffset int + translateKeyIndex func(index, columns uint8) uint8 + readKeys func(*Device) (chan Key, error) + imagePageSize int + imagePageHeaderSize int + flipImage func(image.Image) image.Image + toImageFormat func(image.Image) ([]byte, error) + imagePageHeader func(pageIndex int, keyIndex uint8, payloadLength int, lastPage bool) []byte + screenPageSize int + screenPageHeaderSize int + screenPageHeader func(page int, x int, y int, width uint, height uint, payloadLength int, lastPage bool) []byte getFirmwareCommand []byte resetCommand []byte @@ -208,10 +217,14 @@ func Devices() ([]Device, error) { Serial: d.Serial, Columns: 4, Rows: 2, - Keys: 24, + Keys: 30, Pixels: 120, DPI: 124, Padding: 16, + ScreenWidth: 800, + ScreenHeight: 100, + ScreenSegments: 4, + Knobs: 4, featureReportSize: 32, firmwareOffset: 6, keyStateOffset: 4, @@ -222,6 +235,9 @@ func Devices() ([]Device, error) { imagePageHeader: rev2ImagePageHeader, flipImage: noFlipping, toImageFormat: toJPEG, + screenPageSize: 1024, + screenPageHeaderSize: 16, + screenPageHeader: touchScreenImagePageHeader, getFirmwareCommand: c_REV2_FIRMWARE, resetCommand: c_REV2_RESET, setBrightnessCommand: c_REV2_BRIGHTNESS, @@ -341,14 +357,12 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { const INPUT_KNOB_USAGE_PRESS = uint8(0) const INPUT_KNOB_USAGE_DIAL = uint8(1) const INPUT_KNOB_STATE_OFFSET = uint8(5) - const INPUT_KNOB_COUNT = uint8(4) const INPUT_TOUCH_USAGE_SHORT = uint8(1) const INPUT_TOUCH_USAGE_LONG = uint8(2) const INPUT_TOUCH_USAGE_SWIPE = uint8(3) - const INPUT_TOUCH_SEGMENTS_COUNT = uint8(4) - const INPUT_POSITION_TYPE_IO = uint8(1) + const INPUT_POSITION_TYPE_ID = uint8(1) const INPUT_POSITION_KNOB_USAGE_ID = uint8(4) const INPUT_POSITION_TOUCH_USAGE_ID = uint8(4) const INPUT_POSITION_TOUCH_X_ID = uint8(6) @@ -377,7 +391,7 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { device.lastActionTime = time.Now() device.sleepMutex.Unlock() - inputType := inputBuffer[INPUT_POSITION_TYPE_IO] + inputType := inputBuffer[INPUT_POSITION_TYPE_ID] if inputType == INPUT_TYPE_ID_BUTTON { for i := device.keyStateOffset; i < len(inputBuffer); i++ { @@ -393,7 +407,7 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { } else if inputType == INPUT_TYPE_ID_KNOB { knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] - for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+INPUT_KNOB_COUNT; i++ { + for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+device.Knobs; i++ { keyValue := inputBuffer[i] if knobUsage == INPUT_KNOB_USAGE_PRESS { @@ -411,9 +425,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { var keyIndex uint8 if int(keyValue)-128 > 0 { //left - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + INPUT_KNOB_COUNT + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + device.Knobs } else { //right - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*INPUT_KNOB_COUNT + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*device.Knobs } kch <- Key{ @@ -427,15 +441,17 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { touchUsage := inputBuffer[INPUT_POSITION_TOUCH_USAGE_ID] x := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X_ID:]) - segment := uint8(math.Floor(float64(x / 200.0))) + + segmentWidth := device.ScreenSegmentWidth() + segment := uint8(math.Floor(float64(uint(x) / segmentWidth))) var keyIndex uint8 if touchUsage == INPUT_TOUCH_USAGE_SHORT { - keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + segment + keyIndex = device.Columns*device.Rows + 3*device.Knobs + segment } else if touchUsage == INPUT_TOUCH_USAGE_LONG { - keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + INPUT_TOUCH_SEGMENTS_COUNT + segment + keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegments + segment } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) @@ -443,9 +459,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { stopSegment := uint8(math.Floor(float64(x2 / 40.0))) if startSegment < stopSegment { //left to right - keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + 2*INPUT_TOUCH_SEGMENTS_COUNT + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments } else if startSegment > stopSegment { //right to left - keyIndex = device.Columns*device.Rows + 3*INPUT_KNOB_COUNT + 2*INPUT_TOUCH_SEGMENTS_COUNT + 1 + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + 1 } else { continue } @@ -462,6 +478,22 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { return kch, nil } +// ScreenSegmentWidth returns the width of a screen segment. Returns 0 if there are no segments. +func (device *Device) ScreenSegmentWidth() uint { + if device.ScreenSegments == 0 { + return 0 + } + return device.ScreenWidth / uint(device.ScreenSegments) +} + +// ScreenSegmentHeight returns the width of a screen segment. Returns 0 if there are no segments. +func (device *Device) ScreenSegmentHeight() uint { + if device.ScreenSegments == 0 { + return 0 + } + return device.ScreenHeight +} + // Sleep puts the device asleep, waiting for a key event to wake it up. func (d *Device) Sleep() error { d.sleepMutex.Lock() @@ -629,44 +661,37 @@ func (d Device) SetImage(index uint8, img image.Image) error { // SetTouchScreenImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image // needs to be in the correct resolution for the device. The index starts with // 0 to 3. -func (d Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) error { +func (device Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) error { - const TOUCHSCREEN_WIDTH = uint(800) - const TOUCHSCREEN_SEGMENT_WIDTH = uint(200) - const TOUCHSCREEN_HEIGHT = uint(100) + segmentWidth := device.ScreenSegmentWidth() - imageBytes, err := d.toImageFormat(d.flipImage(img)) + imageBytes, err := device.toImageFormat(device.flipImage(img)) if err != nil { return fmt.Errorf("cannot convert image data: %v", err) } - const MAX_PACKET_SIZE = 1024 - const PACKET_HEADER_LENGTH = 16 - const MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - PACKET_HEADER_LENGTH - imageData := imageData{ - image: imageBytes, - //pageSize: d.imagePageSize - d.imagePageHeaderSize, - pageSize: MAX_PAYLOAD_SIZE, + image: imageBytes, + pageSize: device.screenPageSize - device.screenPageHeaderSize, } - x := int(uint(segmentIndex) * TOUCHSCREEN_SEGMENT_WIDTH) + x := int(uint(segmentIndex) * segmentWidth) y := 0 - data := make([]byte, MAX_PACKET_SIZE) + data := make([]byte, device.screenPageSize) var page int var lastPage bool for !lastPage { var payload []byte payload, lastPage = imageData.Page(page) - header := touchScreenImagePageHeader(page, x, y, TOUCHSCREEN_SEGMENT_WIDTH, TOUCHSCREEN_HEIGHT, len(payload), lastPage) + header := device.screenPageHeader(page, x, y, segmentWidth, device.ScreenSegmentHeight(), len(payload), lastPage) copy(data, header) copy(data[len(header):], payload) - _, err := d.device.Write(data) + _, err := device.device.Write(data) if err != nil { return fmt.Errorf("cannot write image page %d of %d (%d image bytes) %d bytes: %v", page, imageData.PageCount(), imageData.Length(), len(data), err) From 5ab175ab7c2508aa0328521e8fcd4d67099d9bf6 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 21 Apr 2023 17:18:39 +0200 Subject: [PATCH 05/17] Set the correct DPI value for SD+ Buttons --- streamdeck.go | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/streamdeck.go b/streamdeck.go index bd5c62b..a651edd 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -219,7 +219,7 @@ func Devices() ([]Device, error) { Rows: 2, Keys: 30, Pixels: 120, - DPI: 124, + DPI: 180, Padding: 16, ScreenWidth: 800, ScreenHeight: 100, @@ -703,6 +703,46 @@ func (device Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) er return nil } +func (device Device) SetTouchScreenImage2(x int, y int, img image.Image) error { + + width := uint(img.Bounds().Dx()) + height := uint(img.Bounds().Dy()) + + imageBytes, err := device.toImageFormat(device.flipImage(img)) + + if err != nil { + return fmt.Errorf("cannot convert image data: %v", err) + } + + imageData := imageData{ + image: imageBytes, + pageSize: device.screenPageSize - device.screenPageHeaderSize, + } + + data := make([]byte, device.screenPageSize) + + var page int + var lastPage bool + for !lastPage { + var payload []byte + payload, lastPage = imageData.Page(page) + header := device.screenPageHeader(page, x, y, width, height, len(payload), lastPage) + + copy(data, header) + copy(data[len(header):], payload) + + _, err := device.device.Write(data) + if err != nil { + return fmt.Errorf("cannot write image page %d of %d (%d image bytes) %d bytes: %v", + page, imageData.PageCount(), imageData.Length(), len(data), err) + } + + page++ + } + + return nil +} + // getFeatureReport from the device without worries about the correct payload // size. func (d Device) getFeatureReport(payload []byte) ([]byte, error) { From 56d19565c3235d806a5e3562dd752ebdcc7ee6e6 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 21 Apr 2023 19:06:38 +0200 Subject: [PATCH 06/17] Inverting NonHoldable to Holdable for better reading. --- streamdeck.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index a651edd..8422ff4 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -101,9 +101,9 @@ type Device struct { // Key holds the current status of a key on the device. type Key struct { - Index uint8 - Pressed bool - NotHoldable bool + Index uint8 + Pressed bool + Holdable bool } // Devices returns all attached Stream Decks. @@ -335,8 +335,9 @@ func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { keyIndex := uint8(i - d.keyStateOffset) if keyBuffer[i] != d.keyState[keyIndex] { kch <- Key{ - Index: d.translateKeyIndex(keyIndex, d.Columns), - Pressed: keyBuffer[i] == 1, + Index: d.translateKeyIndex(keyIndex, d.Columns), + Pressed: keyBuffer[i] == 1, + Holdable: true, } } } @@ -399,8 +400,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { if inputBuffer[i] != device.keyState[keyIndex] { device.keyState[keyIndex] = inputBuffer[i] kch <- Key{ - Index: keyIndex, - Pressed: inputBuffer[i] == 1, + Index: keyIndex, + Pressed: inputBuffer[i] == 1, + Holdable: true, } } } @@ -417,8 +419,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { device.keyState[keyIndex] = keyValue kch <- Key{ - Index: keyIndex, - Pressed: keyValue == 1, + Index: keyIndex, + Pressed: keyValue == 1, + Holdable: true, } } } else if knobUsage == INPUT_KNOB_USAGE_DIAL && inputBuffer[i] > 0 { @@ -431,9 +434,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { } kch <- Key{ - Index: keyIndex, - Pressed: true, - NotHoldable: true, + Index: keyIndex, + Pressed: true, + Holdable: false, } } } @@ -467,9 +470,9 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { } } kch <- Key{ - Index: keyIndex, - Pressed: true, - NotHoldable: true, + Index: keyIndex, + Pressed: true, + Holdable: false, } } } From c0ab6b1d20cc0c950a841858f497d6bbdd9520b4 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 21 Apr 2023 21:20:12 +0200 Subject: [PATCH 07/17] Change flipImage configuration to be optional --- streamdeck.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 8422ff4..23f34fc 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -233,7 +233,6 @@ func Devices() ([]Device, error) { imagePageSize: 1024, imagePageHeaderSize: 8, imagePageHeader: rev2ImagePageHeader, - flipImage: noFlipping, toImageFormat: toJPEG, screenPageSize: 1024, screenPageHeaderSize: 16, @@ -628,7 +627,7 @@ func (d Device) SetImage(index uint8, img image.Image) error { return fmt.Errorf("supplied image has wrong dimensions, expected %[1]dx%[1]d pixels", d.Pixels) } - imageBytes, err := d.toImageFormat(d.flipImage(img)) + imageBytes, err := d.transformImage(img) if err != nil { return fmt.Errorf("cannot convert image data: %v", err) } @@ -668,7 +667,7 @@ func (device Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) er segmentWidth := device.ScreenSegmentWidth() - imageBytes, err := device.toImageFormat(device.flipImage(img)) + imageBytes, err := device.transformImage(img) if err != nil { return fmt.Errorf("cannot convert image data: %v", err) @@ -711,7 +710,7 @@ func (device Device) SetTouchScreenImage2(x int, y int, img image.Image) error { width := uint(img.Bounds().Dx()) height := uint(img.Bounds().Dy()) - imageBytes, err := device.toImageFormat(device.flipImage(img)) + imageBytes, err := device.transformImage(img) if err != nil { return fmt.Errorf("cannot convert image data: %v", err) @@ -790,9 +789,13 @@ func toRGBA(img image.Image) *image.RGBA { return out } -// noFlipping returns the given image without any flipping. -func noFlipping(img image.Image) image.Image { - return img +// transformImage transforms the image for sending it to the device. +func (device *Device) transformImage(img image.Image) ([]byte, error) { + if device.flipImage != nil { + img = device.flipImage(img) + } + + return device.toImageFormat(img) } // flipHorizontally returns the given image horizontally flipped. From 5eaf7d4b9f875bd50220eb9fbf8ce5854333aab8 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Sun, 23 Apr 2023 15:17:35 +0200 Subject: [PATCH 08/17] Add horizontal and vertical DPI for SD+ --- streamdeck.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 23f34fc..83c7036 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -60,9 +60,11 @@ type Device struct { DPI uint Padding uint - ScreenWidth uint - ScreenHeight uint - ScreenSegments uint8 + ScreenWidth uint + ScreenHeight uint + ScreenVerticalDPI uint + ScreenHorizontalDPI uint + ScreenSegments uint8 Knobs uint8 @@ -223,6 +225,8 @@ func Devices() ([]Device, error) { Padding: 16, ScreenWidth: 800, ScreenHeight: 100, + ScreenVerticalDPI: 181, //14mm and 100px + ScreenHorizontalDPI: 188, //108mm and 800px ScreenSegments: 4, Knobs: 4, featureReportSize: 32, From 5d8e6150d5d984dc077e3cdeb2d33eef51243065 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Sun, 23 Apr 2023 18:47:08 +0200 Subject: [PATCH 09/17] Reduce code redundancy --- streamdeck.go | 92 +++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 83c7036..2e0bd56 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -319,31 +319,15 @@ func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { return } - // don't trigger a key event if the device is asleep, but wake it - if d.asleep { - _ = d.Wake() - - // reset state so no spurious key events get triggered - for i := d.keyStateOffset; i < len(keyBuffer); i++ { - keyBuffer[i] = 0 - } + if d.isAwakened() { + resetKeysStates(d, keyBuffer) + // Dont trigger a key event, because the key awoke the device continue } - d.sleepMutex.Lock() - d.lastActionTime = time.Now() - d.sleepMutex.Unlock() - - for i := d.keyStateOffset; i < len(keyBuffer); i++ { - keyIndex := uint8(i - d.keyStateOffset) - if keyBuffer[i] != d.keyState[keyIndex] { - kch <- Key{ - Index: d.translateKeyIndex(keyIndex, d.Columns), - Pressed: keyBuffer[i] == 1, - Holdable: true, - } - } - } + d.updateLastActionTimeToNow() + + d.sendNewButtonKeyEventsToChannel(keyBuffer, kch) } }() @@ -380,35 +364,19 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { return } - // don't trigger a key event if the device is asleep, but wake it - if device.asleep { - _ = device.Wake() - - // reset state so no spurious key events get triggered - for i := device.keyStateOffset; i < len(inputBuffer); i++ { - inputBuffer[i] = 0 - } + if device.isAwakened() { + resetKeysStates(device, inputBuffer) + // Dont trigger a key event, because the key awoke the device continue } - device.sleepMutex.Lock() - device.lastActionTime = time.Now() - device.sleepMutex.Unlock() + device.updateLastActionTimeToNow() inputType := inputBuffer[INPUT_POSITION_TYPE_ID] if inputType == INPUT_TYPE_ID_BUTTON { - for i := device.keyStateOffset; i < len(inputBuffer); i++ { - keyIndex := uint8(i - device.keyStateOffset) - if inputBuffer[i] != device.keyState[keyIndex] { - device.keyState[keyIndex] = inputBuffer[i] - kch <- Key{ - Index: keyIndex, - Pressed: inputBuffer[i] == 1, - Holdable: true, - } - } - } + device.sendNewButtonKeyEventsToChannel(inputBuffer, kch) + } else if inputType == INPUT_TYPE_ID_KNOB { knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] @@ -484,6 +452,26 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { return kch, nil } +func (device *Device) sendNewButtonKeyEventsToChannel(inputBuffer []byte, kch chan Key) { + for i := device.keyStateOffset; i < len(inputBuffer); i++ { + keyIndex := uint8(i - device.keyStateOffset) + if inputBuffer[i] != device.keyState[keyIndex] { + device.keyState[keyIndex] = inputBuffer[i] + kch <- Key{ + Index: keyIndex, + Pressed: inputBuffer[i] == 1, + Holdable: true, + } + } + } +} + +func (device *Device) updateLastActionTimeToNow() { + device.sleepMutex.Lock() + device.lastActionTime = time.Now() + device.sleepMutex.Unlock() +} + // ScreenSegmentWidth returns the width of a screen segment. Returns 0 if there are no segments. func (device *Device) ScreenSegmentWidth() uint { if device.ScreenSegments == 0 { @@ -802,6 +790,22 @@ func (device *Device) transformImage(img image.Image) ([]byte, error) { return device.toImageFormat(img) } +func (device *Device) isAwakened() bool { + if device.asleep { + _ = device.Wake() + return true + } + + return false +} + +func resetKeysStates(device *Device, inputBuffer []byte) { + // reset state so no spurious key events get triggered + for i := device.keyStateOffset; i < len(inputBuffer); i++ { + inputBuffer[i] = 0 + } +} + // flipHorizontally returns the given image horizontally flipped. func flipHorizontally(img image.Image) image.Image { flipped := image.NewRGBA(img.Bounds()) From 402a873e115326a5d861013c479dfc99c3702066 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Sun, 23 Apr 2023 19:21:41 +0200 Subject: [PATCH 10/17] Clean up code for processing SD+ input events --- streamdeck.go | 172 ++++++++++++++++++++++++++------------------------ 1 file changed, 91 insertions(+), 81 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 2e0bd56..e6548e2 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -33,6 +33,26 @@ const ( PID_STREAMDECK_MINI_MK2 = 0x0090 PID_STREAMDECK_XL = 0x006c PID_STREAMDECK_PLUS = 0x0084 + + INPUT_TYPE_ID_BUTTON = uint8(0) + INPUT_TYPE_ID_TOUCH = uint8(2) + INPUT_TYPE_ID_KNOB = uint8(3) + + INPUT_KNOB_USAGE_PRESS = uint8(0) + INPUT_KNOB_USAGE_DIAL = uint8(1) + INPUT_KNOB_STATE_OFFSET = uint8(5) + + INPUT_TOUCH_USAGE_SHORT = uint8(1) + INPUT_TOUCH_USAGE_LONG = uint8(2) + INPUT_TOUCH_USAGE_SWIPE = uint8(3) + + INPUT_POSITION_TYPE_ID = uint8(1) + INPUT_POSITION_KNOB_USAGE_ID = uint8(4) + INPUT_POSITION_TOUCH_USAGE_ID = uint8(4) + INPUT_POSITION_TOUCH_X_ID = uint8(6) + INPUT_POSITION_TOUCH_Y_ID = uint8(8) + INPUT_POSITION_TOUCH_X2_ID = uint8(10) + INPUT_POSITION_TOUCH_Y2_ID = uint8(12) ) // Firmware command IDs. @@ -327,7 +347,7 @@ func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { d.updateLastActionTimeToNow() - d.sendNewButtonKeyEventsToChannel(keyBuffer, kch) + d.sendButtonKeyEventsToChannel(keyBuffer, kch) } }() @@ -338,25 +358,6 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { kch := make(chan Key) inputBuffer := make([]byte, 13) go func() { - const INPUT_TYPE_ID_BUTTON = uint8(0) - const INPUT_TYPE_ID_TOUCH = uint8(2) - const INPUT_TYPE_ID_KNOB = uint8(3) - - const INPUT_KNOB_USAGE_PRESS = uint8(0) - const INPUT_KNOB_USAGE_DIAL = uint8(1) - const INPUT_KNOB_STATE_OFFSET = uint8(5) - - const INPUT_TOUCH_USAGE_SHORT = uint8(1) - const INPUT_TOUCH_USAGE_LONG = uint8(2) - const INPUT_TOUCH_USAGE_SWIPE = uint8(3) - - const INPUT_POSITION_TYPE_ID = uint8(1) - const INPUT_POSITION_KNOB_USAGE_ID = uint8(4) - const INPUT_POSITION_TOUCH_USAGE_ID = uint8(4) - const INPUT_POSITION_TOUCH_X_ID = uint8(6) - const INPUT_POSITION_TOUCH_Y_ID = uint8(8) - const INPUT_POSITION_TOUCH_X2_ID = uint8(10) - const INPUT_POSITION_TOUCH_Y2_ID = uint8(12) for { if _, err := device.device.Read(inputBuffer); err != nil { @@ -375,84 +376,93 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { inputType := inputBuffer[INPUT_POSITION_TYPE_ID] if inputType == INPUT_TYPE_ID_BUTTON { - device.sendNewButtonKeyEventsToChannel(inputBuffer, kch) + device.sendButtonKeyEventsToChannel(inputBuffer, kch) } else if inputType == INPUT_TYPE_ID_KNOB { - knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] - - for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+device.Knobs; i++ { - keyValue := inputBuffer[i] - - if knobUsage == INPUT_KNOB_USAGE_PRESS { - keyIndex := i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows - - if keyValue != device.keyState[keyIndex] { - device.keyState[keyIndex] = keyValue - - kch <- Key{ - Index: keyIndex, - Pressed: keyValue == 1, - Holdable: true, - } - } - } else if knobUsage == INPUT_KNOB_USAGE_DIAL && inputBuffer[i] > 0 { - var keyIndex uint8 - - if int(keyValue)-128 > 0 { //left - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + device.Knobs - } else { //right - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*device.Knobs - } - - kch <- Key{ - Index: keyIndex, - Pressed: true, - Holdable: false, - } - } - } + device.sendKnobEventsToChannel(inputBuffer, kch) + } else if inputType == INPUT_TYPE_ID_TOUCH { - touchUsage := inputBuffer[INPUT_POSITION_TOUCH_USAGE_ID] + device.sendTouchEventsToChannel(inputBuffer, kch) + } + } + }() + + return kch, nil +} + +func (device *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) { + touchUsage := inputBuffer[INPUT_POSITION_TOUCH_USAGE_ID] - x := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X_ID:]) + x := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X_ID:]) - segmentWidth := device.ScreenSegmentWidth() - segment := uint8(math.Floor(float64(uint(x) / segmentWidth))) + segmentWidth := device.ScreenSegmentWidth() + segment := uint8(math.Floor(float64(uint(x) / segmentWidth))) - var keyIndex uint8 + var keyIndex uint8 - if touchUsage == INPUT_TOUCH_USAGE_SHORT { - keyIndex = device.Columns*device.Rows + 3*device.Knobs + segment + if touchUsage == INPUT_TOUCH_USAGE_SHORT { + keyIndex = device.Columns*device.Rows + 3*device.Knobs + segment - } else if touchUsage == INPUT_TOUCH_USAGE_LONG { - keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegments + segment + } else if touchUsage == INPUT_TOUCH_USAGE_LONG { + keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegments + segment - } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { - x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) - startSegment := uint8(math.Floor(float64(x / 40.0))) - stopSegment := uint8(math.Floor(float64(x2 / 40.0))) + } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { + x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) + startSegment := uint8(math.Floor(float64(x / 40.0))) + stopSegment := uint8(math.Floor(float64(x2 / 40.0))) + + if startSegment < stopSegment { //left to right + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + } else if startSegment > stopSegment { //right to left + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + 1 + } else { + return + } + } + kch <- Key{ + Index: keyIndex, + Pressed: true, + Holdable: false, + } +} + +func (device *Device) sendKnobEventsToChannel(inputBuffer []byte, kch chan Key) { + knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] + + for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+device.Knobs; i++ { + keyValue := inputBuffer[i] + + if knobUsage == INPUT_KNOB_USAGE_PRESS { + keyIndex := i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + + if keyValue != device.keyState[keyIndex] { + device.keyState[keyIndex] = keyValue - if startSegment < stopSegment { //left to right - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments - } else if startSegment > stopSegment { //right to left - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + 1 - } else { - continue - } - } kch <- Key{ Index: keyIndex, - Pressed: true, - Holdable: false, + Pressed: keyValue == 1, + Holdable: true, } } - } - }() + } else if knobUsage == INPUT_KNOB_USAGE_DIAL && inputBuffer[i] > 0 { + var keyIndex uint8 - return kch, nil + if int(keyValue)-128 > 0 { //left turn + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + device.Knobs + } else { //right turn + keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*device.Knobs + } + + kch <- Key{ + Index: keyIndex, + Pressed: true, + Holdable: false, + } + } + } } -func (device *Device) sendNewButtonKeyEventsToChannel(inputBuffer []byte, kch chan Key) { +func (device *Device) sendButtonKeyEventsToChannel(inputBuffer []byte, kch chan Key) { for i := device.keyStateOffset; i < len(inputBuffer); i++ { keyIndex := uint8(i - device.keyStateOffset) if inputBuffer[i] != device.keyState[keyIndex] { From e41433c1a131c51235fa684d71203cbc7c082a39 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Sun, 23 Apr 2023 20:15:03 +0200 Subject: [PATCH 11/17] Clean up code for setting touch screen image --- streamdeck.go | 91 ++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index e6548e2..835c7f6 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -80,11 +80,11 @@ type Device struct { DPI uint Padding uint - ScreenWidth uint - ScreenHeight uint - ScreenVerticalDPI uint - ScreenHorizontalDPI uint - ScreenSegments uint8 + ScreenWidth uint + ScreenHeight uint + ScreenVerticalDPI uint + ScreenHorizontalDPI uint + ScreenSegmentsAmount uint8 Knobs uint8 @@ -100,7 +100,7 @@ type Device struct { imagePageHeader func(pageIndex int, keyIndex uint8, payloadLength int, lastPage bool) []byte screenPageSize int screenPageHeaderSize int - screenPageHeader func(page int, x int, y int, width uint, height uint, payloadLength int, lastPage bool) []byte + screenPageHeader func(page int, position image.Point, width uint, height uint, payloadLength int, lastPage bool) []byte getFirmwareCommand []byte resetCommand []byte @@ -247,7 +247,7 @@ func Devices() ([]Device, error) { ScreenHeight: 100, ScreenVerticalDPI: 181, //14mm and 100px ScreenHorizontalDPI: 188, //108mm and 800px - ScreenSegments: 4, + ScreenSegmentsAmount: 4, Knobs: 4, featureReportSize: 32, firmwareOffset: 6, @@ -404,7 +404,7 @@ func (device *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) keyIndex = device.Columns*device.Rows + 3*device.Knobs + segment } else if touchUsage == INPUT_TOUCH_USAGE_LONG { - keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegments + segment + keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegmentsAmount + segment } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) @@ -412,9 +412,9 @@ func (device *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) stopSegment := uint8(math.Floor(float64(x2 / 40.0))) if startSegment < stopSegment { //left to right - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegmentsAmount } else if startSegment > stopSegment { //right to left - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegments + 1 + keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegmentsAmount + 1 } else { return } @@ -484,15 +484,15 @@ func (device *Device) updateLastActionTimeToNow() { // ScreenSegmentWidth returns the width of a screen segment. Returns 0 if there are no segments. func (device *Device) ScreenSegmentWidth() uint { - if device.ScreenSegments == 0 { + if device.ScreenSegmentsAmount == 0 { return 0 } - return device.ScreenWidth / uint(device.ScreenSegments) + return device.ScreenWidth / uint(device.ScreenSegmentsAmount) } // ScreenSegmentHeight returns the width of a screen segment. Returns 0 if there are no segments. func (device *Device) ScreenSegmentHeight() uint { - if device.ScreenSegments == 0 { + if device.ScreenSegmentsAmount == 0 { return 0 } return device.ScreenHeight @@ -662,55 +662,22 @@ func (d Device) SetImage(index uint8, img image.Image) error { return nil } -// SetTouchScreenImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image +// SetTouchScreenSegmentImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image // needs to be in the correct resolution for the device. The index starts with -// 0 to 3. -func (device Device) SetTouchScreenImage(segmentIndex uint8, img image.Image) error { +// 0 to Device.ScreenSegmentsAmount-1. +func (device Device) SetTouchScreenSegmentImage(segmentIndex uint8, img image.Image) error { - segmentWidth := device.ScreenSegmentWidth() - - imageBytes, err := device.transformImage(img) - - if err != nil { - return fmt.Errorf("cannot convert image data: %v", err) - } - - imageData := imageData{ - image: imageBytes, - pageSize: device.screenPageSize - device.screenPageHeaderSize, - } - - x := int(uint(segmentIndex) * segmentWidth) - y := 0 - - data := make([]byte, device.screenPageSize) - - var page int - var lastPage bool - for !lastPage { - var payload []byte - payload, lastPage = imageData.Page(page) - header := device.screenPageHeader(page, x, y, segmentWidth, device.ScreenSegmentHeight(), len(payload), lastPage) - - copy(data, header) - copy(data[len(header):], payload) - - _, err := device.device.Write(data) - if err != nil { - return fmt.Errorf("cannot write image page %d of %d (%d image bytes) %d bytes: %v", - page, imageData.PageCount(), imageData.Length(), len(data), err) - } - - page++ + position := image.Point{ + X: int(uint(segmentIndex) * device.ScreenSegmentWidth()), + Y: 0, } - return nil + return device.SetTouchScreenImage(position, device.ScreenSegmentWidth(), device.ScreenSegmentHeight(), img) } -func (device Device) SetTouchScreenImage2(x int, y int, img image.Image) error { - - width := uint(img.Bounds().Dx()) - height := uint(img.Bounds().Dy()) +// SetTouchScreenImage sets the image of the Stream Deck Plus touch screen at the given point. The provided image +// needs to be in the correct resolution for the device. +func (device Device) SetTouchScreenImage(position image.Point, width uint, height uint, img image.Image) error { imageBytes, err := device.transformImage(img) @@ -730,7 +697,7 @@ func (device Device) SetTouchScreenImage2(x int, y int, img image.Image) error { for !lastPage { var payload []byte payload, lastPage = imageData.Page(page) - header := device.screenPageHeader(page, x, y, width, height, len(payload), lastPage) + header := device.screenPageHeader(page, position, width, height, len(payload), lastPage) copy(data, header) copy(data[len(header):], payload) @@ -963,7 +930,7 @@ func rev2ImagePageHeader(pageIndex int, keyIndex uint8, payloadLength int, lastP // touchScreenImagePageHeader returns the image page header sequence used by Stream // Deck Plus for the touch screen. -func touchScreenImagePageHeader(page int, x int, y int, width uint, height uint, payloadLength int, lastPage bool) []byte { +func touchScreenImagePageHeader(page int, position image.Point, width uint, height uint, payloadLength int, lastPage bool) []byte { var lastPageByte byte if lastPage { @@ -973,10 +940,10 @@ func touchScreenImagePageHeader(page int, x int, y int, width uint, height uint, return []byte{ 0x02, // 0 Elgato secret flag value #1 0x0c, // 1 Elgato secret flag value #2 - byte(x), // 2 x low byte - byte(x >> 8), // 3 x high byte - byte(y), // 4 y low byte - byte(y >> 8), // 5 y high byte + byte(position.X), // 2 x low byte + byte(position.X >> 8), // 3 x high byte + byte(position.Y), // 4 y low byte + byte(position.Y >> 8), // 5 y high byte byte(width), // 6 width low byte byte(width >> 8), // 7 width high byte byte(height), // 8 height low byte From 956921422cd177fe9f41324cd48dcf61e328b86c Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Sun, 23 Apr 2023 21:22:49 +0200 Subject: [PATCH 12/17] Update README for SD+ mappings to key indexes --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/README.md b/README.md index 630db55..c71ade8 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,67 @@ Reset the device: streamdeck-cli reset ``` +## Stream Deck Plus + +At the current state, the knobs and touch screen usage will be transformed to "normal button keys indexes". + +### Normal Buttons + +The 8 "normal" buttons have the key index 0 - 7 (top left to bottom right) + +### Touch Screen + +The touch screen is divided into four horizontal segments (matching the numbers of knobs): +Segment index is from 0 - 3 (from left to right) + +The key indexes for touch screen usages are: + +Segment 0 +- Short touch: 20 +- Long touch: 24 + +Segment 1 +- Short touch: 21 +- Long touch: 25 + +Segment 2 +- Short touch: 22 +- Long touch: 26 + +Segment 3 +- Short touch: 23 +- Long touch: 27 + +All touch screen presses are not "holdable" like normal buttons. + +Swiping is mapped to: +- From left to right: 28 +- From right to left: 29 + +### Knobs + +The knobs usages will be mapped to key indexes as following (left to right): + +Knob 1 +- Press: 8 (holdable) +- Left turn: 12 +- Right turn: 16 + +Knob 2 +- Press: 9 (holdable) +- Left turn: 13 +- Right turn: 17 + +Knob 3 +- Press: 10 (holdable) +- Left turn: 14 +- Right turn: 18 + +Knob 4 +- Press: 11 (holdable) +- Left turn: 15 +- Right turn: 19 + ## Feedback Got some feedback or suggestions? Please open an issue or drop me a note! From 0c43a51ab3d23c0b5f2814a04e7ccfbaec89bfa1 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Fri, 28 Apr 2023 13:26:54 +0200 Subject: [PATCH 13/17] Clean up code (satisfying linter) --- streamdeck.go | 102 +++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 835c7f6..064fa53 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -358,7 +358,6 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { kch := make(chan Key) inputBuffer := make([]byte, 13) go func() { - for { if _, err := device.device.Read(inputBuffer); err != nil { close(kch) @@ -377,10 +376,8 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { if inputType == INPUT_TYPE_ID_BUTTON { device.sendButtonKeyEventsToChannel(inputBuffer, kch) - } else if inputType == INPUT_TYPE_ID_KNOB { device.sendKnobEventsToChannel(inputBuffer, kch) - } else if inputType == INPUT_TYPE_ID_TOUCH { device.sendTouchEventsToChannel(inputBuffer, kch) } @@ -390,31 +387,29 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { return kch, nil } -func (device *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) { +func (d *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) { touchUsage := inputBuffer[INPUT_POSITION_TOUCH_USAGE_ID] x := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X_ID:]) - segmentWidth := device.ScreenSegmentWidth() - segment := uint8(math.Floor(float64(uint(x) / segmentWidth))) + segmentWidth := d.ScreenSegmentWidth() + segment := uint8(math.Floor(float64(x) / float64(segmentWidth))) var keyIndex uint8 if touchUsage == INPUT_TOUCH_USAGE_SHORT { - keyIndex = device.Columns*device.Rows + 3*device.Knobs + segment - + keyIndex = d.Columns*d.Rows + 3*d.Knobs + segment } else if touchUsage == INPUT_TOUCH_USAGE_LONG { - keyIndex = device.Columns*device.Rows + 3*device.Knobs + device.ScreenSegmentsAmount + segment - + keyIndex = d.Columns*d.Rows + 3*d.Knobs + d.ScreenSegmentsAmount + segment } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) - startSegment := uint8(math.Floor(float64(x / 40.0))) - stopSegment := uint8(math.Floor(float64(x2 / 40.0))) + startSegment := uint8(math.Floor(float64(x) / 40.0)) + stopSegment := uint8(math.Floor(float64(x2) / 40.0)) if startSegment < stopSegment { //left to right - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegmentsAmount + keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegmentsAmount } else if startSegment > stopSegment { //right to left - keyIndex = device.Columns*device.Rows + 3*device.Knobs + 2*device.ScreenSegmentsAmount + 1 + keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegmentsAmount + 1 } else { return } @@ -426,17 +421,17 @@ func (device *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) } } -func (device *Device) sendKnobEventsToChannel(inputBuffer []byte, kch chan Key) { +func (d *Device) sendKnobEventsToChannel(inputBuffer []byte, kch chan Key) { knobUsage := inputBuffer[INPUT_POSITION_KNOB_USAGE_ID] - for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+device.Knobs; i++ { + for i := INPUT_KNOB_STATE_OFFSET; i < INPUT_KNOB_STATE_OFFSET+d.Knobs; i++ { keyValue := inputBuffer[i] if knobUsage == INPUT_KNOB_USAGE_PRESS { - keyIndex := i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + keyIndex := i - INPUT_KNOB_STATE_OFFSET + d.Columns*d.Rows - if keyValue != device.keyState[keyIndex] { - device.keyState[keyIndex] = keyValue + if keyValue != d.keyState[keyIndex] { + d.keyState[keyIndex] = keyValue kch <- Key{ Index: keyIndex, @@ -448,9 +443,9 @@ func (device *Device) sendKnobEventsToChannel(inputBuffer []byte, kch chan Key) var keyIndex uint8 if int(keyValue)-128 > 0 { //left turn - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + device.Knobs + keyIndex = i - INPUT_KNOB_STATE_OFFSET + d.Columns*d.Rows + d.Knobs } else { //right turn - keyIndex = i - INPUT_KNOB_STATE_OFFSET + device.Columns*device.Rows + 2*device.Knobs + keyIndex = i - INPUT_KNOB_STATE_OFFSET + d.Columns*d.Rows + 2*d.Knobs } kch <- Key{ @@ -462,11 +457,11 @@ func (device *Device) sendKnobEventsToChannel(inputBuffer []byte, kch chan Key) } } -func (device *Device) sendButtonKeyEventsToChannel(inputBuffer []byte, kch chan Key) { - for i := device.keyStateOffset; i < len(inputBuffer); i++ { - keyIndex := uint8(i - device.keyStateOffset) - if inputBuffer[i] != device.keyState[keyIndex] { - device.keyState[keyIndex] = inputBuffer[i] +func (d *Device) sendButtonKeyEventsToChannel(inputBuffer []byte, kch chan Key) { + for i := d.keyStateOffset; i < len(inputBuffer); i++ { + keyIndex := uint8(i - d.keyStateOffset) + if inputBuffer[i] != d.keyState[keyIndex] { + d.keyState[keyIndex] = inputBuffer[i] kch <- Key{ Index: keyIndex, Pressed: inputBuffer[i] == 1, @@ -476,26 +471,26 @@ func (device *Device) sendButtonKeyEventsToChannel(inputBuffer []byte, kch chan } } -func (device *Device) updateLastActionTimeToNow() { - device.sleepMutex.Lock() - device.lastActionTime = time.Now() - device.sleepMutex.Unlock() +func (d *Device) updateLastActionTimeToNow() { + d.sleepMutex.Lock() + d.lastActionTime = time.Now() + d.sleepMutex.Unlock() } // ScreenSegmentWidth returns the width of a screen segment. Returns 0 if there are no segments. -func (device *Device) ScreenSegmentWidth() uint { - if device.ScreenSegmentsAmount == 0 { +func (d *Device) ScreenSegmentWidth() uint { + if d.ScreenSegmentsAmount == 0 { return 0 } - return device.ScreenWidth / uint(device.ScreenSegmentsAmount) + return d.ScreenWidth / uint(d.ScreenSegmentsAmount) } // ScreenSegmentHeight returns the width of a screen segment. Returns 0 if there are no segments. -func (device *Device) ScreenSegmentHeight() uint { - if device.ScreenSegmentsAmount == 0 { +func (d *Device) ScreenSegmentHeight() uint { + if d.ScreenSegmentsAmount == 0 { return 0 } - return device.ScreenHeight + return d.ScreenHeight } // Sleep puts the device asleep, waiting for a key event to wake it up. @@ -665,21 +660,19 @@ func (d Device) SetImage(index uint8, img image.Image) error { // SetTouchScreenSegmentImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image // needs to be in the correct resolution for the device. The index starts with // 0 to Device.ScreenSegmentsAmount-1. -func (device Device) SetTouchScreenSegmentImage(segmentIndex uint8, img image.Image) error { - +func (d Device) SetTouchScreenSegmentImage(segmentIndex uint8, img image.Image) error { position := image.Point{ - X: int(uint(segmentIndex) * device.ScreenSegmentWidth()), + X: int(uint(segmentIndex) * d.ScreenSegmentWidth()), Y: 0, } - return device.SetTouchScreenImage(position, device.ScreenSegmentWidth(), device.ScreenSegmentHeight(), img) + return d.SetTouchScreenImage(position, d.ScreenSegmentWidth(), d.ScreenSegmentHeight(), img) } // SetTouchScreenImage sets the image of the Stream Deck Plus touch screen at the given point. The provided image // needs to be in the correct resolution for the device. -func (device Device) SetTouchScreenImage(position image.Point, width uint, height uint, img image.Image) error { - - imageBytes, err := device.transformImage(img) +func (d Device) SetTouchScreenImage(position image.Point, width uint, height uint, img image.Image) error { + imageBytes, err := d.transformImage(img) if err != nil { return fmt.Errorf("cannot convert image data: %v", err) @@ -687,22 +680,22 @@ func (device Device) SetTouchScreenImage(position image.Point, width uint, heigh imageData := imageData{ image: imageBytes, - pageSize: device.screenPageSize - device.screenPageHeaderSize, + pageSize: d.screenPageSize - d.screenPageHeaderSize, } - data := make([]byte, device.screenPageSize) + data := make([]byte, d.screenPageSize) var page int var lastPage bool for !lastPage { var payload []byte payload, lastPage = imageData.Page(page) - header := device.screenPageHeader(page, position, width, height, len(payload), lastPage) + header := d.screenPageHeader(page, position, width, height, len(payload), lastPage) copy(data, header) copy(data[len(header):], payload) - _, err := device.device.Write(data) + _, err := d.device.Write(data) if err != nil { return fmt.Errorf("cannot write image page %d of %d (%d image bytes) %d bytes: %v", page, imageData.PageCount(), imageData.Length(), len(data), err) @@ -759,17 +752,17 @@ func toRGBA(img image.Image) *image.RGBA { } // transformImage transforms the image for sending it to the device. -func (device *Device) transformImage(img image.Image) ([]byte, error) { - if device.flipImage != nil { - img = device.flipImage(img) +func (d *Device) transformImage(img image.Image) ([]byte, error) { + if d.flipImage != nil { + img = d.flipImage(img) } - return device.toImageFormat(img) + return d.toImageFormat(img) } -func (device *Device) isAwakened() bool { - if device.asleep { - _ = device.Wake() +func (d *Device) isAwakened() bool { + if d.asleep { + _ = d.Wake() return true } @@ -931,7 +924,6 @@ func rev2ImagePageHeader(pageIndex int, keyIndex uint8, payloadLength int, lastP // touchScreenImagePageHeader returns the image page header sequence used by Stream // Deck Plus for the touch screen. func touchScreenImagePageHeader(page int, position image.Point, width uint, height uint, payloadLength int, lastPage bool) []byte { - var lastPageByte byte if lastPage { lastPageByte = 1 From cd11d91f4633c386b39c5c631480807b3fbb6da9 Mon Sep 17 00:00:00 2001 From: SomeoneOnEarth Date: Wed, 3 May 2023 13:34:14 +0200 Subject: [PATCH 14/17] Improve readability --- streamdeck.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index 064fa53..fb1e7af 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -339,7 +339,8 @@ func readKeysForButtonsOnlyInput(d *Device) (chan Key, error) { return } - if d.isAwakened() { + if d.Asleep() { + _ = d.Wake() resetKeysStates(d, keyBuffer) // Dont trigger a key event, because the key awoke the device continue @@ -364,7 +365,8 @@ func readKeysForMultipleInputTypes(device *Device) (chan Key, error) { return } - if device.isAwakened() { + if device.Asleep() { + _ = device.Wake() resetKeysStates(device, inputBuffer) // Dont trigger a key event, because the key awoke the device continue @@ -760,15 +762,6 @@ func (d *Device) transformImage(img image.Image) ([]byte, error) { return d.toImageFormat(img) } -func (d *Device) isAwakened() bool { - if d.asleep { - _ = d.Wake() - return true - } - - return false -} - func resetKeysStates(device *Device, inputBuffer []byte) { // reset state so no spurious key events get triggered for i := device.keyStateOffset; i < len(inputBuffer); i++ { From ebfc80e2d52571c5e532b4f1537973e0888b0568 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 16 Jun 2023 13:19:33 +0200 Subject: [PATCH 15/17] docs: add hardware support paragraph to README --- README.md | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c71ade8..91163c1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Go ReportCard](https://goreportcard.com/badge/github.com/muesli/streamdeck?style=for-the-badge)](https://goreportcard.com/report/muesli/streamdeck) [![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](https://pkg.go.dev/github.com/muesli/streamdeck) -A CLI application and Go library to control your Elgato Stream Deck on Linux. +A Go library and CLI to control your Elgato Stream Deck on Linux. If you're looking for a complete Linux service to control your StreamDeck, check out [Deckmaster](https://github.com/muesli/deckmaster), which is based on this @@ -66,20 +66,47 @@ Reset the device: streamdeck-cli reset ``` -## Stream Deck Plus +## Hardware Support -At the current state, the knobs and touch screen usage will be transformed to "normal button keys indexes". +
+Stream Deck Mini -### Normal Buttons +#### Regular Buttons -The 8 "normal" buttons have the key index 0 - 7 (top left to bottom right) +The 6 regular buttons are mapped to key index 0 - 5 (top left to bottom right). +
-### Touch Screen +
+Stream Deck (including MK.2) -The touch screen is divided into four horizontal segments (matching the numbers of knobs): -Segment index is from 0 - 3 (from left to right) +#### Regular Buttons -The key indexes for touch screen usages are: +The 15 regular buttons are mapped to key index 0 - 14 (top left to bottom right). +
+ +
+Stream Deck XL + +#### Regular Buttons + +The 32 regular buttons are mapped to key index 0 - 31 (top left to bottom right). +
+ +
+Stream Deck Plus + +Knobs and touchscreen usage are mapped to regular button key indexes. + +#### Regular Buttons + +The 8 regular buttons are mapped to key index 0 - 7 (top left to bottom right). + +#### Touchscreen + +The touchscreen is divided into four horizontal segments (matching the +number of buttons and knobs per row) with index 0 - 3 (from left to right). + +The key indexes for touchscreen usage are: Segment 0 - Short touch: 20 @@ -97,13 +124,13 @@ Segment 3 - Short touch: 23 - Long touch: 27 -All touch screen presses are not "holdable" like normal buttons. +All touchscreen presses are not "holdable" like normal buttons. Swiping is mapped to: - From left to right: 28 - From right to left: 29 -### Knobs +#### Knobs The knobs usages will be mapped to key indexes as following (left to right): @@ -126,6 +153,7 @@ Knob 4 - Press: 11 (holdable) - Left turn: 15 - Right turn: 19 +
## Feedback From 3b2995085abfee7d5d394b1f220285e7aa6be4a6 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 23 Jun 2023 17:13:59 +0200 Subject: [PATCH 16/17] chore: rename ScreenSegmentsAmount to ScreenSegments --- streamdeck.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index fb1e7af..fbdf6ca 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -80,11 +80,11 @@ type Device struct { DPI uint Padding uint - ScreenWidth uint - ScreenHeight uint - ScreenVerticalDPI uint - ScreenHorizontalDPI uint - ScreenSegmentsAmount uint8 + ScreenWidth uint + ScreenHeight uint + ScreenVerticalDPI uint + ScreenHorizontalDPI uint + ScreenSegments uint8 Knobs uint8 @@ -247,7 +247,7 @@ func Devices() ([]Device, error) { ScreenHeight: 100, ScreenVerticalDPI: 181, //14mm and 100px ScreenHorizontalDPI: 188, //108mm and 800px - ScreenSegmentsAmount: 4, + ScreenSegments: 4, Knobs: 4, featureReportSize: 32, firmwareOffset: 6, @@ -402,16 +402,16 @@ func (d *Device) sendTouchEventsToChannel(inputBuffer []byte, kch chan Key) { if touchUsage == INPUT_TOUCH_USAGE_SHORT { keyIndex = d.Columns*d.Rows + 3*d.Knobs + segment } else if touchUsage == INPUT_TOUCH_USAGE_LONG { - keyIndex = d.Columns*d.Rows + 3*d.Knobs + d.ScreenSegmentsAmount + segment + keyIndex = d.Columns*d.Rows + 3*d.Knobs + d.ScreenSegments + segment } else if touchUsage == INPUT_TOUCH_USAGE_SWIPE { x2 := binary.LittleEndian.Uint16(inputBuffer[INPUT_POSITION_TOUCH_X2_ID:]) startSegment := uint8(math.Floor(float64(x) / 40.0)) stopSegment := uint8(math.Floor(float64(x2) / 40.0)) if startSegment < stopSegment { //left to right - keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegmentsAmount + keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegments } else if startSegment > stopSegment { //right to left - keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegmentsAmount + 1 + keyIndex = d.Columns*d.Rows + 3*d.Knobs + 2*d.ScreenSegments + 1 } else { return } @@ -481,15 +481,15 @@ func (d *Device) updateLastActionTimeToNow() { // ScreenSegmentWidth returns the width of a screen segment. Returns 0 if there are no segments. func (d *Device) ScreenSegmentWidth() uint { - if d.ScreenSegmentsAmount == 0 { + if d.ScreenSegments == 0 { return 0 } - return d.ScreenWidth / uint(d.ScreenSegmentsAmount) + return d.ScreenWidth / uint(d.ScreenSegments) } // ScreenSegmentHeight returns the width of a screen segment. Returns 0 if there are no segments. func (d *Device) ScreenSegmentHeight() uint { - if d.ScreenSegmentsAmount == 0 { + if d.ScreenSegments == 0 { return 0 } return d.ScreenHeight @@ -661,7 +661,7 @@ func (d Device) SetImage(index uint8, img image.Image) error { // SetTouchScreenSegmentImage sets the image of a segment of the Stream Deck Plus touch screen. The provided image // needs to be in the correct resolution for the device. The index starts with -// 0 to Device.ScreenSegmentsAmount-1. +// 0 to Device.ScreenSegments-1. func (d Device) SetTouchScreenSegmentImage(segmentIndex uint8, img image.Image) error { position := image.Point{ X: int(uint(segmentIndex) * d.ScreenSegmentWidth()), From 833e526dd60c9c1567703c4a9a0a6573207f8400 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 7 Jul 2023 15:24:26 +0200 Subject: [PATCH 17/17] chore: unify vertical & horizontal DPIs --- streamdeck.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/streamdeck.go b/streamdeck.go index fbdf6ca..83f42e8 100644 --- a/streamdeck.go +++ b/streamdeck.go @@ -80,11 +80,10 @@ type Device struct { DPI uint Padding uint - ScreenWidth uint - ScreenHeight uint - ScreenVerticalDPI uint - ScreenHorizontalDPI uint - ScreenSegments uint8 + ScreenWidth uint + ScreenHeight uint + ScreenDPI uint + ScreenSegments uint8 Knobs uint8 @@ -245,8 +244,7 @@ func Devices() ([]Device, error) { Padding: 16, ScreenWidth: 800, ScreenHeight: 100, - ScreenVerticalDPI: 181, //14mm and 100px - ScreenHorizontalDPI: 188, //108mm and 800px + ScreenDPI: 184, //108x14mm at 800x100px ScreenSegments: 4, Knobs: 4, featureReportSize: 32,