Socket
Socket
Sign inDemoInstall

elgato-stream-deck

Package Overview
Dependencies
95
Maintainers
2
Versions
22
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.0 to 2.0.0

41

CHANGELOG.md

@@ -0,1 +1,42 @@

# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="2.0.0"></a>
# [2.0.0](https://github.com/Lange/node-elgato-stream-deck/compare/v1.2.0...v2.0.0) (2017-11-28)
### Features
* add `fillPanel` method
* add `clearAllKeys` method
* return the `StreamDeck` constructor instead of automatically instantiating it
* allow providing a `devicePath` to the constructor
* if no device path is provided, will attempt to use the first found Stream Deck. Errors if no Stream Decks are connected.
* update `this.keyState` *before* emitting `down` and `up` events
* this is technically a *breaking change*, but is very unlikely to affect any production code
### Bug Fixes
* fix center-cropping in `fillImageFromFile`
* fix `sharp` only being a `devDependency`, and not a production `dependency`
### Code Refactoring
* refactor `StreamDeck` class to move as much as possible to static methods and static getters
* refactor code to use `async`/`await`
* this is a *breaking change*, because we now only support Node.js v7.6 or newer
### Documentation
* update all examples
* add `fillPanel` example
### BREAKING CHANGES
* `this.keyState` is now updated **before** `down` and `up` events are emitted.
* Support for versions of Node.js earlier than 7.6 has been dropped.
* The `StreamDeck` constructor is now required when `require`ing this library, instead of an instance of the class.
* See the docs for updated examples.
<a name="1.2.0"></a>

@@ -2,0 +43,0 @@ # [1.2.0](https://github.com/Lange/node-elgato-stream-deck/compare/v1.1.0...v1.2.0) (2017-06-23)

255

index.js

@@ -16,27 +16,100 @@ 'use strict';

