@@ -8,12 +8,56 @@ import (
8
8
"github.com/karalabe/hid"
9
9
)
10
10
11
- const vendorID = 4057
12
- const productID = 0x6c
11
+ const vendorID = 0x0fd9
12
+
13
+ // deviceType represents one of the various types of StreamDeck (mini/orig/orig2/xl)
14
+ type deviceType struct {
15
+ name string
16
+ imageSize image.Point
17
+ usbProductID uint16
18
+ resetPacket []byte
19
+ numberOfButtons uint
20
+ brightnessPacket []byte
21
+ buttonReadOffset uint
22
+ imageFormat string
23
+ imagePayloadPerPage uint
24
+ imageHeaderFunc func (bytesRemaining uint , btnIndex uint , pageNumber uint ) []byte
25
+ }
26
+
27
+ var deviceTypes []deviceType
28
+
29
+ // RegisterDevicetype allows the declaration of a new type of device, intended for use by subpackage "devices"
30
+ func RegisterDevicetype (
31
+ name string ,
32
+ imageSize image.Point ,
33
+ usbProductID uint16 ,
34
+ resetPacket []byte ,
35
+ numberOfButtons uint ,
36
+ brightnessPacket []byte ,
37
+ buttonReadOffset uint ,
38
+ imageFormat string ,
39
+ imagePayloadPerPage uint ,
40
+ imageHeaderFunc func (bytesRemaining uint , btnIndex uint , pageNumber uint ) []byte ,
41
+ ) {
42
+ d := deviceType {
43
+ name : name ,
44
+ imageSize : imageSize ,
45
+ usbProductID : usbProductID ,
46
+ resetPacket : resetPacket ,
47
+ numberOfButtons : numberOfButtons ,
48
+ brightnessPacket : brightnessPacket ,
49
+ buttonReadOffset : buttonReadOffset ,
50
+ imageFormat : imageFormat ,
51
+ imagePayloadPerPage : imagePayloadPerPage ,
52
+ imageHeaderFunc : imageHeaderFunc ,
53
+ }
54
+ deviceTypes = append (deviceTypes , d )
55
+ }
13
56
14
57
// Device is a struct which represents an actual Streamdeck device, and holds its reference to the USB HID device
15
58
type Device struct {
16
59
fd * hid.Device
60
+ deviceType deviceType
17
61
buttonPressListeners []func (int , * Device , error )
18
62
}
19
63
@@ -29,16 +73,23 @@ func OpenWithoutReset() (*Device, error) {
29
73
30
74
// Opens a new StreamdeckXL device, and returns a handle
31
75
func rawOpen (reset bool ) (* Device , error ) {
32
- devices := hid .Enumerate (vendorID , productID )
76
+ devices := hid .Enumerate (vendorID , 0 )
33
77
if len (devices ) == 0 {
34
- return nil , errors .New ("no stream deck device found" )
78
+ return nil , errors .New ("No elgato devices found" )
35
79
}
80
+
81
+ retval := & Device {}
36
82
id := 0
83
+ // Iterate over the known device types, matching to product ID
84
+ for _ , devType := range deviceTypes {
85
+ if devices [id ].ProductID == devType .usbProductID {
86
+ retval .deviceType = devType
87
+ }
88
+ }
37
89
dev , err := devices [id ].Open ()
38
90
if err != nil {
39
91
return nil , err
40
92
}
41
- retval := & Device {}
42
93
retval .fd = dev
43
94
if reset {
44
95
retval .ResetComms ()
@@ -47,6 +98,11 @@ func rawOpen(reset bool) (*Device, error) {
47
98
return retval , nil
48
99
}
49
100
101
+ // GetName returns the name of the type of Streamdeck
102
+ func (d * Device ) GetName () string {
103
+ return d .deviceType .name
104
+ }
105
+
50
106
// Close the device
51
107
func (d * Device ) Close () {
52
108
d .fd .Close ()
@@ -62,21 +118,27 @@ func (d *Device) SetBrightness(pct int) {
62
118
pct = 100
63
119
}
64
120
65
- payload := []byte {'\x03' , '\x08' , byte (pct )}
121
+ preamble := d .deviceType .brightnessPacket
122
+ payload := append (preamble , byte (pct ))
66
123
d .fd .SendFeatureReport (payload )
67
124
}
68
125
69
126
// ClearButtons writes a black square to all buttons
70
127
func (d * Device ) ClearButtons () {
71
- for i := 0 ; i < 32 ; i ++ {
128
+ numButtons := int (d .deviceType .numberOfButtons )
129
+ for i := 0 ; i < numButtons ; i ++ {
72
130
d .WriteColorToButton (i , color .Black )
73
131
}
74
132
}
75
133
76
134
// WriteColorToButton writes a specified color to the given button
77
- func (d * Device ) WriteColorToButton (btnIndex int , colour color.Color ) {
135
+ func (d * Device ) WriteColorToButton (btnIndex int , colour color.Color ) error {
78
136
img := getSolidColourImage (colour )
79
- d .rawWriteToButton (btnIndex , getImageAsJpeg (img ))
137
+ imgForButton , err := getImageForButton (img , d .deviceType .imageFormat )
138
+ if err != nil {
139
+ return err
140
+ }
141
+ return d .rawWriteToButton (btnIndex , imgForButton )
80
142
}
81
143
82
144
// WriteImageToButton writes a specified image file to the given button
@@ -90,18 +152,19 @@ func (d *Device) WriteImageToButton(btnIndex int, filename string) error {
90
152
}
91
153
92
154
func (d * Device ) buttonPressListener () {
93
- var buttonMask [32 ]bool
155
+ var buttonMask []bool
156
+ buttonMask = make ([]bool , d .deviceType .numberOfButtons )
94
157
for {
95
- data := make ([]byte , 50 )
158
+ data := make ([]byte , d . deviceType . numberOfButtons + d . deviceType . buttonReadOffset )
96
159
_ , err := d .fd .Read (data )
97
160
if err != nil {
98
161
d .sendButtonPressEvent (- 1 , err )
99
162
break
100
163
}
101
- for i := 0 ; i < 32 ; i ++ {
102
- if data [4 + i ] == 1 {
164
+ for i := uint ( 0 ) ; i < d . deviceType . numberOfButtons ; i ++ {
165
+ if data [d . deviceType . buttonReadOffset + i ] == 1 {
103
166
if ! buttonMask [i ] {
104
- d .sendButtonPressEvent (i , nil )
167
+ d .sendButtonPressEvent (int ( i ) , nil )
105
168
}
106
169
buttonMask [i ] = true
107
170
} else {
@@ -124,48 +187,43 @@ func (d *Device) ButtonPress(f func(int, *Device, error)) {
124
187
125
188
// ResetComms will reset the comms protocol to the StreamDeck; useful if things have gotten de-synced, but it will also reboot the StreamDeck
126
189
func (d * Device ) ResetComms () {
127
- payload := [] byte { '\x03' , '\x02' }
190
+ payload := d . deviceType . resetPacket
128
191
d .fd .SendFeatureReport (payload )
129
192
}
130
193
131
194
// WriteRawImageToButton takes an `image.Image` and writes it to the given button, after resizing and rotating the image to fit the button (for some reason the StreamDeck screens are all upside down)
132
195
func (d * Device ) WriteRawImageToButton (btnIndex int , rawImg image.Image ) error {
133
- img := resizeAndRotate (rawImg , 96 , 96 )
134
- return d .rawWriteToButton (btnIndex , getImageAsJpeg (img ))
196
+ img := resizeAndRotate (rawImg , d .deviceType .imageSize .X , d .deviceType .imageSize .Y )
197
+ imgForButton , err := getImageForButton (img , d .deviceType .imageFormat )
198
+ if err != nil {
199
+ return err
200
+ }
201
+ return d .rawWriteToButton (btnIndex , imgForButton )
135
202
}
136
203
137
204
func (d * Device ) rawWriteToButton (btnIndex int , rawImage []byte ) error {
138
205
// Based on set_key_image from https://github.com/abcminiuser/python-elgato-streamdeck/blob/master/src/StreamDeck/Devices/StreamDeckXL.py#L151
139
206
pageNumber := 0
140
207
bytesRemaining := len (rawImage )
141
208
142
- imageReportLength := 1024
143
- imageReportHeaderLength := 8
144
- imageReportPayloadLength := imageReportLength - imageReportHeaderLength
145
-
146
209
// Surely no image can be more than 20 packets...?
147
210
payloads := make ([][]byte , 20 )
148
211
149
212
for bytesRemaining > 0 {
213
+
214
+ header := d .deviceType .imageHeaderFunc (uint (bytesRemaining ), uint (btnIndex ), uint (pageNumber ))
215
+ imageReportLength := int (d .deviceType .imagePayloadPerPage )
216
+ imageReportHeaderLength := len (header )
217
+ imageReportPayloadLength := imageReportLength - imageReportHeaderLength
218
+
150
219
thisLength := 0
151
220
if imageReportPayloadLength < bytesRemaining {
152
221
thisLength = imageReportPayloadLength
153
222
} else {
154
223
thisLength = bytesRemaining
155
224
}
156
- bytesSent := pageNumber * imageReportPayloadLength
157
- header := []byte {'\x02' , '\x07' , byte (btnIndex )}
158
- if thisLength == bytesRemaining {
159
- header = append (header , '\x01' )
160
- } else {
161
- header = append (header , '\x00' )
162
- }
163
-
164
- header = append (header , byte (thisLength & 0xff ))
165
- header = append (header , byte (thisLength >> 8 ))
166
225
167
- header = append (header , byte (pageNumber & 0xff ))
168
- header = append (header , byte (pageNumber >> 8 ))
226
+ bytesSent := pageNumber * imageReportPayloadLength
169
227
170
228
payload := append (header , rawImage [bytesSent :(bytesSent + thisLength )]... )
171
229
padding := make ([]byte , imageReportLength - len (payload ))
@@ -177,7 +235,7 @@ func (d *Device) rawWriteToButton(btnIndex int, rawImage []byte) error {
177
235
bytesRemaining = bytesRemaining - thisLength
178
236
pageNumber = pageNumber + 1
179
237
if pageNumber >= len (payloads ) {
180
- return errors .New ("Image too big for button, aborting. You probably need to reset the Streamdeck at this stage, and modify the size of `payloads` on line 142 to be something bigger. " )
238
+ return errors .New ("Image too big for button comms , aborting - you probably need to reset the Streamdeck at this stage, and modify the size of `payloads` on line 142 to be something bigger" )
181
239
}
182
240
}
183
241
return nil
0 commit comments