loupedeck-commander 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +54 -3
  2. package/VERSION.md +33 -0
  3. package/common/BaseLoupeDeckHandler.mjs +8 -8
  4. package/common/touchbuttons.mjs +173 -273
  5. package/{ExampleDeviceHandler.mjs → example/ExampleDeviceHandler.mjs} +1 -1
  6. package/icons/bulb.png +0 -0
  7. package/icons/bulb_filled.png +0 -0
  8. package/icons/home.png +0 -0
  9. package/icons/home_filled.png +0 -0
  10. package/icons/mountain.png +0 -0
  11. package/icons/user.png +0 -0
  12. package/icons/user_filled.png +0 -0
  13. package/icons/volume-mute.png +0 -0
  14. package/icons/volume-mute_filled.png +0 -0
  15. package/icons/volume.png +0 -0
  16. package/icons/volume_filled.png +0 -0
  17. package/package.json +1 -1
  18. package/profile-1.json +36 -132
  19. package/icons/A-G_n.png +0 -0
  20. package/icons/A-G_p.png +0 -0
  21. package/icons/A-H_n.png +0 -0
  22. package/icons/A-H_p.png +0 -0
  23. package/icons/Audio_Plug_n.png +0 -0
  24. package/icons/Audio_Plug_p.png +0 -0
  25. package/icons/Comp_A_n.png +0 -0
  26. package/icons/Comp_A_p.png +0 -0
  27. package/icons/Comp_B_n.png +0 -0
  28. package/icons/Comp_B_p.png +0 -0
  29. package/icons/Comp_C_n.png +0 -0
  30. package/icons/Comp_C_p.png +0 -0
  31. package/icons/Comp_D_n.png +0 -0
  32. package/icons/Comp_D_p.png +0 -0
  33. package/icons/Comp_E_n.png +0 -0
  34. package/icons/Comp_E_p.png +0 -0
  35. package/icons/Comp_F_n.png +0 -0
  36. package/icons/Comp_F_p.png +0 -0
  37. package/icons/Comp_G_n.png +0 -0
  38. package/icons/Comp_G_p.png +0 -0
  39. package/icons/Comp_H_n.png +0 -0
  40. package/icons/Comp_H_p.png +0 -0
  41. package/icons/Mic_n.png +0 -0
  42. package/icons/Mic_p.png +0 -0
  43. package/icons/Sound_VolHigh.png +0 -0
  44. package/icons/Sound_VolLow.png +0 -0
  45. package/icons/Sound_VolMid.png +0 -0
  46. package/icons/Sound_Voldown.png +0 -0
  47. package/icons/Sound_Volup.png +0 -0
  48. package/icons/Sound_off.png +0 -0
  49. package/icons/cia.png +0 -0
  50. package/icons/saph.png +0 -0
  51. /package/{example.mjs → example/example.mjs} +0 -0
package/README.md CHANGED
@@ -5,14 +5,33 @@
5
5
  The loupedeck-commander is helping you using your Loupedeck device more easily, and connect it with custom commands you define with script files.
6
6
 
7
7
  Features:
8
+
8
9
  - Reference image files for every state of your button in the touch-display fields
9
10
  - Connect commands to your state transitions