const NUM_TOTAL_PIXELS = NUM_FIRST_PAGE_PIXELS + NUM_SECOND_PAGE_PIXELS;
const NUM_BUTTON_COLUMNS = 5;
const NUM_BUTTON_ROWS = 3;
const devices = HID.devices();
const connectedStreamDecks = devices.filter(device => {
return device.vendorId === 0x0fd9 && device.productId === 0x0060;
});
class StreamDeck extends EventEmitter {
/**
* The pixel size of an icon written to the Stream Deck key.
*
* @readonly
*/
static get ICON_SIZE() {
return ICON_SIZE;
}
/* istanbul ignore if */
if (connectedStreamDecks.length > 1) {
throw new Error('More than one Stream Deck is connected. This is unsupported at this time.');
}
/**
* Checks a value is a valid RGB value. A number between 0 and 255.
*
* @static
* @param {number} value The number to check
*/
static checkRGBValue(value) {
if (value < 0 || value > 255) {
throw new TypeError('Expected a valid color RGB value 0 - 255');
}
}
/* istanbul ignore if */
if (connectedStreamDecks.length < 1) {
throw new Error('No Stream Decks are connected.');
}
/**
* Checks a keyIndex is a valid key for a stream deck. A number between 0 and 14.
*
* @static
* @param {number} keyIndex The keyIndex to check
*/
static checkValidKeyIndex(keyIndex) {
if (keyIndex < 0 || keyIndex > 14) {
throw new TypeError('Expected a valid keyIndex 0 - 14');
}
}
class StreamDeck extends EventEmitter {
constructor(device) {
/**
* Pads a given buffer till padLength with 0s.
*
* @private
* @param {Buffer} buffer Buffer to pad
* @param {number} padLength The length to pad to
* @returns {Buffer} The Buffer padded to the length requested
*/
static padBufferToLength(buffer, padLength) {
return Buffer.concat([buffer, StreamDeck.createPadBuffer(padLength - buffer.length)]);
}
/**
* Returns an empty buffer (filled with zeroes) of the given length
*
* @private
* @param {number} padLength Length of the buffer
* @returns {Buffer}
*/
static createPadBuffer(padLength) {
return Buffer.alloc(padLength);
}
/**
* Converts a buffer into an number[]. Used to supply the underlying
* node-hid device with the format it accepts.
*
* @static
* @param {Buffer} buffer Buffer to convert
* @returns {number[]} the converted buffer
*/
static bufferToIntArray(buffer) {
const array = [];
for (const pair of buffer.entries()) {
array.push(pair[1]);
}
return array;
}
constructor(devicePath) {
super();
this.device = device;
if (typeof devicePath === 'undefined') {
// Device path not provided, will then select any connected device.
const devices = HID.devices();
const connectedStreamDecks = devices.filter(device => {
return device.vendorId === 0x0fd9 && device.productId === 0x0060;
});
if (!connectedStreamDecks.length) {
throw new Error('No Stream Decks are connected.');
}
this.device = new HID.HID(connectedStreamDecks[0].path);
} else {
this.device = new HID.HID(devicePath);
}
this.keyState = new Array(NUM_KEYS).fill(false);
this.device.on('data', data => {
// The first byte is a report ID, the last byte appears to be padding
// strip these out for now.
// The first byte is a report ID, the last byte appears to be padding.
// We strip these out for now.
data = data.slice(1, data.length - 1);

@@ -46,3 +119,5 @@

const keyPressed = Boolean(data[i]);
if (keyPressed !== this.keyState[i]) {
const stateChanged = keyPressed !== this.keyState[i];
if (stateChanged) {
this.keyState[i] = keyPressed;
if (keyPressed) {

@@ -54,4 +129,2 @@ this.emit('down', i);

}
this.keyState[i] = keyPressed;
}

@@ -106,26 +179,2 @@ });

/**
* Checks a value is a valid RGB value. A number between 0 and 255.
*
* @static
* @param {number} value The number to check
*/
static checkRGBValue(value) {
if (value < 0 || value > 255) {
throw new TypeError('Expected a valid color RGB value 0 - 255');
}
}
/**
* Checks a keyIndex is a valid key for a stream deck. A number between 0 and 14.
*
* @static
* @param {number} keyIndex The keyIndex to check
*/
static checkValidKeyIndex(keyIndex) {
if (keyIndex < 0 || keyIndex > 14) {
throw new TypeError('Expected a valid keyIndex 0 - 14');
}
}
/**
* Fills the given key with an image in a Buffer.

@@ -169,8 +218,7 @@ *

*/
fillImageFromFile(keyIndex, filePath) {
async fillImageFromFile(keyIndex, filePath) {
StreamDeck.checkValidKeyIndex(keyIndex);
return sharp(filePath)
.flatten() // Eliminate alpha channel, if any.
.resize(this.ICON_SIZE)
.resize(StreamDeck.ICON_SIZE, StreamDeck.ICON_SIZE)
.raw()

@@ -184,2 +232,39 @@ .toBuffer()

/**
* Fills the whole panel with an image in a Buffer.
* The image is scaled to fit, and then center-cropped (if necessary).
*
* @param {Buffer|String} imagePathOrBuffer
* @param {Object} [sharpOptions] - Options to pass to sharp, necessary if supplying a buffer of raw pixels.
* See http://sharp.dimens.io/en/latest/api-constructor/#sharpinput-options for more details.
*/
async fillPanel(imagePathOrBuffer, sharpOptions) {
const image = await sharp(imagePathOrBuffer, sharpOptions)
.resize(NUM_BUTTON_COLUMNS * ICON_SIZE, NUM_BUTTON_ROWS * ICON_SIZE)
.flatten(); // Eliminate alpha channel, if any.
const buttons = [];
for (let row = 0; row < NUM_BUTTON_ROWS; row++) {
for (let column = 0; column < NUM_BUTTON_COLUMNS; column++) {
buttons.push({
index: (row * NUM_BUTTON_COLUMNS) + NUM_BUTTON_COLUMNS - column - 1,
x: column,
y: row
});
}
}
const buttonFillPromises = buttons.map(async button => {
const imageBuffer = await image.extract({
left: button.x * ICON_SIZE,
top: button.y * ICON_SIZE,
width: ICON_SIZE,
height: ICON_SIZE
}).raw().toBuffer();
return this.fillImage(button.index, imageBuffer);
});
return Promise.all(buttonFillPromises);
}
/**
* Clears the given key.

@@ -192,3 +277,2 @@ *

StreamDeck.checkValidKeyIndex(keyIndex);
return this.fillColor(keyIndex, 0, 0, 0);

@@ -198,2 +282,13 @@ }

/**
* Clears all keys.
*
* returns {undefined}
*/
clearAllKeys() {
for (let keyIndex = 0; keyIndex < NUM_KEYS; keyIndex++) {
this.clearKey(keyIndex);
}
}
/**
* Sets the brightness of the keys on the Stream Deck

@@ -207,3 +302,5 @@ *

}
this.sendFeatureReport(this._padToLength(Buffer.from([0x05, 0x55, 0xaa, 0xd1, 0x01, percentage]), 17));
const brightnessCommandBuffer = Buffer.from([0x05, 0x55, 0xaa, 0xd1, 0x01, percentage]);
this.sendFeatureReport(StreamDeck.padBufferToLength(brightnessCommandBuffer, 17));
}

@@ -232,3 +329,3 @@

const packet = this._padToLength(Buffer.concat([header, buffer]), PAGE_PACKET_SIZE);
const packet = StreamDeck.padBufferToLength(Buffer.concat([header, buffer]), PAGE_PACKET_SIZE);
return this.write(packet);

@@ -246,56 +343,12 @@ }

_writePage2(keyIndex, buffer) {
const header = Buffer.from([0x02, 0x01, 0x02, 0x00, 0x01, keyIndex + 1]);
const packet = this._padToLength(Buffer.concat([header, this._pad(10), buffer]), PAGE_PACKET_SIZE);
const header = Buffer.from([
0x02, 0x01, 0x02, 0x00, 0x01, keyIndex + 1, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]);
const packet = StreamDeck.padBufferToLength(Buffer.concat([header, buffer]), PAGE_PACKET_SIZE);
return this.write(packet);
}
/**
* Pads a given buffer till padLength with 0s.
*
* @private
* @param {Buffer} buffer Buffer to pad
* @param {number} padLength The length to pad to
* @returns {Buffer} The Buffer padded to the length requested
*/
_padToLength(buffer, padLength) {
return Buffer.concat([buffer, this._pad(padLength - buffer.length)]);
}
/**
* Returns an empty buffer (filled with zeroes) of the given length
*
* @private
* @param {number} padLength Length of the buffer
* @returns {Buffer}
*/
_pad(padLength) {
return Buffer.alloc(padLength);
}
/**
* The pixel size of an icon written to the Stream Deck key.
*
* @readonly
*/
get ICON_SIZE() {
return ICON_SIZE;
}
/**
* Converts a buffer into an number[]. Used to supply the underlying
* node-hid device with the format it accepts.
*
* @static
* @param {Buffer} buffer Buffer to convert
* @returns {number[]} the converted buffer
*/
static bufferToIntArray(buffer) {
const array = [];
for (const pair of buffer.entries()) {
array.push(pair[1]);
}
return array;
}
}
module.exports = new StreamDeck(new HID.HID(connectedStreamDecks[0].path));
module.exports = StreamDeck;

@@ -0,0 +0,0 @@ MIT License

{
"name": "elgato-stream-deck",
"version": "1.2.0",
"version": "2.0.0",
"description": "An npm module for interfacing with the Elgato Stream Deck",

@@ -8,15 +8,16 @@ "main": "index.js",

"dependencies": {
"node-hid": "^0.5.4"
"node-hid": "^0.5.7",
"sharp": "^0.18.4"
},
"devDependencies": {
"ava": "^0.19.1",
"coveralls": "^2.13.1",
"eslint": "^3.19.0",
"eslint-config-xo": "^0.18.2",
"eslint-plugin-ava": "^4.2.0",
"mockery": "^2.0.0",
"nyc": "^10.3.2",
"pureimage": "0.0.21",
"sharp": "^0.17.3",
"sinon": "^2.2.0",
"ava": "^0.23.0",
"coveralls": "^3.0.0",
"eslint": "^4.11.0",
"eslint-config-xo": "^0.19.0",
"eslint-plugin-ava": "^4.2.2",
"mockery": "^2.1.0",
"nyc": "^11.3.0",
"pureimage": "0.1.3",
"sinon": "^4.1.2",
"standard-version": "^4.2.0",
"stream-buffers": "^3.0.1",

@@ -27,2 +28,5 @@ "weallbehave": "^1.2.0",

"scripts": {
"prerelease": "npm t",
"release": "standard-version",
"postrelease": "npm publish && git push --follow-tags",
"pretest": "npm run static",

@@ -82,4 +86,4 @@ "test": "nyc ava",

"engines": {
"node": ">=4"
"node": ">=7.6"
}
}

@@ -21,3 +21,3 @@ # elgato-stream-deck [![npm version](https://img.shields.io/npm/v/elgato-stream-deck.svg)](https://npm.im/elgato-stream-deck) [![license](https://img.shields.io/npm/l/elgato-stream-deck.svg)](https://npm.im/elgato-stream-deck) [![Travis](https://travis-ci.org/Lange/node-elgato-stream-deck.svg?branch=master)](https://travis-ci.org/Lange/node-elgato-stream-deck) [![Coverage Status](https://coveralls.io/repos/github/Lange/node-elgato-stream-deck/badge.svg?branch=master)](https://coveralls.io/github/Lange/node-elgato-stream-deck?branch=master) [![Join the chat at https://gitter.im/node-elgato-stream-deck/Lobby](https://badges.gitter.im/node-elgato-stream-deck/Lobby.svg)](https://gitter.im/node-elgato-stream-deck/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

* MacOS
* Install [Xcode](https://developer.apple.com/xcode/download/), then:
* Install the Xcode Command Line Tools:
```bash

@@ -28,12 +28,12 @@ xcode-select --install

* Follow the instructions for Linux in the ["Compiling from source"](https://github.com/node-hid/node-hid#compiling-from-source) steps for `node-hid`:
```bash
sudo apt-get install build-essential git
sudo apt-get install gcc-4.8 g++-4.8 && export CXX=g++-4.8
sudo apt-get install sudo apt install libusb-1.0-0 libusb-1.0-0-dev
```
* Install a recent version of Node.js. We've had success with v7:
```bash
curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt-get install -y nodejs
```
```bash
sudo apt-get install build-essential git
sudo apt-get install gcc-4.8 g++-4.8 && export CXX=g++-4.8
sudo apt-get install sudo apt install libusb-1.0-0 libusb-1.0-0-dev
```
* Install a recent version of Node.js.:
```bash
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
```
* Try installing `node-elgato-stream-deck`

@@ -56,3 +56,5 @@ * If you still have issues, ensure everything is updated and try again:

* [`fillImage`](#fill-image)
* [`fillPanel`](#fill-panel)
* [`clearKey`](#clear-key)
* [`clearAllKeys`](#clear-all-keys)
* [`setBrightness`](#set-brightness)

@@ -71,14 +73,21 @@ * [Events](#events)

const path = require('path');
const streamDeck = require('elgato-stream-deck');
const StreamDeck = require('elgato-stream-deck');
streamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
// Automatically discovers connected Stream Decks, and attaches to the first one.
// Throws if there are no connected stream decks.
// You also have the option of providing the devicePath yourself as the first argument to the constructor.
// For example: const myStreamDeck = new StreamDeck('\\\\?\\hid#vid_05f3&pid_0405&mi_00#7&56cf813&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}')
// Device paths can be obtained via node-hid: https://github.com/node-hid/node-hid
const myStreamDeck = new StreamDeck();
myStreamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
});
streamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
myStreamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
});
streamDeck.on('error', error => {
console.error(error);
myStreamDeck.on('error', error => {
console.error(error);
});

@@ -88,3 +97,3 @@

// This is asynchronous and returns a promise.
streamDeck.fillImageFromFile(3, path.resolve(__dirname, 'github_logo.png')).then(() => {
myStreamDeck.fillImageFromFile(3, path.resolve(__dirname, 'github_logo.png')).then(() => {
console.log('Successfully wrote a GitHub logo to key 3.');

@@ -94,3 +103,3 @@ });

// Fill the first button form the left in the first row with a solid red color. This is synchronous.
streamDeck.fillColor(4, 255, 0, 0);
myStreamDeck.fillColor(4, 255, 0, 0);
console.log('Successfully wrote a red square to key 4.');

@@ -102,14 +111,15 @@ ```

```typescript
import streamDeck = require('elgato-stream-deck');
import StreamDeck = require('elgato-stream-deck');
const myStreamDeck = new StreamDeck(); // Will throw an error if no Stream Decks are connected.
streamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
myStreamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
});
streamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
myStreamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
});
streamDeck.on('error', error => {
console.error(error);
myStreamDeck.on('error', error => {
console.error(error);
});

@@ -120,7 +130,8 @@ ```

* Miltiplatform support: Windows 7-10, MacOS, Linux, and even Raspberry Pi!
* Multiplatform support: Windows 7-10, MacOS, Linux, and even Raspberry Pi!
* Key `down` and key `up` events
* Fill keys with images or solid RGB colors
* Typescript support
* Fill the entire panel with a single image, spread across all keys
* Set the Stream Deck brightness
* TypeScript support

@@ -199,4 +210,4 @@ ### Planned Features

.flatten() // Eliminate alpha channel, if any.
.resize(streamDeck.ICON_SIZE) // Scale down to the right size, cropping if necessary.
.raw() // Give us uncompressed RGB
.resize(streamDeck.ICON_SIZE, streamDeck.ICON_SIZE) // Scale up/down to the right size, cropping if necessary.
.raw() // Give us uncompressed RGB.
.toBuffer()

@@ -211,2 +222,21 @@ .then(buffer => {

#### <a name="fill-panel"></a> `> streamDeck.fillPanel(imagePathOrBuffer[, sharpOptions]) -> Promise`
Asynchronously applies an image to the entire panel, spreading it over all keys. The image is scaled down and center-cropped to fit. This method does not currently account for the gaps between keys, and behaves as if each key was directly connected to its neighbors. If you wish to account for the gaps between keys, you'll need to do so via other means, and bake that into the image you provide to `fillPanel`.
This method accepts either a path to an image on the disk, or a buffer. The image or path or buffer is passed directly to [`sharp`](https://github.com/lovell/sharp). Therefore, this method accepts all images and buffers which `sharp` can accept.
##### Example
```javascript
// Fill the second button from the left in the first row with an image of the GitHub logo.
streamDeck.fillImageFromFile(3, path.resolve(__dirname, 'github_logo.png'))
.then(() => {
console.log('Successfully wrote a GitHub logo to key 3.');
})
.catch(err => {
console.error(err);
});
```
#### <a name="clear-key"></a> `> streamDeck.clearKey(keyIndex) -> undefined`

@@ -216,2 +246,6 @@

#### <a name="clear-all-keys"></a> `> streamDeck.clearAllKeys() -> undefined`
Synchronously clears all keys on the device.
##### Example

@@ -226,3 +260,3 @@

Synchronously set the brightness of the Stream Deck.
Synchronously set the brightness of the Stream Deck. This affects all keys at once. The brightness of individual keys cannot be controlled.

@@ -246,3 +280,3 @@ ##### Example

streamDeck.on('down', keyIndex => {
console.log('key %d down', keyIndex);
console.log('key %d down', keyIndex);
});

@@ -259,3 +293,3 @@ ```

streamDeck.on('up', keyIndex => {
console.log('key %d up', keyIndex);
console.log('key %d up', keyIndex);
});

@@ -273,3 +307,3 @@ ```

streamDeck.on('error', error => {
console.error(error);
console.error(error);
});

@@ -276,0 +310,0 @@ ```

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc