diff --git a/lib/Data/Upgrades/v3tov4.js b/lib/Data/Upgrades/v3tov4.js index f4726c7245..5782e6d795 100644 --- a/lib/Data/Upgrades/v3tov4.js +++ b/lib/Data/Upgrades/v3tov4.js @@ -32,6 +32,12 @@ function addControlIdsToPages(db) { /** do the database upgrades to convert from the v3 to the v4 format */ function convertDatabaseToV4(db, logger) { addControlIdsToPages(db) + + // If xkeys was previously enabled, then preserve the old layout + const userconfig = db.getKey('userconfig', {}) + if (userconfig.xkeys_enable && userconfig.xkeys_legacy_layout === undefined) { + userconfig.xkeys_legacy_layout = true + } } function ParseBankControlId(controlId) { diff --git a/lib/Data/UserConfig.js b/lib/Data/UserConfig.js index 7afadf7553..a05b6eff37 100644 --- a/lib/Data/UserConfig.js +++ b/lib/Data/UserConfig.js @@ -39,6 +39,7 @@ class DataUserConfig extends CoreBase { remove_topbar: false, xkeys_enable: true, + xkeys_legacy_layout: false, elgato_plugin_enable: false, // Also disables local streamdeck usb_hotplug: true, loupedeck_enable: false, @@ -344,6 +345,10 @@ class DataUserConfig extends CoreBase { } this.data[key] = value + if (save) { + this.db.setKey('userconfig', this.data) + } + this.logger.info(`set '${key}' to: ${JSON.stringify(value)}`) this.io.emit('set_userconfig_key', key, value) setImmediate(() => { @@ -362,10 +367,6 @@ class DataUserConfig extends CoreBase { this.graphics.discardAllOutOfBoundsControls() } - - if (save) { - this.db.setKey('userconfig', this.data) - } } /** diff --git a/lib/Resources/Util.js b/lib/Resources/Util.js index cfac75293e..c93f9785c6 100644 --- a/lib/Resources/Util.js +++ b/lib/Resources/Util.js @@ -154,9 +154,9 @@ export function clamp(val, min, max) { } export function translateRotation(rotation) { - if (rotation === 90) return imageRs.RotationMode.CW270 - if (rotation === -90) return imageRs.RotationMode.CW90 - if (rotation === 180) return imageRs.RotationMode.CW180 + if (rotation === 90 || rotation === 'surface90') return imageRs.RotationMode.CW270 + if (rotation === -90 || rotation === 'surface-90') return imageRs.RotationMode.CW90 + if (rotation === 180 || rotation === 'surface180') return imageRs.RotationMode.CW180 return null } diff --git a/lib/Surface/Controller.js b/lib/Surface/Controller.js index b5218d56c6..8b1328d6cf 100644 --- a/lib/Surface/Controller.js +++ b/lib/Surface/Controller.js @@ -528,6 +528,9 @@ class SurfaceController extends CoreBase { // } else if (deviceInfo.vendorId === 1523 && deviceInfo.interface === 0) { if (this.userconfig.getKey('xkeys_enable')) { + deviceInfo.options = { + useLegacyLayout: !!this.userconfig.getKey('xkeys_legacy_layout'), + } await this.#addDevice(deviceInfo, 'xkeys', XKeysDriver) } } @@ -636,7 +639,7 @@ class SurfaceController extends CoreBase { } try { - const dev = await factory.create(deviceInfo.path) + const dev = await factory.create(deviceInfo.path, deviceInfo.options) this.#createSurfaceHandler(deviceInfo.path, type, dev) setImmediate(() => { diff --git a/lib/Surface/Handler.js b/lib/Surface/Handler.js index 208eb9bccb..25717d1812 100644 --- a/lib/Surface/Handler.js +++ b/lib/Surface/Handler.js @@ -18,7 +18,8 @@ import CoreBase from '../Core/Base.js' import { oldBankIndexToXY } from '../Shared/ControlId.js' import { cloneDeep } from 'lodash-es' -import { LEGACY_BUTTONS_PER_COLUMN, LEGACY_BUTTONS_PER_ROW, LEGACY_MAX_BUTTONS } from '../Util/Constants.js' +import { LEGACY_MAX_BUTTONS } from '../Util/Constants.js' +import { rotateXYForPanel, unrotateXYForPanel } from './Util.js' const PINCODE_NUMBER_POSITIONS = [ // 0 @@ -40,19 +41,19 @@ const PINCODE_CODE_POSITION = [0, 1] const PINCODE_NUMBER_POSITIONS_SKIP_FIRST_COL = [ // 0 - [4, 1], + [5, 1], // 1 2 3 - [1, 2], [2, 2], [3, 2], + [4, 2], // 4 5 6 - [1, 1], [2, 1], [3, 1], + [4, 1], // 7 8 9 - [1, 0], [2, 0], [3, 0], + [4, 0], ] class SurfaceHandler extends CoreBase { @@ -92,6 +93,20 @@ class SurfaceHandler extends CoreBase { */ #xkeysPageCount = 0 + get panelGridSize() { + const rotation = this.panelconfig.config.rotation + if (rotation === 'surface90' || rotation === 'surface-90') { + const rawGridSize = this.panel.gridSize + + return { + rows: rawGridSize.columns, + columns: rawGridSize.rows, + } + } else { + return this.panel.gridSize + } + } + constructor(registry, integrationType, panel, isLocked) { super(registry, `device(${panel.info.deviceId})`, `Surface/Handler/${panel.info.deviceId}`) this.logger.silly('loading for ' + panel.info.devicepath) @@ -113,7 +128,7 @@ class SurfaceHandler extends CoreBase { } if (this.panel.info.type === 'Loupedeck CT') { this.pincodeNumberPositions = PINCODE_NUMBER_POSITIONS_SKIP_FIRST_COL - this.pincodeCodePosition = [2, 4] + this.pincodeCodePosition = [3, 4] } this.currentPage = 1 // The current page of the device @@ -249,28 +264,10 @@ class SurfaceHandler extends CoreBase { }) } else if (this.#xkeysPageCount > 0) { this.#xkeysDrawPages() - } else if (this.panel.info.type === 'Loupedeck CT') { - const gridSize = this.panel.gridSize - - for (let y = 0; y < gridSize.rows; y += 1) { - let pageNumber = this.currentPage - if (y >= gridSize.rows) pageNumber += 1 - if (pageNumber > 99) pageNumber = 1 - - for (let x = 0; x < gridSize.columns; x += 1) { - const image = this.graphics.getBank({ - pageNumber, - column: x, - row: y % 4, - }) - - this.panel.draw(x, y, image) - } - } } else { const { xOffset, yOffset } = this.#getCurrentOffset() - const gridSize = this.panel.gridSize + const gridSize = this.panelGridSize for (let y = 0; y < gridSize.rows; y++) { for (let x = 0; x < gridSize.columns; x++) { @@ -280,13 +277,19 @@ class SurfaceHandler extends CoreBase { row: y + yOffset, }) - this.panel.draw(x, y, image) + this.#drawButtonTransformed(x, y, image) } } } } } + #drawButtonTransformed(x, y, image) { + const [transformedX, transformedY] = rotateXYForPanel(x, y, this.panelGridSize, this.panelconfig.config.rotation) + + this.panel.draw(transformedX, transformedY, image) + } + getPanelConfig() { return this.panelconfig.config } @@ -315,20 +318,20 @@ class SurfaceHandler extends CoreBase { // xkeys mode const pageOffset = location.pageNumber - this.currentPage if (pageOffset >= 0 && pageOffset < this.#xkeysPageCount) { - this.panel.drawColor(pageOffset, location.column, location.row, render.style?.bgcolor || 0) + const [transformedX, transformedY] = rotateXYForPanel( + location.column, + location.row, + this.panelGridSize, + this.panelconfig.config.rotation + ) + + this.panel.drawColor(pageOffset, transformedX, transformedY, render.style?.bgcolor || 0) } - } else if ( - this.panel.info.type === 'Loupedeck CT' && - (location.pageNumber - this.currentPage == 1 || (location.pageNumber == 1 && this.currentPage == 99)) && - location.row < 3 // lower half of CT has only 3 rows, zero based - ) { - // Loupdeck CT lower half, draw button with row offset by 4 - this.panel.draw(location.column, location.row + 4, render) } else if (location.pageNumber == this.currentPage) { // normal mode const { xOffset, yOffset } = this.#getCurrentOffset() - this.panel.draw(location.column - xOffset, location.row - yOffset, render) + this.#drawButtonTransformed(location.column - xOffset, location.row - yOffset, render) } } @@ -363,10 +366,12 @@ class SurfaceHandler extends CoreBase { if (!this.isSurfaceLocked) { this.emit('interaction') + const [x2, y2] = unrotateXYForPanel(x, y, this.panelGridSize, this.panelconfig.config.rotation) + // Translate key for offset const { xOffset, yOffset } = this.#getCurrentOffset() - const coordinate = `${x + xOffset}/${y + yOffset}` + const coordinate = `${y2 + yOffset}/${x2 + xOffset}` let thisPage = this.currentPage @@ -379,19 +384,19 @@ class SurfaceHandler extends CoreBase { delete this.#currentButtonPresses[coordinate] } - // allow the xkeys and loupedeck CT to span pages + // allow the xkeys (legacy mode) to span pages thisPage += pageOffset ?? 0 // loop at page 99 if (thisPage > 99) thisPage = 1 const controlId = this.page.getControlIdAt({ pageNumber: thisPage, - column: x + xOffset, - row: y + yOffset, + column: x2 + xOffset, + row: y2 + yOffset, }) this.controls.pressControl(controlId, pressed, this.deviceId) - this.logger.debug(`Button ${thisPage}/${x + xOffset}/${y + yOffset} ${pressed ? 'pressed' : 'released'}`) + this.logger.debug(`Button ${thisPage}/${coordinate} ${pressed ? 'pressed' : 'released'}`) } else { if (pressed) { const pressCode = this.pincodeNumberPositions.findIndex((pos) => pos[0] == x && pos[1] == y) @@ -425,24 +430,26 @@ class SurfaceHandler extends CoreBase { if (!this.isSurfaceLocked) { this.emit('interaction') + const [x2, y2] = unrotateXYForPanel(x, y, this.panelGridSize, this.panelconfig.config.rotation) + // Translate key for offset const { xOffset, yOffset } = this.#getCurrentOffset() let thisPage = this.currentPage - // allow the xkeys and loupedeck CT to span pages + // allow the xkeys (legacy mode) to span pages thisPage += pageOffset ?? 0 // loop at page 99 if (thisPage > 99) thisPage = 1 const controlId = this.page.getControlIdAt({ pageNumber: thisPage, - column: x + xOffset, - row: y + yOffset, + column: x2 + xOffset, + row: y2 + yOffset, }) this.controls.rotateControl(controlId, direction, this.deviceId) - this.logger.debug(`Rotary ${thisPage}/${x + xOffset}/${y + yOffset} rotated ${direction ? 'right' : 'left'}`) + this.logger.debug(`Rotary ${thisPage}/${x2 + xOffset}/${y2 + yOffset} rotated ${direction ? 'right' : 'left'}`) } else { // Ignore when locked out } @@ -466,7 +473,13 @@ class SurfaceHandler extends CoreBase { row: xy[1], }) - this.panel.drawColor(page, ...xy, render.style?.bgcolor || 0) + const [transformedX, transformedY] = rotateXYForPanel( + ...xy, + this.panelGridSize, + this.panelconfig.config.rotation + ) + + this.panel.drawColor(page, transformedX, transformedY, render.style?.bgcolor || 0) } } } diff --git a/lib/Surface/IP/ElgatoPlugin.js b/lib/Surface/IP/ElgatoPlugin.js index 47450abe00..ac01000c72 100644 --- a/lib/Surface/IP/ElgatoPlugin.js +++ b/lib/Surface/IP/ElgatoPlugin.js @@ -40,7 +40,7 @@ class SurfaceIPElgatoPlugin extends EventEmitter { this.info = { type: 'Elgato Streamdeck Plugin', devicepath: devicepath, - configFields: ['rotation'], + configFields: ['legacy_rotation'], deviceId: 'plugin', } diff --git a/lib/Surface/IP/Satellite.js b/lib/Surface/IP/Satellite.js index afa8ee9d03..33398abac9 100644 --- a/lib/Surface/IP/Satellite.js +++ b/lib/Surface/IP/Satellite.js @@ -54,7 +54,7 @@ class SurfaceIPSatellite extends EventEmitter { this.logger.info(`Adding Satellite device "${this.deviceId}"`) if (this.#streamBitmapSize) { - this.info.configFields.push('rotation') + this.info.configFields.push('legacy_rotation') } this._config = { diff --git a/lib/Surface/USB/ElgatoStreamDeck.js b/lib/Surface/USB/ElgatoStreamDeck.js index f3552fc9b0..47ca89e59c 100644 --- a/lib/Surface/USB/ElgatoStreamDeck.js +++ b/lib/Surface/USB/ElgatoStreamDeck.js @@ -22,7 +22,7 @@ import imageRs from '@julusian/image-rs' import LogController from '../../Log/Controller.js' import ImageWriteQueue from '../../Resources/ImageWriteQueue.js' import { translateRotation } from '../../Resources/Util.js' -import { convertXYToIndexForPanel, convertPanelIndexToXY } from '../Util.js' +import { convertXYToIndexForPanel, convertPanelIndexToXY, rotateXYForPanel } from '../Util.js' const setTimeoutPromise = util.promisify(setTimeout) class SurfaceUSBElgatoStreamDeck extends EventEmitter { @@ -43,7 +43,7 @@ class SurfaceUSBElgatoStreamDeck extends EventEmitter { this.info = { type: `Elgato ${this.streamDeck.PRODUCT_NAME}`, devicepath: devicepath, - configFields: ['brightness', 'rotation'], + configFields: ['brightness', 'legacy_rotation'], deviceId: undefined, // set in #init() } diff --git a/lib/Surface/USB/Infinitton.js b/lib/Surface/USB/Infinitton.js index f589608c4a..c99fae81e5 100644 --- a/lib/Surface/USB/Infinitton.js +++ b/lib/Surface/USB/Infinitton.js @@ -37,7 +37,7 @@ class SurfaceUSBInfinitton { this.info = { type: 'Infinitton iDisplay device', devicepath: devicepath, - configFields: ['brightness', 'rotation'], + configFields: ['brightness', 'legacy_rotation'], deviceId: `infinitton:${serialnumber}`, } diff --git a/lib/Surface/USB/LoupedeckCt.js b/lib/Surface/USB/LoupedeckCt.js index 86323b89e6..92fc4c7662 100644 --- a/lib/Surface/USB/LoupedeckCt.js +++ b/lib/Surface/USB/LoupedeckCt.js @@ -18,6 +18,7 @@ import { EventEmitter } from 'events' import { LoupedeckBufferFormat, LoupedeckDisplayId, openLoupedeck } from '@loupedeck/node' import { convertPanelIndexToXY } from '../Util.js' +import { translateRotation } from '../../Resources/Util.js' import ImageWriteQueue from '../../Resources/ImageWriteQueue.js' import imageRs from '@julusian/image-rs' import LogController from '../../Log/Controller.js' @@ -100,8 +101,7 @@ class SurfaceUSBLoupedeckCt extends EventEmitter { const xy = rotaryToXY(this.modelInfo, info) if (!xy) return - const pageOffset = Math.floor(xy[1] / 4) - this.emit('rotate', xy[0], xy[1] % 4, delta == 1, pageOffset) + this.emit('rotate', xy[0], xy[1], delta == 1) }) this.loupedeck.on('touchstart', (data) => { @@ -211,8 +211,8 @@ class SurfaceUSBLoupedeckCt extends EventEmitter { height ) - // const rotation = translateRotation(this.config.rotation) - // if (rotation !== null) image = image.rotate(rotation) + const rotation = translateRotation(this.config.rotation) + if (rotation !== null) image = image.rotate(rotation) newbuffer = Buffer.from(await image.toBuffer(imageRs.PixelFormat.Rgb)) } catch (e) { @@ -246,10 +246,9 @@ class SurfaceUSBLoupedeckCt extends EventEmitter { if (!xy) return const x = xy[0] - const y = xy[1] % 4 - const pageOffset = Math.floor(xy[1] / 4) + const y = xy[1] - this.emit('click', x, y, state, pageOffset) + this.emit('click', x, y, state) } async #init() { @@ -365,7 +364,10 @@ class SurfaceUSBLoupedeckCt extends EventEmitter { const buttonIndex = this.modelInfo.buttons.findIndex((btn) => btn[0] == x && btn[1] == y) if (buttonIndex >= 0) { - const color = render.style ? colorToRgb(render.style.bgcolor) : { r: 0, g: 0, b: 0 } + let color = { r: 0, g: 0, b: 0 } + if (render.style === 'pageup') color = { r: 255, g: 255, b: 255 } + else if (render.style === 'pagedown') color = { r: 0, g: 0, b: 255 } + else if (render.style) color = colorToRgb(render.style.bgcolor) this.loupedeck .setButtonColor({ diff --git a/lib/Surface/USB/LoupedeckLive.js b/lib/Surface/USB/LoupedeckLive.js index 1283e8a8bc..0b17e6fa77 100644 --- a/lib/Surface/USB/LoupedeckLive.js +++ b/lib/Surface/USB/LoupedeckLive.js @@ -21,6 +21,7 @@ import ImageWriteQueue from '../../Resources/ImageWriteQueue.js' import imageRs from '@julusian/image-rs' import LogController from '../../Log/Controller.js' import { convertPanelIndexToXY } from '../Util.js' +import { translateRotation } from '../../Resources/Util.js' const loupedeckLiveInfo = { totalCols: 8, @@ -203,8 +204,6 @@ class SurfaceUSBLoupedeckLive extends EventEmitter { const width = this.loupedeck.lcdKeySize const height = this.loupedeck.lcdKeySize - // const rotation = translateRotation(this.config.rotation) - let newbuffer try { let imagesize = Math.sqrt(buffer.length / 4) // TODO: assuming here that the image is square @@ -213,8 +212,8 @@ class SurfaceUSBLoupedeckLive extends EventEmitter { height ) - // const rotation = translateRotation(this.config.rotation) - // if (rotation !== null) image = image.rotate(rotation) + const rotation = translateRotation(this.config.rotation) + if (rotation !== null) image = image.rotate(rotation) newbuffer = Buffer.from(await image.toBuffer(imageRs.PixelFormat.Rgb)) } catch (e) { diff --git a/lib/Surface/USB/XKeys.js b/lib/Surface/USB/XKeys.js index 4ec5d79092..ce9d12ebde 100644 --- a/lib/Surface/USB/XKeys.js +++ b/lib/Surface/USB/XKeys.js @@ -1,3 +1,5 @@ +// @ts-check + /* * This file is part of the Companion project * Copyright (c) 2022 VICREO BV @@ -27,12 +29,13 @@ import { LEGACY_BUTTONS_PER_COLUMN, LEGACY_BUTTONS_PER_ROW, LEGACY_MAX_BUTTONS } * @memberof xkeys */ class SurfaceUSBXKeys extends EventEmitter { - constructor(devicepath, panel, deviceId) { + constructor(devicepath, panel, deviceId, options) { super() this.logger = LogController.createLogger(`Surface/USB/XKeys/${devicepath}`) this.myXkeysPanel = panel + this.useLegacyLayout = !!options.useLegacyLayout this.mapDeviceToCompanion = [] this.mapCompanionToDevice = [] @@ -46,11 +49,6 @@ class SurfaceUSBXKeys extends EventEmitter { deviceId: deviceId, } - this.gridSize = { - columns: LEGACY_BUTTONS_PER_ROW, - rows: LEGACY_BUTTONS_PER_COLUMN, - } - this.config = { brightness: 60, illuminate_pressed: true, @@ -58,16 +56,28 @@ class SurfaceUSBXKeys extends EventEmitter { const { colCount, rowCount } = this.myXkeysPanel.info - // Mapping buttons - for (var leftRight = 0; leftRight < colCount; leftRight++) { - for (var topBottom = 0; topBottom < rowCount; topBottom++) { - this.mapDeviceToCompanion.push(leftRight + topBottom * colCount) + if (this.useLegacyLayout) { + this.gridSize = { + columns: LEGACY_BUTTONS_PER_ROW, + rows: LEGACY_BUTTONS_PER_COLUMN, } - } - // Mapping for feedback - for (let topBottom = 1; topBottom <= rowCount; topBottom++) { - for (let leftRight = 0; leftRight < colCount; leftRight++) { - this.mapCompanionToDevice.push(topBottom + leftRight * rowCount) + + // Mapping buttons + for (var leftRight = 0; leftRight < colCount; leftRight++) { + for (var topBottom = 0; topBottom < rowCount; topBottom++) { + this.mapDeviceToCompanion.push(leftRight + topBottom * colCount) + } + } + // Mapping for feedback + for (let topBottom = 1; topBottom <= rowCount; topBottom++) { + for (let leftRight = 0; leftRight < colCount; leftRight++) { + this.mapCompanionToDevice.push(topBottom + leftRight * rowCount) + } + } + } else { + this.gridSize = { + columns: colCount, + rows: rowCount, } } @@ -90,18 +100,15 @@ class SurfaceUSBXKeys extends EventEmitter { // Listen to pressed buttons: this.myXkeysPanel.on('down', (keyIndex, metadata) => { - const key = this.mapDeviceToCompanion[keyIndex - 1] - if (key === undefined) { - return - } + const location = this.#translateIndexToXY(keyIndex) + if (!location) return - this.logger.debug(`keyIndex: ${keyIndex}, companion button: ${key}`) - this.pressed.add(keyIndex) + const [x, y, pageOffset] = location - const pageOffset = Math.floor(key / LEGACY_MAX_BUTTONS) - const localKey = key % LEGACY_MAX_BUTTONS + this.logger.debug(`keyIndex: ${keyIndex}, companion button: ${y}/${x}`) + this.pressed.add(keyIndex) - this.#emitClick(localKey, true, pageOffset) + this.emit('click', x, y, true, pageOffset) // Light up a button when pressed: try { @@ -116,18 +123,15 @@ class SurfaceUSBXKeys extends EventEmitter { // Listen to released buttons: this.myXkeysPanel.on('up', (keyIndex, metadata) => { - const key = this.mapDeviceToCompanion[keyIndex - 1] - if (key === undefined) { - return - } + const location = this.#translateIndexToXY(keyIndex) + if (!location) return - this.logger.debug(`keyIndex: ${keyIndex}, companion button: ${key}`) - this.pressed.delete(keyIndex) + const [x, y, pageOffset] = location - const pageOffset = Math.floor(key / LEGACY_MAX_BUTTONS) - const localKey = key % LEGACY_MAX_BUTTONS + this.logger.debug(`keyIndex: ${keyIndex}, companion button: ${y}/${x}`) + this.pressed.delete(keyIndex) - this.#emitClick(localKey, false, pageOffset) + this.emit('click', x, y, false, pageOffset) // Turn off button light when released: try { @@ -166,31 +170,50 @@ class SurfaceUSBXKeys extends EventEmitter { }) } - #emitClick(key, state, pageOffset) { - const xy = convertPanelIndexToXY(key, this.gridSize) - if (xy) { - this.emit('click', ...xy, state, pageOffset) + #translateIndexToXY(keyIndex) { + if (this.useLegacyLayout) { + const key = this.mapDeviceToCompanion[keyIndex - 1] + if (key === undefined) { + return + } + + const pageOffset = Math.floor(key / LEGACY_MAX_BUTTONS) + const localKey = key % LEGACY_MAX_BUTTONS + + const xy = convertPanelIndexToXY(localKey, this.gridSize) + if (xy) { + return [...xy, pageOffset] + } + } else { + const gridSize = this.gridSize + keyIndex -= 1 + if (isNaN(keyIndex) || keyIndex < 0 || keyIndex >= gridSize.columns * gridSize.rows) return undefined + const x = Math.floor(keyIndex / gridSize.rows) + const y = keyIndex % gridSize.rows + return [x, y, undefined] } } async #init() { this.logger.debug(`Xkeys ${this.myXkeysPanel.info.name} detected`) - setTimeout(() => { - const { colCount, rowCount } = this.myXkeysPanel.info - // Ask companion to provide colours for enough pages of buttons - this.emit('xkeys-subscribePage', Math.ceil((colCount * rowCount) / LEGACY_MAX_BUTTONS)) - }, 1000) + if (this.useLegacyLayout) { + setTimeout(() => { + const { colCount, rowCount } = this.myXkeysPanel.info + // Ask companion to provide colours for enough pages of buttons + this.emit('xkeys-subscribePage', Math.ceil((colCount * rowCount) / LEGACY_MAX_BUTTONS)) + }, 1000) + } } - static async create(devicepath) { + static async create(devicepath, options) { const panel = await setupXkeysPanel(devicepath) try { const deviceId = `xkeys:${panel.info.productId}-${panel.info.unitId}` // TODO - this needs some additional uniqueness to the sufix // (${devicepath.slice(0, -1).slice(-10)})` // This suffix produces `dev/hidraw` on linux, which is not useful. - const self = new SurfaceUSBXKeys(devicepath, panel, deviceId) + const self = new SurfaceUSBXKeys(devicepath, panel, deviceId, options || {}) await self.#init() @@ -204,7 +227,7 @@ class SurfaceUSBXKeys extends EventEmitter { /** * Process the information from the GUI and what is saved in database - * @param {companion config} config + * @param {object} config * @returns false when nothing happens */ setConfig(config) { @@ -238,11 +261,35 @@ class SurfaceUSBXKeys extends EventEmitter { } } - draw() { - // Should never be fired + draw(x, y, render) { + // Should never be used for legacy layout + if (this.useLegacyLayout) return + + const gridSize = this.gridSize + if (x < 0 || y < 0 || x >= gridSize.columns || y >= gridSize.rows) return + + const buttonIndex = x * gridSize.rows + y + 1 + const color = render?.style?.bgcolor ?? 0 + this.#drawColorAtIndex(buttonIndex, color) } drawColor(page, x, y, color) { + if (!this.useLegacyLayout) return + + const key = convertXYToIndexForPanel(x, y, this.gridSize) + if (key === undefined) return + + const buttonNumber = page * LEGACY_MAX_BUTTONS + key + 1 + if (buttonNumber <= this.mapCompanionToDevice.length) { + const buttonIndex = this.mapCompanionToDevice[buttonNumber - 1] + + this.#drawColorAtIndex(buttonIndex, color) + } + } + + #drawColorAtIndex(buttonIndex, color) { + if (buttonIndex === undefined) return + // Feedback const color2 = { r: (color >> 16) & 0xff, @@ -250,25 +297,16 @@ class SurfaceUSBXKeys extends EventEmitter { b: color & 0xff, } - const key = convertXYToIndexForPanel(x, y, this.gridSize) - if (!key) return - - const buttonNumber = page * LEGACY_MAX_BUTTONS + key - if (buttonNumber <= this.mapCompanionToDevice.length) { - const buttonIndex = this.mapCompanionToDevice[buttonNumber - 1] - if (buttonIndex !== undefined) { - const tmpColor = { ...color2 } - if (this.pressed.has(buttonIndex) && this.config.illuminate_pressed) tmpColor.r = 255 - - try { - this.myXkeysPanel.setBacklight(buttonIndex, tmpColor) - } catch (e) { - this.logger.debug(`Failed to set backlight: ${e}`) - } + const tmpColor = { ...color2 } + if (this.pressed.has(buttonIndex) && this.config.illuminate_pressed) tmpColor.r = 255 - this.lastColors[buttonIndex] = color2 - } + try { + this.myXkeysPanel.setBacklight(buttonIndex, tmpColor) + } catch (e) { + this.logger.debug(`Failed to set backlight: ${e}`) } + + this.lastColors[buttonIndex] = color2 } } diff --git a/lib/Surface/Util.js b/lib/Surface/Util.js index 67875d7b2d..333bf2e622 100644 --- a/lib/Surface/Util.js +++ b/lib/Surface/Util.js @@ -12,3 +12,29 @@ export function convertPanelIndexToXY(index, gridSize) { const y = Math.floor(index / gridSize.columns) return [x, y] } + +export function rotateXYForPanel(x, y, gridSize, rotation) { + switch (rotation) { + case 'surface90': + return [y, gridSize.columns - x - 1] + case 'surface-90': + return [gridSize.rows - y - 1, x] + case 'surface180': + return [gridSize.columns - x - 1, gridSize.rows - y - 1] + default: + return [x, y] + } +} + +export function unrotateXYForPanel(x, y, gridSize, rotation) { + switch (rotation) { + case 'surface90': + return [gridSize.columns - y - 1, x] + case 'surface-90': + return [y, gridSize.rows - x - 1] + case 'surface180': + return [gridSize.columns - x - 1, gridSize.rows - y - 1] + default: + return [x, y] + } +} diff --git a/webui/src/Surfaces/EditModal.jsx b/webui/src/Surfaces/EditModal.jsx index 416af496c5..fd17b40cb1 100644 --- a/webui/src/Surfaces/EditModal.jsx +++ b/webui/src/Surfaces/EditModal.jsx @@ -211,21 +211,31 @@ export const SurfaceEditModal = forwardRef(function SurfaceEditModal(_props, ref /> )} - {deviceInfo.configFields?.includes('rotation') && ( - - Button rotation - updateConfig('rotation', parseInt(e.currentTarget.value))} - > - - - - - - - )} + + + Button rotation + { + const valueNumber = parseInt(e.currentTarget.value) + updateConfig('rotation', isNaN(valueNumber) ? e.currentTarget.value : valueNumber) + }} + > + + + + + + {deviceInfo.configFields?.includes('legacy_rotation') && ( + <> + + + + + )} + + {deviceInfo.configFields?.includes('emulator_control_enable') && ( Enable support for Logitech R400/Mastercue/DSan diff --git a/webui/src/UserConfig/SurfacesConfig.jsx b/webui/src/UserConfig/SurfacesConfig.jsx index b98012c507..82c038c0d1 100644 --- a/webui/src/UserConfig/SurfacesConfig.jsx +++ b/webui/src/UserConfig/SurfacesConfig.jsx @@ -76,6 +76,28 @@ export function SurfacesConfig({ config, setValue, resetValue }) { + + + Use old layout for X-keys +
+ (Requires Companion restart) + + +
+ setValue('xkeys_legacy_layout', e.currentTarget.checked)} + /> +
+ + + resetValue('xkeys_legacy_layout')} title="Reset to default"> + + + + Enable connected Loupedeck and Razer Stream Controller devices