11
+ - Tested with following LoupeDeck devices:
12
+ - [LoupeDeck Live](https://loupedeck.com/products/loupedeck-live/)
13
+ - [LoupeDeck CT](https://loupedeck.com/products/loupedeck-ct/)
14
+
15
+ Runs on:
16
+
17
+ - Linux (Tested with Ubuntu 22.04 and Debian 11/12 on x64 & arm32/arm64)
18
+ - Windows (Tested with Windows 10/11 on x64)
19
+
20
+ Small footprint
21
+
22
+ - Raspberry PI Zero is suitable to run this
23
+
24
+ Please take care about the following:
25
+
26
+ - LoupeDeck devices after `version 0.2.x` use a serial interface instead of WebSocket.
27
+ When using this library please upgrade your firmware using the [LoupeDeck Software](https://loupedeck.com/downloads/)
28
+ Tested with Firmware version [`version 0.2.5`](https://support.loupedeck.com/f-a-q-support#firmware-connectivity-issues)
10
29
 
11
30
  ## Basic Usage
12
31
 
13
32
  Do the following steps:
14
33
 
15
- ```
34
+ ```bash
16
35
  # init your new node package
17
36
  npm init
18
37
 
@@ -23,10 +42,11 @@ npm install -s loupedeck-commander
23
42
  Create a new configuration file with at least one profile or copy from here,
24
43
  and replace the image references with your own icons (90x90px size):
25
44
 
26
- - [config.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.json)
27
- - [profile-1.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.json)
45
+ - [config.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/config.json)
46
+ - [profile-1.json](https://gitlab.com/keckxde/loupedeck-commander/-/blob/main/profile-1.json)
28
47
 
29
48
  Create a `index.mjs` file to open up your loupedeck connection:
49
+
30
50
  ```javascript
31
51
  # save as index.mjs
32
52
  import { BaseLoupeDeckHandler } from 'loupedeck-commander'
@@ -49,3 +69,34 @@ Run the script using the following command:
49
69
  ```bash
50
70
  node index.mjs
51
71
  ```
72
+
73
+ If you end up with errors related to canvas module - please install the dependencies as mentioned below, and run `npm install -s` again
74
+
75
+ ## Hints
76
+
77
+ ### CANVAS Module - additional effort needed
78
+
79
+ The library is using [canvas](https://www.npmjs.com/package/canvas) to load images and render graphical content on the LoupeDeck devices.
80
+ Please follow the instructions to install the preconditions, to properly use canvas on your system.
81
+
82
+ Example for Ubuntu:
83
+
84
+ ```bash
85
+ # different build & graphic dev-libs are needed to use canvas module:
86
+ sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
87
+ ```
88
+
89
+ ### Resizing of images
90
+
91
+ The buttons images shown on the different buttons should be prepared with specific resolutions:
92
+
93
+ - Main/Center Touch-Area: 90px x 90px per Button
94
+ - Left/Right Touch-Area: 90px x 270px per Button
95
+ - LoupeDeck CT Knob Touch-Area: 240px x 240px Button
96
+
97
+ if you already have suitable icons with the right aspect ratio, you could resize them with imagemagick/mogrify:
98
+
99
+ ```bash
100
+ mkdir resized
101
+ mogrify -resize 90x90 -quality 100 -path resized *.png
102
+ ```
package/VERSION.md ADDED
@@ -0,0 +1,33 @@
1
+ # Versions
2
+
3
+ ## 1.0.2
4
+
5
+ 1. Updated button rendering strategy:
6
+ - Render Background Color - per Button State
7
+ - Render Image on Top (if available), works well with transparent images
8
+
9
+ 2. Updated configuration strategy:
10
+ - Keep all state related attributes per button-state also in config:
11
+ example:
12
+
13
+ ```json
14
+ "states": {
15
+ "off": {
16
+ "color": "#aaaaaa",
17
+ "image": "icons/bulb.png",
18
+ "cmd": "echo \"{id} {state}\""
19
+ },
20
+ "on": {
21
+ "color": "#11ff11",
22
+ "image": "icons/bulb.png",
23
+ "cmd": "echo \"{id} {state}\""
24
+ }
25
+ ```
26
+
27
+ ## 1.0.1
28
+
29
+ bugfixes and documentation
30
+
31
+ ## 1.0.0
32
+
33
+ first public release
@@ -1,6 +1,6 @@
1
1
  import pkg from 'loupedeck'
2
2
  import { ApplicationConfig } from './ApplicationConfig.mjs'
3
- import { TouchButtonField, PhysicalButtonField } from './touchbuttons.mjs'
3
+ import { ButtonField } from './touchbuttons.mjs'
4
4
  const { discover, HAPTIC } = pkg
5
5
 
6
6
  export class BaseLoupeDeckHandler {
@@ -76,15 +76,15 @@ export class BaseLoupeDeckHandler {
76
76
  const dKnob = this.device.displays.knob
77
77
 
78
78
  const profile = this.getCurrentProfile()
79
- this.screens.center = new TouchButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
80
- this.screens.left = new TouchButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
81
- this.screens.right = new TouchButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right)
79
+ this.screens.center = new ButtonField('center', this.device.rows, this.device.columns, dCenter.width, dCenter.height, profile.touch.center)
80
+ this.screens.left = new ButtonField('left', 1, 1, dLeft.width, dLeft.height, profile.touch.left)
81
+ this.screens.right = new ButtonField('right', 1, 1, dRight.width, dRight.height, profile.touch.right)
82
82
  // knob Display is only available in the CT-version - not with live:
83
83
  if (dKnob) {
84
- this.screens.knob = new TouchButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob)
84
+ this.screens.knob = new ButtonField('knob', 1, 1, dKnob.width, dKnob.height, profile.touch.knob)
85
85
  }
86
86
 
87
- this.buttons = new PhysicalButtonField('bottom', profile.buttons)
87
+ this.buttons = new ButtonField('buttons', 1, 1, 0, 0, profile.buttons)
88
88
 
89
89
  await this.screens.center.load()
90
90
  await this.screens.right.load()
@@ -155,7 +155,7 @@ export class BaseLoupeDeckHandler {
155
155
  }
156
156
 
157
157
  async onTouchMove (event) {
158
- const ok = false
158
+ let ok = false
159
159
  const changedTouches = event.changedTouches
160
160
  for (let i = 0; i < changedTouches.length; i++) {
161
161
  const x = changedTouches[i].x
@@ -165,7 +165,7 @@ export class BaseLoupeDeckHandler {
165
165
  if (id === undefined) {
166
166
  id = 0 // for left/right
167
167
  }
168
- console.info('TouchMove : ', screen, id, x, y)
168
+ ok = await this.screens[screen].touchmove(id, x, y)
169
169
  }
170
170
  return ok
171
171
  }
@@ -3,14 +3,6 @@ import { sh } from './cmd-executer.mjs'
3
3
  import format from 'string-template'
4
4
  import { calcDelta } from './utils.mjs'
5
5
 
6
- const ButtonStyle = {
7
- NONE: 0,
8
- IMAGE: 1,
9
- COLOR: 2,
10
- TEXT: 3,
11
- PHYSICAL: 3
12
- }
13
-
14
6
  export const ButtonIndex = {
15
7
  BUTN_0: 0,
16
8
  BUTN_1: 1,
@@ -29,35 +21,58 @@ const ButtonType = {
29
21
  PUSH: 'PUSH'
30
22
  }
31
23
 
32
- export class TouchButtonField {
33
- #buttons = []
24
+ export class ButtonField {
25
+ #buttons = {}
26
+ #screen
34
27
  width = 0
35
28
  height = 0
36
29
  #rows = 0
37
30
  #columns = 0
31
+ #keys = []
32
+ #type
38
33
  #name
39
34
  constructor (name, rows, columns, width, height, data) {
40
- console.info(`TouchButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
35
+ console.info(`ButtonField ${name.padEnd(10, ' ')} Buttons: ${rows} x ${columns} , Pixels ${width} x ${height}`)
41
36
  this.#name = name
42
37
  this.width = width
43
38
  this.height = height
44
39
  this.#rows = rows
45
40
  this.#columns = columns
46
- for (let i = 0; i < rows * columns; i++) {
47
- const tb = new TouchButton(`touch-${i}`, width / columns, height / rows, data[i])
48
- this.#buttons.push(tb)
41
+ this.#screen = this.width > 0 && this.height > 0
42
+ this.#type = 'button'
43
+ if (this.#screen) { this.#type = 'touch' }
44
+
45
+ const keys = Object.keys(data)
46
+ for (let i = 0; i < keys.length; i++) {
47
+ const key = keys[i]
48
+ const tb = new Button(`${this.#type}-${key}`, width / columns, height / rows, data[key])
49
+ this.#buttons[key] = tb
49
50
  }
51
+
52
+ this.#keys = keys
50
53
  }
51
54
 
52
55
  async draw (device) {
53
- device.drawScreen(this.#name, ctx => {
54
- for (let i = 0; i < this.#rows * this.#columns; i++) {
55
- const row = Math.floor(i / device.columns)
56
- const column = i % device.columns
57
-
58
- this.#buttons[i].draw(row, column, ctx)
56
+ if (!this.#screen) {
57
+ // physical buttons:
58
+ for (let i = 0; i < this.#keys.length; i++) {
59
+ const key = this.#keys[i]
60
+ this.#buttons[key].drawPhysical(device, key)
59
61
  }
60
- })
62
+ } else {
63
+ // screen:
64
+ device.drawScreen(this.#name, ctx => {
65
+ ctx.globalCompositeOperation = 'source-atop'
66
+ for (let i = 0; i < this.#keys.length; i++) {
67
+ const key = this.#keys[i]
68
+ const iValue = parseInt(key, 10)
69
+ const row = Math.floor(iValue / device.columns)
70
+ const column = iValue % device.columns
71
+
72
+ this.#buttons[key].draw(row, column, ctx)
73
+ }
74
+ })
75
+ }
61
76
  }
62
77
 
63
78
  setIntState (id, val) {
@@ -65,127 +80,94 @@ export class TouchButtonField {
65
80
  }
66
81
 
67
82
  async load () {
68
- for (let i = 0; i < this.#rows * this.#columns; i++) {
69
- await this.#buttons[i].load()
83
+ for (let i = 0; i < this.#keys.length; i++) {
84
+ const key = this.#keys[i]
85
+ if (isNaN(key)) {
86
+ await this.#buttons[key].load()
87
+ } else {
88
+ const iVal = parseInt(key, 10)
89
+ await this.#buttons[iVal].load()
90
+ }
70
91
  }
71
92
  }
72
93
 
73
94
  async pressed (id) {
74
- // console.info(`pressed ${id}`)
75
- await this.#buttons[id].pressed()
95
+ this.checkAndCreateButton(id)
96
+ const result = await this.#buttons[id].pressed()
97
+ if (!result) {
98
+ console.info(`pressed ${this.#type} ${id}`)
99
+ }
100
+ return result
76
101
  }
77
102
 
78
103
  async released (id) {
79
- // console.info(`released ${id}`)
80
104
  const result = await this.#buttons[id].released()
81
105
  if (result) {
82
106
  // disable all other buttons of the group, if this one had been activated:
83
- for (let i = 0; i < this.#rows * this.#columns; i++) {
84
- if (id === i) { continue }
85
- if (this.#buttons[i].group === this.#buttons[id].group) {
86
- this.#buttons[i].setState(0)
107
+ for (let i = 0; i < this.#keys.length; i++) {
108
+ let key = this.#keys[i]
109
+ if (!isNaN(key)) { key = parseInt(key, 10) }
110
+ if (id === key) { continue }
111
+ if (this.#buttons[key].group === this.#buttons[id].group) {
112
+ this.#buttons[key].setState(0)
87
113
  }
88
114
  }
89
115
  }
90
- return result
91
- }
92
- }
93
-
94
- export class PhysicalButtonField {
95
- #buttons = {}
96
- #name
97
- constructor (name, data) {
98
- console.info(`PhysicalButtonField ${name.padEnd(10, ' ')} `)
99
- this.#name = name
100
- const keys = Object.keys(data)
101
- for (let i = 0; i < keys.length; i++) {
102
- const key = keys[i]
103
- const tb = new TouchButton(key, 1, 1, data[key])
104
- this.#buttons[key] = tb
116
+ if (!result) {
117
+ console.info(`released ${this.#type} ${id}`)
105
118
  }
119
+ return result
106
120
  }
107
121
 
108
- async draw (device) {
109
- const keys = Object.keys(this.#buttons)
110
- for (let i = 0; i < keys.length; i++) {
111
- const key = keys[i]
112
- this.#buttons[key].drawPhysical(device, key)
113
- }
122
+ async rotated (id, delta) {
123
+ this.checkAndCreateButton(id)
124
+ const result = await this.#buttons[id].rotated(delta)
125
+ if (!result) { console.info(`rotated ${this.#type} ${id} ${delta}`) }
126
+ return result
114
127
  }
115
128
 
116
- async load () {
117
- const keys = Object.keys(this.#buttons)
118
- for (let i = 0; i < keys.length; i++) {
119
- const key = keys[i]
120
- await this.#buttons[key].load()
121
- }
129
+ async touchmove (id, x, y) {
130
+ this.checkAndCreateButton(id)
131
+ const result = await this.#buttons[id].touchmove(x, y)
132
+ if (!result) { console.info(`touchmove ${id} ${x} ${y}`) }
133
+ return result
122
134
  }
123
135
 
124
136
  checkAndCreateButton (id) {
125
137
  if (!(id in this.#buttons)) {
126
- const tb = new TouchButton(id, 1, 1, id)
138
+ const tb = new Button(id, 1, 1, id)
127
139
  this.#buttons[id] = tb
128
140
  }
129
141
  }
130
-
131
- setIntState (id, val) {
132
- this.checkAndCreateButton(id)
133
- this.#buttons[id].setIntState(val)
134
- }
135
-
136
- async pressed (id) {
137
- // console.info(`pressed ${id}`)
138
- this.checkAndCreateButton(id)
139
- return await this.#buttons[id].pressed()
140
- }
141
-
142
- async released (id) {
143
- // console.info(`released ${id}`)
144
- this.checkAndCreateButton(id)
145
- return await this.#buttons[id].released()
146
- }
147
-
148
- async rotated (id, delta) {
149
- // console.info(`rotated ${id} ${delta}`)
150
- this.checkAndCreateButton(id)
151
- return await this.#buttons[id].rotated(delta)
152
- }
153
142
  }
154
143
 
155
- export class TouchButton {
144
+ export class Button {
156
145
  width = 0
157
146
  height = 0
158
- #style
159
- #type
160
- #index = 0
161
- #states = []
147
+
148
+ #type = ButtonType.TOGGLE
149
+
162
150
  #min = 0
163
151
  #max = 100
164
152
  #value = 50
165
- #data
166
153
  #name = undefined
167
154
 
168
- #images = {}
169
- #colors = {
170
- off: '#000000'
171
- // on: '#110011'
172
- }
173
-
174
- #rgbs = {
175
- off: [0, 0, 0]
176
- // on: [0, 255, 0]
177
- }
178
-
179
- #commands = {
180
- off: '',
181
- on: ''
182
- }
155
+ #index = 0
156
+ #keys
157
+ #states
183
158
 
184
159
  group = ''
185
160
 
186
161
  text = ''
187
162
  font = '16px Arial'
188
163
 
164
+ #x
165
+ #y
166
+ #moveLeft
167
+ #moveRight
168
+ #moveUp
169
+ #moveDown
170
+
189
171
  // Timestamp when button was pressed
190
172
  timeStampPressed
191
173
  // Timestamp when button was released
@@ -200,29 +182,16 @@ export class TouchButton {
200
182
  this.width = width
201
183
  this.height = height
202
184
  this.#index = 0
203
- this.#style = ButtonStyle.NONE
204
- this.#type = ButtonType.TOGGLE
205
- this.#states = Object.keys(this.#rgbs)
206
185
 
207
- if (data) {
186
+ if (data && data.states) {
208
187
  this.group = data.group
209
- this.#style = ButtonStyle.PHYSICAL
210
- if (data.images && Object.keys(data.images).length > 0) {
211
- this.#style = ButtonStyle.IMAGE
212
- this.#states = Object.keys(data.images)
213
- } else if (data.colors && Object.keys(data.colors).length > 0) {
214
- this.#style = ButtonStyle.COLOR
215
- this.#states = Object.keys(data.colors)
216
- } else if (data.rgb && Object.keys(data.rgb).length > 0) {
217
- this.#states = Object.keys(data.rgb)
218
- }
219
- if (data.commands) {
220
- this.#commands = data.commands
221
- }
222
188
 
189
+ this.#states = data.states
190
+ this.#keys = Object.keys(this.#states)
223
191
  if (data.type) {
224
192
  this.#type = data.type.toUpperCase()
225
193
  }
194
+
226
195
  if (data.minPressed) {
227
196
  this.minPressed = data.minPressed
228
197
  }
@@ -230,12 +199,11 @@ export class TouchButton {
230
199
  if (data.text) {
231
200
  this.text = data.text
232
201
  }
233
- this.#data = data
234
202
  }
235
- }
236
-
237
- setButtonStyle (type) {
238
- this.#style = type
203
+ if (this.#states === undefined) {
204
+ this.#states = {}
205
+ this.#keys = []
206
+ }
239
207
  }
240
208
 
241
209
  setState (index = 0) {
@@ -243,10 +211,17 @@ export class TouchButton {
243
211
  }
244
212
 
245
213
  async drawPhysical (device, id) {
214
+ const elem = this.getCurrentElement()
215
+ if (!elem || !elem.color) { return }
216
+
217
+ const r = parseInt(elem.color.slice(1, 3), 16)
218
+ const g = parseInt(elem.color.slice(3, 5), 16)
219
+ const b = parseInt(elem.color.slice(5, 7), 16)
220
+
246
221
  try {
247
222
  const val = {
248
223
  id,
249
- color: this.getCurrentRGB()
224
+ color: `rgba(${r}, ${g}, ${b})`
250
225
  }
251
226
  device.setButtonColor(val)
252
227
  } catch (error) {
@@ -257,160 +232,49 @@ export class TouchButton {
257
232
  async draw (row, column, ctx) {
258
233
  const x = column * this.width
259
234
  const y = row * this.height
260
- switch (this.#style) {
261
- case ButtonStyle.IMAGE:
262
- ctx.drawImage(this.getCurrentStateImage(), x, y, this.width, this.height)
263
- break
264
- case ButtonStyle.COLOR:
265
- // Draw a Colored Rectangle
266
- ctx.fillStyle = this.getCurrentStateFill()
267
- ctx.fillRect(x, y, this.width, this.height)
268
- break
269
- case ButtonStyle.NONE:
270
- // Draw a Colored Rectangle
271
- ctx.fillStyle = this.getCurrentStateFill(0)
272
- ctx.fillRect(x, y, this.width, this.height)
273
- break
274
- }
275
235
 
276
- ctx.fillStyle = '#000000'
277
- ctx.font = this.font
278
- ctx.fillText(this.text, x + 10, y - 10)
279
- }
236
+ const elem = this.getCurrentElement()
280
237
 
281
- async load () {
282
- let bLoaded = false
283
- while (!bLoaded) {
284
- switch (this.#style) {
285
- case ButtonStyle.IMAGE:
286
- bLoaded = await this.loadAsImage()
287
- break
288
- case ButtonStyle.COLOR:
289
- bLoaded = await this.loadAsColor()
290
- break
291
- case ButtonStyle.PHYSICAL:
292
- bLoaded = await this.loadAsRGB()
293
- break
294
- default:
295
- bLoaded = true
238
+ if (elem) {
239
+ if (elem.color) {
240
+ ctx.fillStyle = elem.color
241
+ ctx.fillRect(x, y, this.width, this.height)
242
+ }
243
+ if (elem.imgBuffer) {
244
+ ctx.drawImage(elem.imgBuffer, x, y, this.width, this.height)
296
245
  }
297
246
  }
298
- }
299
247
 
300
- async loadAsImage () {
301
- this.#states = Object.keys(this.#data.images)
302
- if (this.#states.length <= 0) {
303
- this.#style = ButtonStyle.COLOR
304
- return false
305
- }
248
+ // ctx.fillStyle = '#000000'
249
+ // ctx.font = this.font
250
+ // ctx.fillText(this.text, x + 10, y - 10)
251
+ }
306
252
 
307
- for (let i = 0; i < this.#states.length; i++) {
308
- const key = this.#states[i]
309
- const file = this.#data.images[key]
253
+ async load () {
254
+ for (let i = 0; i < this.#keys.length; i++) {
255
+ const key = this.#keys[i]
256
+ const elem = this.#states[key]
257
+ const file = elem.image
310
258
  if (file !== undefined && file !== '') {
311
259
  try {
312
- this.#images[key] = await loadImage(file)
260
+ this.#states[key].imgBuffer = await loadImage(file)
313
261
  } catch (e) {
314
262
  console.error('No such image', file)
315
- this.#style = ButtonStyle.COLOR
316
- delete this.#images[key]
317
263
  return false
318
264
  }
319
- } else {
320
- // this is not an image type
321
265
  }
322
266
  }
323
- return true
324
267
  }
325
268
 
326
- async loadAsColor () {
327
- if (this.#states.length <= 0) {
328
- // use default colors
329
- this.#states = Object.keys(this.#colors)
330
- return true
331
- }
332
-
333
- for (let i = 0; i < this.#states.length; i++) {
334
- const key = this.#states[i]
335
- if (this.#data.colors && key in this.#data.colors) {
336
- const color = this.#data.colors[key]
337
- if (color !== undefined && color !== '') {
338
- this.#colors[key] = color
339
- }
340
- }
341
- }
342
- return true
343
- }
344
-
345
- async loadAsRGB () {
346
- if (this.#states.length <= 0) {
347
- // use default colors
348
- this.#states = Object.keys(this.#colors)
349
- return true
350
- }
351
-
352
- if (this.#data.rgb) {
353
- const keys = Object.keys(this.#data.rgb)
354
- for (let i = 0; i < keys.length; i++) {
355
- const key = this.#states[i]
356
- const rgb = this.#data.rgb[key]
357
- if (rgb !== undefined && rgb !== '') {
358
- this.#rgbs[key] = rgb
359
- }
360
- }
361
- }
362
-
363
- return true
364
- }
365
-
366
- getCurrentStateImage () {
367
- const key = this.#states[this.#index]
368
- return this.#images[key]
369
- }
370
-
371
- getCurrentStateFill (forceIndex = -1) {
372
- let key
373
- if (forceIndex >= 0) {
374
- key = this.#states[forceIndex]
375
- } else {
376
- key = this.#states[this.#index]
377
- }
378
- return this.#colors[key]
379
- }
380
-
381
- getCurrentRGB () {
382
- const key = this.#states[this.#index]
383
- const rgb = this.#rgbs[key]
384
-
385
- return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
269
+ getCurrentElement () {
270
+ const key = this.#keys[this.#index]
271
+ return this.#states[key]
386
272
  }
387
273
 
388
274
  getCurrentText () {
389
275
  return this.text
390
276
  }
391
277
 
392
- getCurrentCommand () {
393
- const key = this.#states[this.#index]
394
- const cmd = this.#commands[key]
395
-
396
- if (!cmd) { return '' }
397
- // Call an action
398
- const params = {
399
- id: this.id,
400
- state: key,
401
- min: this.#min,
402
- max: this.#max,
403
- value: this.#value,
404
- text: this.getCurrentText()
405
- }
406
- /* const keys = Object.keys(this.#data.config)
407
- keys.forEach(key => {
408
- params[key] = this.#data.config[key]
409
- }) */
410
-
411
- return format(cmd, params)
412
- }
413
-
414
278
  setIntState (val) {
415
279
  this.#index = val
416
280
  }
@@ -418,27 +282,21 @@ export class TouchButton {
418
282
  pressed () {
419
283
  this.timeStampPressed = Date.now()
420
284
 
421
- switch (this.#style) {
422
- case ButtonStyle.NONE:
423
- break
424
- default:
425
- this.#index++
426
- this.#index %= this.#states.length
427
- }
285
+ this.#index++
286
+ this.#index %= this.#keys.length
428
287
  return true
429
288
  }
430
289
 
431
290
  released () {
291
+ if (!this.getCurrentElement()) { return false }
432
292
  this.timeStampReleased = Date.now()
433
293
  this.timeHold = this.timeStampReleased - this.timeStampPressed
434
294
 
435
- if (this.#style === ButtonStyle.NONE) { return }
436
-
437
295
  if (this.timeHold < this.minPressed) {
438
296
  // Update the State according to the not correct pressed state
439
297
  console.log('Did not hold minimum time of ', this.minPressed, 'only', this.timeHold)
440
298
  this.#index--
441
- if (this.#index < 0) { this.#index = this.#states.length - 1 }
299
+ if (this.#index < 0) { this.#index = this.#keys.length - 1 }
442
300
  return false
443
301
  }
444
302
 
@@ -449,7 +307,7 @@ export class TouchButton {
449
307
  break
450
308
  default:
451
309
  this.#index--
452
- if (this.#index < 0) { this.#index = this.#states.length - 1 }
310
+ if (this.#index < 0) { this.#index = this.#keys.length - 1 }
453
311
 
454
312
  break
455
313
  }
@@ -458,16 +316,58 @@ export class TouchButton {
458
316
  }
459
317
 
460
318
  async rotated (delta) {
319
+ if (!this.getCurrentElement()) { return false }
320
+
461
321
  this.#value = calcDelta(this.#value, delta, this.#max)
462
322
  return this.runCommand()
463
323
  }
464
324
 
465
- runCommand () {
466
- const cmd = this.getCurrentCommand()
467
- if (cmd && cmd !== '') {
468
- sh(cmd)
469
- return true
325
+ async touchmove (x, y) {
326
+ // if (!this.getCurrentElement()) { return false }
327
+
328
+ if (x > this.#x) {
329
+ this.#moveRight = true
330
+ this.#moveLeft = false
331
+ } else if (x < this.#x) {
332
+ this.#moveRight = false
333
+ this.#moveLeft = true
470
334
  }
335
+
336
+ if (y > this.#y) {
337
+ this.#moveDown = true
338
+ this.#moveUp = false
339
+ } else if (y < this.#y) {
340
+ this.#moveDown = false
341
+ this.#moveUp = true
342
+ }
343
+
344
+ this.#x = x
345
+ this.#y = y
346
+ // console.log(`d: ${this.#moveDown} r: ${this.#moveRight} `)
471
347
  return false
472
348
  }
349
+
350
+ runCommand () {
351
+ const elem = this.getCurrentElement()
352
+ if (!elem || !elem.cmd) {
353
+ return
354
+ }
355
+ // Call an action
356
+ const params = {
357
+ id: this.id,
358
+ state: this.#keys[this.#index],
359
+ min: this.#min,
360
+ max: this.#max,
361
+ value: this.#value,
362
+ text: this.getCurrentText()
363
+ }
364
+ /* const keys = Object.keys(this.#data.config)
365
+ keys.forEach(key => {
366
+ params[key] = this.#data.config[key]
367
+ }) */
368
+
369
+ const cmdFormatted = format(elem.cmd, params)
370
+
371
+ return sh(cmdFormatted)
372
+ }
473
373
  }
@@ -1,5 +1,5 @@
1
1
  import pkg from 'loupedeck'
2
- import { BaseLoupeDeckHandler } from './common/BaseLoupeDeckHandler.mjs'
2
+ import { BaseLoupeDeckHandler } from '../common/BaseLoupeDeckHandler.mjs'
3
3
  const { HAPTIC } = pkg
4
4
 
5
5
  /**
package/icons/bulb.png ADDED
Binary file
Binary file
package/icons/home.png ADDED
Binary file
Binary file
Binary file
package/icons/user.png ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loupedeck-commander",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A system to ease working with LoupeDeck devices using CMD-line interfaces",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
package/profile-1.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "profile": "KVM",
2
+ "profile": "sample",
3
3
  "description": "",
4
4
  "config": {},
5
5
  "touch": {
@@ -7,153 +7,57 @@
7
7
  "right": {},
8
8
  "center": {
9
9
  "0": {
10
- "images": {
11
- "off": "icons/Comp_E_n.png",
12
- "on": "icons/Comp_E_p.png"
13
- },
14
- "commands": {
15
- "off": "echo \"{id} {state}\"",
16
- "on": "echo \"{id} {state}\""
10
+ "states": {
11
+ "off": {
12
+ "color": "#aaaaaa",
13
+ "image": "icons/home.png",
14
+ "cmd": "echo \"{id} {state}\""
15
+ },
16
+ "on": {
17
+ "color": "#11ff11",
18
+ "image": "icons/home.png",
19
+ "cmd": "echo \"{id} {state}\""
20
+ }
17
21
  },
18
22
  "group": "kvm1"
19
23
  },
20
24
  "1": {
21
- "images": {
22
- "off": "icons/Comp_D_n.png",
23
- "on": "icons/Comp_D_p.png"
24
- },
25
- "group": "kvm1"
26
- },
27
- "2": {
28
- "images": {
29
- "off": "icons/Comp_C_n.png",
30
- "on": "icons/Comp_C_p.png"
31
- },
32
- "group": "kvm1"
33
- },
34
- "3": {
35
- "images": {
36
- "off": "icons/Comp_B_n.png",
37
- "on": "icons/Comp_B_p.png"
38
- },
39
- "group": "kvm1"
40
- },
41
- "4": {
42
- "images": {
43
- "off": "icons/Comp_F_n.png",
44
- "on": "icons/Comp_F_p.png"
45
- },
46
- "group": "kvm1"
47
- },
48
- "5": {
49
- "minPressed": 1000,
50
- "type": "push",
51
- "colors": {
52
- "off": "#000000",
53
- "on": "#ff0000"
54
- }
55
- },
56
- "7": {
57
- "images": {
58
- "off": "icons/Comp_A_n.png",
59
- "on": "icons/Comp_A_p.png"
25
+ "states": {
26
+ "off": {
27
+ "color": "#aaaaaa",
28
+ "image": "icons/bulb.png",
29
+ "cmd": "echo \"{id} {state}\""
30
+ },
31
+ "on": {
32
+ "color": "#11ff11",
33
+ "image": "icons/bulb.png",
34
+ "cmd": "echo \"{id} {state}\""
35
+ }
60
36
  },
61
37
  "group": "kvm1"
62
38
  }
63
39
  },
64
- "knob": {
65
- "0": {
66
- "images": {
67
- "off": "icons/cia.png",
68
- "on": "icons/saph.png"
69
- },
70
- "commands": {
71
- "off": "echo \"CIA {state}\"",
72
- "on": "echo \"CIA {state}\""
73
- }
74
- }
75
- }
40
+ "knob": {}
76
41
  },
77
42
  "knobs": {
78
43
  "left": {},
79
44
  "right": {}
80
45
  },
81
46
  "buttons": {
82
- "knobTL": {
83
- "commands": {
84
- "off": "echo \"{id} {state} {value}\"",
85
- "on": "echo \"{id} {state} {value}\""
86
- }
87
- },
88
- "knobTR": {
89
- "commands": {
90
- "off": "echo \"{id} {state} {value}\"",
91
- "on": "echo \"{id} {state} {value}\""
92
- }
93
- },
94
- "knobCL": {
95
- "commands": {
96
- "off": "echo \"{id} {state} {value}\"",
97
- "on": "echo \"{id} {state} {value}\""
98
- }
99
- },
100
- "0": {
101
- "commands": {
102
- "off": "echo \"{id} {state}\"",
103
- "on": "echo \"{id} {state}\""
104
- },
105
- "rgb": {
106
- "off": [
107
- 0,
108
- 0,
109
- 0
110
- ],
111
- "on": [
112
- 0,
113
- 255,
114
- 0
115
- ],
116
- "intermediate": [
117
- 0,
118
- 255,
119
- 255
120
- ]
121
- }
122
- },
123
47
  "1": {
124
- "text": "PA",
125
- "type": "toggle",
126
- "commands": {
127
- "off": "echo \"{id} {state}\"",
128
- "on": "echo \"{id} {state}\"",
129
- "intermediate": "echo \"{id} {state}\""
48
+ "states": {
49
+ "off": {
50
+ "color": "#aaaaaa",
51
+ "image": "icons/bulb.png",
52
+ "cmd": "echo \"{id} {state}\""
53
+ },
54
+ "on": {
55
+ "color": "#11ff11",
56
+ "image": "icons/bulb.png",
57
+ "cmd": "echo \"{id} {state}\""
58
+ }
130
59
  },
131
- "rgb": {
132
- "off": [
133
- 0,
134
- 0,
135
- 0
136
- ],
137
- "on": [
138
- 0,
139
- 255,
140
- 0
141
- ],
142
- "intermediate": [
143
- 0,
144
- 255,
145
- 255
146
- ]
147
- }
148
- },
149
- "home": {
150
- "rgb": {
151
- "off": [
152
- 0,
153
- 0,
154
- 0
155
- ]
156
- }
60
+ "group": "kvm1"
157
61
  }
158
62
  }
159
63
  }
package/icons/A-G_n.png DELETED
Binary file
package/icons/A-G_p.png DELETED
Binary file
package/icons/A-H_n.png DELETED
Binary file
package/icons/A-H_p.png DELETED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/icons/Mic_n.png DELETED
Binary file
package/icons/Mic_p.png DELETED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/icons/cia.png DELETED
Binary file
package/icons/saph.png DELETED
Binary file
File without changes