UNPKG

@xkeys-lib/core

Version:

NPM package to interact with the X-keys panels

198 lines 8.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GenericXKeysWatcher = void 0; const events_1 = require("events"); /** * Set up a watcher for newly connected X-keys panels. * Note: It is highly recommended to set up a listener for the disconnected event on the X-keys panel, to clean up after a disconnected device. */ class GenericXKeysWatcher extends events_1.EventEmitter { constructor(_options) { super(); this._options = _options; this.updateConnectedDevicesTimeout = null; this.updateConnectedDevicesIsRunning = false; this.updateConnectedDevicesRunAgain = false; this.seenDevices = new Set(); this.setupXkeys = new Map(); /** A value that is incremented whenever we expect to find a new or removed device in updateConnectedDevices(). */ this.shouldFindChangedReTries = 0; this.isActive = true; this.debug = false; /** A list of the devices we've called setupNewDevice() for */ // private setupXkeysPanels: XKeys[] = [] this.prevConnectedIdentifiers = {}; /** Unique unitIds grouped into productId groups. */ this.uniqueIds = new Map(); // Do a sweep for all currently connected X-keys panels: this.triggerUpdateConnectedDevices(false); } get options() { var _a, _b, _c, _d, _e, _f; return { automaticUnitIdMode: (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.automaticUnitIdMode) !== null && _b !== void 0 ? _b : false, usePolling: (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.usePolling) !== null && _d !== void 0 ? _d : false, pollingInterval: (_f = (_e = this._options) === null || _e === void 0 ? void 0 : _e.pollingInterval) !== null && _f !== void 0 ? _f : 1000, }; } /** * Stop the watcher * @param closeAllDevices Set to false in order to NOT close all devices. Use this if you only want to stop the watching. Defaults to true */ async stop(closeAllDevices = true) { // To be implemented by the subclass and call super.stop() at the end this.isActive = false; if (closeAllDevices) { // In order for an application to close gracefully, // we need to close all devices that we've called setupXkeysPanel() on: await Promise.all(Array.from(this.seenDevices.keys()).map(async (device) => this.handleRemovedDevice(device))); } } triggerUpdateConnectedDevices(somethingWasAddedOrRemoved) { if (somethingWasAddedOrRemoved) { this.shouldFindChangedReTries++; } if (this.updateConnectedDevicesIsRunning) { // It is already running, so we'll run it again later, when it's done: this.updateConnectedDevicesRunAgain = true; return; } else if (this.updateConnectedDevicesTimeout) { // It is already scheduled to run. if (somethingWasAddedOrRemoved) { // Set it to run now: clearTimeout(this.updateConnectedDevicesTimeout); this.updateConnectedDevicesTimeout = null; } else { return; } } if (!this.updateConnectedDevicesTimeout) { this.updateConnectedDevicesRunAgain = false; this.updateConnectedDevicesTimeout = setTimeout(() => { this.updateConnectedDevicesTimeout = null; this.updateConnectedDevicesIsRunning = true; this.updateConnectedDevices() .catch(console.error) .finally(() => { this.updateConnectedDevicesIsRunning = false; if (this.updateConnectedDevicesRunAgain) this.triggerUpdateConnectedDevices(false); }); }, somethingWasAddedOrRemoved ? 10 : Math.min(this.options.pollingInterval * 0.5, 300)); } } async updateConnectedDevices() { this.debugLog('updateConnectedDevices'); const connectedDevices = await this.getConnectedDevices(); let removed = 0; let added = 0; // Removed devices: for (const device of this.seenDevices.keys()) { if (!connectedDevices.has(device)) { // A device has been removed this.debugLog('removed'); removed++; await this.handleRemovedDevice(device); } } // Added devices: for (const connectedDevice of connectedDevices.keys()) { if (!this.seenDevices.has(connectedDevice)) { // A device has been added this.debugLog('added'); added++; this.seenDevices.add(connectedDevice); this.handleNewDevice(connectedDevice); } } if (this.shouldFindChangedReTries > 0 && (added === 0 || removed === 0)) { // We expected to find something changed, but didn't. // Try again later: this.shouldFindChangedReTries--; this.triggerUpdateConnectedDevices(false); } else { this.shouldFindChangedReTries = 0; } } handleNewDevice(device) { // This is called when a new device has been added / connected this.setupXkeysPanel(device) .then(async (xKeysPanel) => { // Since this is async, check if the panel is still connected: if (this.seenDevices.has(device)) { await this.setupNewDevice(device, xKeysPanel); } else { await this.handleRemovedDevice(device); } }) .catch((err) => { this.emit('error', err); }); } async handleRemovedDevice(device) { // This is called when a device has been removed / disconnected this.seenDevices.delete(device); const xkeys = this.setupXkeys.get(device); this.debugLog('aa'); if (xkeys) { this.debugLog('bb'); await xkeys._handleDeviceDisconnected(); this.setupXkeys.delete(device); } } async setupNewDevice(device, xKeysPanel) { // Store for future reference: this.setupXkeys.set(device, xKeysPanel); xKeysPanel.once('disconnected', () => { this.handleRemovedDevice(device).catch((e) => this.emit('error', e)); }); // this.setupXkeysPanels.push(xkeysPanel) if (this.options.automaticUnitIdMode) { if (xKeysPanel.unitId === 0) { // if it is 0, we assume that it's new from the factory and can be safely changed xKeysPanel.setUnitId(this._getNextUniqueId(xKeysPanel)); // the lookup-cache is stored either in memory, or preferably on disk } // the PID+UID pair is enough to uniquely identify a panel. const uniqueIdentifier = xKeysPanel.uniqueId; const previousXKeysPanel = this.prevConnectedIdentifiers[uniqueIdentifier]; if (previousXKeysPanel) { // This panel has been connected before. // We want the XKeys-instance to emit a 'reconnected' event. // This means that we kill off the newly created xkeysPanel, and await previousXKeysPanel._handleDeviceReconnected(xKeysPanel._getHIDDevice(), xKeysPanel._getDeviceInfo()); } else { // It seems that this panel hasn't been connected before this.emit('connected', xKeysPanel); this.prevConnectedIdentifiers[uniqueIdentifier] = xKeysPanel; } } else { // Default behavior: this.emit('connected', xKeysPanel); } } _getNextUniqueId(xkeysPanel) { let nextId = this.uniqueIds.get(xkeysPanel.info.productId); if (!nextId) { nextId = 32; // Starting at 32 } else { nextId++; } if (nextId > 255) throw new Error('No more unique ids available!'); this.uniqueIds.set(xkeysPanel.info.productId, nextId); return nextId; } debugLog(...args) { if (this.debug) console.log(...args); } } exports.GenericXKeysWatcher = GenericXKeysWatcher; //# sourceMappingURL=watcher.js.map