Skip to content

Commit

Permalink
fix: expose Xkeys.filterDevice() static method, used to filter for co…
Browse files Browse the repository at this point in the history
…mpatible X-keys devices when manually handling HID devices
  • Loading branch information
nytamin committed Dec 6, 2023
1 parent 190d4a1 commit ab542a8
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 32 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ If you're upgrading from `<2.0.0`, please read the [_Migrations_](#Migrations) s

## Getting started - Node.js

_[See examples folder for more examples.](./packages/node/examples/)_

### Watch for connected X-keys (recommended)

This is the recommended way to use this library, to automatically be connected or reconnected to the panel.
Expand Down
41 changes: 23 additions & 18 deletions packages/core/src/xkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,34 +47,37 @@ export class XKeys extends EventEmitter {
static get vendorId(): number {
return XKEYS_VENDOR_ID
}
static filterDevice(deviceInfo: DeviceInfo): { product: Product; productId: number; interface: number } | null {
if (deviceInfo.vendorId !== XKEYS_VENDOR_ID) return null

for (const product of Object.values<Product>(PRODUCTS)) {
for (const hidDevice of product.hidDevices) {
if (
hidDevice[0] === deviceInfo.productId &&
(deviceInfo.interface === null || hidDevice[1] === deviceInfo.interface)
) {
return {
product,
productId: hidDevice[0],
interface: hidDevice[1],
} // Return & break out of the loops
}
}
}
return null
}

constructor(private device: HIDDevice, private deviceInfo: DeviceInfo, private _devicePath: string | undefined) {
super()

this.product = this._setupDevice(deviceInfo)
}
private _setupDevice(deviceInfo: DeviceInfo) {
const findProduct = (): { product: Product; productId: number; interface: number } => {
for (const product of Object.values<Product>(PRODUCTS)) {
for (const hidDevice of product.hidDevices) {
if (
hidDevice[0] === deviceInfo.productId &&
(deviceInfo.interface === null || hidDevice[1] === deviceInfo.interface)
) {
return {
product,
productId: hidDevice[0],
interface: hidDevice[1],
} // Return & break out of the loops
}
}
}
// else:
const found = XKeys.filterDevice(deviceInfo)
if (!found)
throw new Error(
`Unknown/Unsupported X-keys: "${deviceInfo.product}" (productId: "${deviceInfo.productId}", interface: "${deviceInfo.interface}").\nPlease report this as an issue on our github page!`
)
}
const found = findProduct()

this.device.on('data', (data: Buffer) => {
if (deviceInfo.productId === 210) {
Expand Down Expand Up @@ -813,7 +816,9 @@ export class XKeys extends EventEmitter {
}
type HIDMessage = (string | number)[]
interface DeviceInfo {
/** Name of the panel */
product: string | undefined
vendorId: number
productId: number
interface: number | null // null means "anything goes", used when interface isn't available
}
File renamed without changes.
48 changes: 48 additions & 0 deletions packages/node/examples/using-node-hid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const HID = require('node-hid')
const { setupXkeysPanel, XKeys } = require('xkeys')

/*
This example shows how to use node-hid to list all connected usb devices, then
connecting to any supported X-keys panels.
*/

Promise.resolve().then(async () => {

// List all connected usb devices:
const devices = await HID.devicesAsync()

for (const device of devices) {

// Filter for supported X-keys devices:
if (XKeys.filterDevice(device)) {

console.log('Connecting to X-keys device:', device.product)

setupXkeysPanel(device)
.then((xkeysPanel) => {
xkeysPanel.on('disconnected', () => {
console.log(`X-keys panel of type ${xkeysPanel.info.name} was disconnected`)
// Clean up stuff
xkeysPanel.removeAllListeners()
})
xkeysPanel.on('error', (...errs) => {
console.log('X-keys error:', ...errs)
})

xkeysPanel.on('down', (keyIndex, metadata) => {
console.log('Button pressed', keyIndex, metadata)
})

// ...
})
.catch(console.log) // Handle error

} else {
// is not an X-keys device
console.log('Not a supported X-keys device:', device.product || device.productId)
}

}

}).catch(console.log)

28 changes: 14 additions & 14 deletions packages/node/src/methods.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Product, XKeys } from '@xkeys-lib/core'
import { PRODUCTS } from '@xkeys-lib/core'
import { XKeys } from '@xkeys-lib/core'
import * as HID from 'node-hid'
import { NodeHIDDevice } from './node-hid-wrapper'

Expand All @@ -24,6 +23,7 @@ export async function setupXkeysPanel(
let deviceInfo:
| {
product: string | undefined
vendorId: number
productId: number
interface: number
}
Expand All @@ -42,6 +42,7 @@ export async function setupXkeysPanel(

deviceInfo = {
product: connectedXkeys[0].product,
vendorId: connectedXkeys[0].vendorId,
productId: connectedXkeys[0].productId,
interface: connectedXkeys[0].interface,
}
Expand All @@ -55,6 +56,7 @@ export async function setupXkeysPanel(

deviceInfo = {
product: devicePathOrHIDDevice.product,
vendorId: devicePathOrHIDDevice.vendorId,
productId: devicePathOrHIDDevice.productId,
interface: devicePathOrHIDDevice.interface,
}
Expand Down Expand Up @@ -84,6 +86,7 @@ export async function setupXkeysPanel(

deviceInfo = {
product: dInfo.product,
vendorId: dInfo.vendorId,
productId: dInfo.productId,
interface: dInfo.interface,
}
Expand All @@ -97,6 +100,7 @@ export async function setupXkeysPanel(
// Look through HID.devices(), because HID.Device contains the productId
deviceInfo = {
product: nodeHidInfo.product,
vendorId: nodeHidInfo.vendorId,
productId: nodeHidInfo.productId,
interface: nodeHidInfo.interface,
}
Expand Down Expand Up @@ -125,20 +129,16 @@ export function listAllConnectedPanels(): HID_Device[] {
const connectedXkeys = HID.devices().filter((device) => {
// Filter to only return the supported devices:

if (device.vendorId !== XKeys.vendorId) return false
if (!device.path) return false

let found = false
for (const product of Object.values<Product>(PRODUCTS)) {
for (const hidDevice of product.hidDevices) {
if (hidDevice[0] === device.productId && hidDevice[1] === device.interface) {
found = true
break
}
}
if (found) break
}
return found
const found = XKeys.filterDevice({
product: device.product,
interface: device.interface,
vendorId: device.vendorId,
productId: device.productId,
})
if (!found) return false
return true
})
return connectedXkeys as HID_Device[]
}
2 changes: 2 additions & 0 deletions packages/webhid/src/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function setupXkeysPanel(browserDevice: HIDDevice): Promise<XKeys>
if (!isValidXkeysUsage(browserDevice)) throw new Error(`Device has incorrect usage/interface`)
if (!browserDevice.productId) throw Error(`Device has no productId!`)

const vendorId = browserDevice.vendorId
const productId = browserDevice.productId

if (!browserDevice.opened) {
Expand All @@ -50,6 +51,7 @@ export async function setupXkeysPanel(browserDevice: HIDDevice): Promise<XKeys>
deviceWrap,
{
product: browserDevice.productName,
vendorId: vendorId,
productId: productId,
interface: null, // todo: Check what to use here (collection.usage?)
},
Expand Down

0 comments on commit ab542a8

Please sign in to comment.