Skip to content

Commit

Permalink
feat: support razer stream controller x
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Apr 18, 2023
1 parent 99de79e commit 71cb009
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 5 deletions.
152 changes: 152 additions & 0 deletions src/device-types/razer-stream-controller-x.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { LoupedeckDevice, LoupedeckDisplayId, LoupedeckBufferFormat, LoupedeckModelId } from '@loupedeck/node'
import sharp = require('sharp')
import { CompanionSatelliteClient } from '../client'
import { CardGenerator } from '../cards'
import { ImageWriteQueue } from '../writeQueue'
import { DeviceDrawProps, DeviceRegisterProps, WrappedDevice } from './api'

export class RazerStreamControllerXWrapper implements WrappedDevice {
readonly #cardGenerator: CardGenerator
readonly #deck: LoupedeckDevice
readonly #deviceId: string

#queueOutputId: number
#isShowingCard = true
#queue: ImageWriteQueue

public get deviceId(): string {
return this.#deviceId
}
public get productName(): string {
return this.#deck.modelName
}

public constructor(deviceId: string, device: LoupedeckDevice, cardGenerator: CardGenerator) {
this.#deck = device
this.#deviceId = deviceId
this.#cardGenerator = cardGenerator

if (device.modelId !== LoupedeckModelId.RazerStreamControllerX)
throw new Error('Incorrect model passed to wrapper!')

this.#queueOutputId = 0

this.#queue = new ImageWriteQueue(async (key: number, buffer: Buffer) => {
if (key > 40) {
return
}

const outputId = this.#queueOutputId

const width = this.#deck.lcdKeySize
const height = this.#deck.lcdKeySize

let newbuffer: Buffer
try {
newbuffer = await sharp(buffer, { raw: { width: 72, height: 72, channels: 3 } })
.resize(width, height)
.raw()
.toBuffer()
} catch (e) {
console.log(`device(${deviceId}): scale image failed: ${e}`)
return
}

// Check if generated image is still valid
if (this.#queueOutputId === outputId) {
try {
if (this.#isShowingCard) {
this.#isShowingCard = false

// Do a blank of the whole panel before drawing a button, so that there isnt any bleed
await this.blankDevice(true)
}

await this.#deck.drawKeyBuffer(key, newbuffer, LoupedeckBufferFormat.RGB)
} catch (e_1) {
console.error(`device(${deviceId}): fillImage failed: ${e_1}`)
}
}
})
}

getRegisterProps(): DeviceRegisterProps {
return {
keysTotal: 15,
keysPerRow: 5,
bitmaps: true,
colours: true,
text: false,
}
}

async close(): Promise<void> {
this.#queue?.abort()
this.#deck.close()
}
async initDevice(client: CompanionSatelliteClient, status: string): Promise<void> {
const convertButtonId = (type: 'button' | 'rotary', id: number): number => {
if (type === 'button') {
return id
}

// Discard
return 99
}
console.log('Registering key events for ' + this.deviceId)
this.#deck.on('down', (info) => client.keyDown(this.deviceId, convertButtonId(info.type, info.index)))
this.#deck.on('up', (info) => client.keyUp(this.deviceId, convertButtonId(info.type, info.index)))

// Start with blanking it
await this.blankDevice()

await this.showStatus(client.host, status)
}

async deviceAdded(): Promise<void> {
this.#queueOutputId++
}
async setBrightness(percent: number): Promise<void> {
this.#deck.setBrightness(percent / 100)
}
async blankDevice(skipButtons?: boolean): Promise<void> {
await this.#deck.blankDevice(true, !skipButtons)
}
async draw(d: DeviceDrawProps): Promise<void> {
if (d.image) {
this.#queue.queue(d.keyIndex, d.image)
} else {
throw new Error(`Cannot draw for Loupedeck without image`)
}
}
async showStatus(hostname: string, status: string): Promise<void> {
const width = this.#deck.displayMain.width
const height = this.#deck.displayMain.height

// abort and discard current operations
this.#queue?.abort()
this.#queueOutputId++
const outputId = this.#queueOutputId
this.#cardGenerator
.generateBasicCard(width, height, hostname, status)
.then(async (buffer) => {
if (outputId === this.#queueOutputId) {
console.log('draw buffer')
this.#isShowingCard = true
// still valid
await this.#deck.drawBuffer(
LoupedeckDisplayId.Center,
buffer,
LoupedeckBufferFormat.RGB,
width,
height,
0,
0
)
}
})
.catch((e) => {
console.error(`Failed to fill device`, e)
})
}
}
28 changes: 23 additions & 5 deletions src/devices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { CompanionSatelliteClient } from './client'
import { getStreamDeckDeviceInfo, openStreamDeck, StreamDeck } from '@elgato-stream-deck/node'
import { usb } from 'usb'
import { CardGenerator } from './cards'
import { XencelabsQuickKeysManagerInstance, XencelabsQuickKeys } from '@xencelabs-quick-keys/node'
import {
XencelabsQuickKeysManagerInstance,
XencelabsQuickKeys,
VENDOR_ID as VendorIdXencelabs,
} from '@xencelabs-quick-keys/node'
import { DeviceId, WrappedDevice } from './device-types/api'
import { StreamDeckWrapper } from './device-types/streamdeck'
import { QuickKeysWrapper } from './device-types/xencelabs-quick-keys'
Expand All @@ -11,7 +15,16 @@ import { InfinittonWrapper } from './device-types/infinitton'
import { LoupedeckLiveWrapper } from './device-types/loupedeck-live'
import { LoupedeckLiveSWrapper } from './device-types/loupedeck-live-s'
import * as HID from 'node-hid'
import { openLoupedeck, listLoupedecks, LoupedeckDevice, LoupedeckModelId } from '@loupedeck/node'
import {
openLoupedeck,
listLoupedecks,
LoupedeckDevice,
LoupedeckModelId,
VendorIdLoupedeck,
VendorIdRazer,
} from '@loupedeck/node'
import { RazerStreamControllerXWrapper } from './device-types/razer-stream-controller-x'
import { VENDOR_ID as VendorIdElgato } from '@elgato-stream-deck/core'

// Force into hidraw mode
HID.setDriverType('hidraw')
Expand All @@ -30,18 +43,21 @@ export class DeviceManager {
this.cardGenerator = new CardGenerator()

usb.on('attach', (dev) => {
if (dev.deviceDescriptor.idVendor === 0x0fd9) {
if (dev.deviceDescriptor.idVendor === VendorIdElgato) {
this.foundDevice(dev)
} else if (
dev.deviceDescriptor.idVendor === 0xffff &&
(dev.deviceDescriptor.idProduct === 0x1f40 || dev.deviceDescriptor.idProduct === 0x1f41)
) {
this.foundDevice(dev)
} else if (dev.deviceDescriptor.idVendor === 0x28bd) {
} else if (dev.deviceDescriptor.idVendor === VendorIdXencelabs) {
XencelabsQuickKeysManagerInstance.scanDevices().catch((e) => {
console.error(`Quickey scan failed: ${e}`)
})
} else if (dev.deviceDescriptor.idVendor === 0x2ec2) {
} else if (
dev.deviceDescriptor.idVendor === VendorIdLoupedeck ||
dev.deviceDescriptor.idVendor === VendorIdRazer
) {
this.foundDevice(dev)
}
})
Expand Down Expand Up @@ -237,6 +253,8 @@ export class DeviceManager {
this.tryAddLoupedeck(dev.path, dev.serialNumber, LoupedeckLiveWrapper)
} else if (dev.serialNumber && dev.model === LoupedeckModelId.LoupedeckLiveS) {
this.tryAddLoupedeck(dev.path, dev.serialNumber, LoupedeckLiveSWrapper)
} else if (dev.serialNumber && dev.model === LoupedeckModelId.RazerStreamControllerX) {
this.tryAddLoupedeck(dev.path, dev.serialNumber, RazerStreamControllerXWrapper)
}
}
})
Expand Down

0 comments on commit 71cb009

Please sign in to comment.