macromate-hid 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -0
- package/cli.js +279 -0
- package/package.json +44 -0
- package/src/constants.js +106 -0
- package/src/controller.js +387 -0
- package/src/index.js +30 -0
- package/types/index.d.ts +338 -0
package/src/constants.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MacroMate HID Constants
|
|
3
|
+
* Protocol constants for MacroMate device
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Device config
|
|
7
|
+
const VID = 0xCAFE;
|
|
8
|
+
const PID = 0x4010;
|
|
9
|
+
const VENDOR_USAGE_PAGE = 0xFF00;
|
|
10
|
+
const REPORT_ID_VENDOR = 3;
|
|
11
|
+
const DEFAULT_TIMEOUT = 2000; // ms
|
|
12
|
+
const TYPE_STRING_TIMEOUT = 10000; // ms
|
|
13
|
+
|
|
14
|
+
// Commands (from protocol.h)
|
|
15
|
+
const CMD = {
|
|
16
|
+
PING: 0x00,
|
|
17
|
+
GET_VERSION: 0x01,
|
|
18
|
+
BOOTLOADER: 0x02,
|
|
19
|
+
KEY_PRESS: 0x10,
|
|
20
|
+
KEY_RELEASE: 0x11,
|
|
21
|
+
KEY_TAP: 0x12,
|
|
22
|
+
KEY_MODIFIER: 0x13,
|
|
23
|
+
KEY_RELEASE_ALL: 0x14,
|
|
24
|
+
KEY_TYPE_STRING: 0x15,
|
|
25
|
+
MOUSE_MOVE: 0x30,
|
|
26
|
+
MOUSE_CLICK: 0x32,
|
|
27
|
+
MOUSE_PRESS: 0x33,
|
|
28
|
+
MOUSE_RELEASE: 0x34,
|
|
29
|
+
MOUSE_SCROLL: 0x35,
|
|
30
|
+
DELAY: 0x50,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Mouse buttons
|
|
34
|
+
const MOUSE_BTN = {
|
|
35
|
+
LEFT: 0x01,
|
|
36
|
+
RIGHT: 0x02,
|
|
37
|
+
MIDDLE: 0x04
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Keyboard modifiers
|
|
41
|
+
const MOD = {
|
|
42
|
+
CTRL: 0x01,
|
|
43
|
+
SHIFT: 0x02,
|
|
44
|
+
ALT: 0x04,
|
|
45
|
+
WIN: 0x08
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// HID Keycodes
|
|
49
|
+
const KEY = {
|
|
50
|
+
// Letters
|
|
51
|
+
A: 0x04, B: 0x05, C: 0x06, D: 0x07, E: 0x08, F: 0x09, G: 0x0A, H: 0x0B,
|
|
52
|
+
I: 0x0C, J: 0x0D, K: 0x0E, L: 0x0F, M: 0x10, N: 0x11, O: 0x12, P: 0x13,
|
|
53
|
+
Q: 0x14, R: 0x15, S: 0x16, T: 0x17, U: 0x18, V: 0x19, W: 0x1A, X: 0x1B,
|
|
54
|
+
Y: 0x1C, Z: 0x1D,
|
|
55
|
+
|
|
56
|
+
// Numbers
|
|
57
|
+
N1: 0x1E, N2: 0x1F, N3: 0x20, N4: 0x21, N5: 0x22,
|
|
58
|
+
N6: 0x23, N7: 0x24, N8: 0x25, N9: 0x26, N0: 0x27,
|
|
59
|
+
|
|
60
|
+
// Special keys
|
|
61
|
+
ENTER: 0x28, ESC: 0x29, BACKSPACE: 0x2A, TAB: 0x2B, SPACE: 0x2C,
|
|
62
|
+
MINUS: 0x2D, EQUAL: 0x2E, BRACKET_LEFT: 0x2F, BRACKET_RIGHT: 0x30,
|
|
63
|
+
BACKSLASH: 0x31, SEMICOLON: 0x33, QUOTE: 0x34, GRAVE: 0x35,
|
|
64
|
+
COMMA: 0x36, PERIOD: 0x37, SLASH: 0x38, CAPS_LOCK: 0x39,
|
|
65
|
+
|
|
66
|
+
// Function keys
|
|
67
|
+
F1: 0x3A, F2: 0x3B, F3: 0x3C, F4: 0x3D, F5: 0x3E, F6: 0x3F,
|
|
68
|
+
F7: 0x40, F8: 0x41, F9: 0x42, F10: 0x43, F11: 0x44, F12: 0x45,
|
|
69
|
+
|
|
70
|
+
// Navigation
|
|
71
|
+
PRINT_SCREEN: 0x46, SCROLL_LOCK: 0x47, PAUSE: 0x48,
|
|
72
|
+
INSERT: 0x49, HOME: 0x4A, PAGE_UP: 0x4B,
|
|
73
|
+
DELETE: 0x4C, END: 0x4D, PAGE_DOWN: 0x4E,
|
|
74
|
+
RIGHT: 0x4F, LEFT: 0x50, DOWN: 0x51, UP: 0x52,
|
|
75
|
+
|
|
76
|
+
// Numpad
|
|
77
|
+
NUM_LOCK: 0x53, NUMPAD_SLASH: 0x54, NUMPAD_ASTERISK: 0x55,
|
|
78
|
+
NUMPAD_MINUS: 0x56, NUMPAD_PLUS: 0x57, NUMPAD_ENTER: 0x58,
|
|
79
|
+
NUMPAD_1: 0x59, NUMPAD_2: 0x5A, NUMPAD_3: 0x5B,
|
|
80
|
+
NUMPAD_4: 0x5C, NUMPAD_5: 0x5D, NUMPAD_6: 0x5E,
|
|
81
|
+
NUMPAD_7: 0x5F, NUMPAD_8: 0x60, NUMPAD_9: 0x61,
|
|
82
|
+
NUMPAD_0: 0x62, NUMPAD_PERIOD: 0x63,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Status codes
|
|
86
|
+
const STATUS = {
|
|
87
|
+
OK: 0x00,
|
|
88
|
+
ERROR_UNKNOWN: 0x01,
|
|
89
|
+
ERROR_INVALID: 0x02,
|
|
90
|
+
ERROR_OVERFLOW: 0x03,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
VID,
|
|
95
|
+
PID,
|
|
96
|
+
VENDOR_USAGE_PAGE,
|
|
97
|
+
REPORT_ID_VENDOR,
|
|
98
|
+
DEFAULT_TIMEOUT,
|
|
99
|
+
TYPE_STRING_TIMEOUT,
|
|
100
|
+
CMD,
|
|
101
|
+
MOUSE_BTN,
|
|
102
|
+
MOD,
|
|
103
|
+
KEY,
|
|
104
|
+
STATUS,
|
|
105
|
+
};
|
|
106
|
+
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MacroController - Main class for communicating with MacroMate device
|
|
3
|
+
* Promise-based API with async/await support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const HID = require('node-hid');
|
|
7
|
+
const {
|
|
8
|
+
VID, PID, VENDOR_USAGE_PAGE, REPORT_ID_VENDOR,
|
|
9
|
+
DEFAULT_TIMEOUT, TYPE_STRING_TIMEOUT,
|
|
10
|
+
CMD, MOUSE_BTN, MOD, KEY, STATUS
|
|
11
|
+
} = require('./constants');
|
|
12
|
+
|
|
13
|
+
class MacroController {
|
|
14
|
+
/**
|
|
15
|
+
* Create a MacroController instance
|
|
16
|
+
* @param {Object} options - Configuration options
|
|
17
|
+
* @param {number} [options.vid=0xCAFE] - Vendor ID
|
|
18
|
+
* @param {number} [options.pid=0x4010] - Product ID
|
|
19
|
+
* @param {number} [options.timeout=2000] - Default command timeout (ms)
|
|
20
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
21
|
+
*/
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.vid = options.vid || VID;
|
|
24
|
+
this.pid = options.pid || PID;
|
|
25
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
26
|
+
this.debug = options.debug || false;
|
|
27
|
+
this.device = null;
|
|
28
|
+
this.deviceInfo = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Log debug messages
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
_log(...args) {
|
|
36
|
+
if (this.debug) {
|
|
37
|
+
console.log('[MacroController]', ...args);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Find all matching devices
|
|
43
|
+
* @returns {Array} Array of device info objects
|
|
44
|
+
*/
|
|
45
|
+
static findDevices(vid = VID, pid = PID) {
|
|
46
|
+
const devices = HID.devices();
|
|
47
|
+
return devices.filter(d =>
|
|
48
|
+
d.vendorId === vid &&
|
|
49
|
+
d.productId === pid &&
|
|
50
|
+
d.usagePage === VENDOR_USAGE_PAGE
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* List all HID devices with the specified VID
|
|
56
|
+
* @param {number} [vid=0xCAFE] - Vendor ID to filter by
|
|
57
|
+
* @returns {Array} Array of device info objects
|
|
58
|
+
*/
|
|
59
|
+
static listDevices(vid = VID) {
|
|
60
|
+
return HID.devices().filter(d => d.vendorId === vid);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if device is connected
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
get isConnected() {
|
|
68
|
+
return this.device !== null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Connect to the device
|
|
73
|
+
* @returns {boolean} True if connected successfully
|
|
74
|
+
* @throws {Error} If device not found or connection failed
|
|
75
|
+
*/
|
|
76
|
+
connect() {
|
|
77
|
+
const devices = HID.devices();
|
|
78
|
+
|
|
79
|
+
this.deviceInfo = devices.find(d =>
|
|
80
|
+
d.vendorId === this.vid &&
|
|
81
|
+
d.productId === this.pid &&
|
|
82
|
+
d.usagePage === VENDOR_USAGE_PAGE
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!this.deviceInfo) {
|
|
86
|
+
throw new Error(`Device not found (VID=0x${this.vid.toString(16)} PID=0x${this.pid.toString(16)})`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this._log('Found device:', this.deviceInfo.product, this.deviceInfo.path);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
this.device = new HID.HID(this.deviceInfo.path);
|
|
93
|
+
this._log('Connected successfully');
|
|
94
|
+
|
|
95
|
+
this.device.on('error', (err) => {
|
|
96
|
+
this._log('HID Error:', err.message);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return true;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
throw new Error(`Connection failed: ${e.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Disconnect from the device
|
|
107
|
+
*/
|
|
108
|
+
disconnect() {
|
|
109
|
+
if (this.device) {
|
|
110
|
+
this.device.close();
|
|
111
|
+
this.device = null;
|
|
112
|
+
this.deviceInfo = null;
|
|
113
|
+
this._log('Disconnected');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Send a raw command to the device
|
|
119
|
+
* @param {number} cmd - Command byte
|
|
120
|
+
* @param {number[]} args - Command arguments
|
|
121
|
+
* @param {Object} options - Options
|
|
122
|
+
* @param {number} [options.timeout] - Command timeout (ms)
|
|
123
|
+
* @returns {Promise<{status: number, data: Buffer}>}
|
|
124
|
+
*/
|
|
125
|
+
sendCommand(cmd, args = [], options = {}) {
|
|
126
|
+
const timeout = options.timeout || this.timeout;
|
|
127
|
+
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
if (!this.device) {
|
|
130
|
+
reject(new Error('Device not connected'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Build packet
|
|
135
|
+
const packet = Buffer.alloc(64);
|
|
136
|
+
packet[0] = cmd;
|
|
137
|
+
args.forEach((arg, i) => {
|
|
138
|
+
packet[1 + i] = arg & 0xFF;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Set up timeout
|
|
142
|
+
const timeoutId = setTimeout(() => {
|
|
143
|
+
reject(new Error('Command timeout'));
|
|
144
|
+
}, timeout);
|
|
145
|
+
|
|
146
|
+
// Set up read callback BEFORE writing
|
|
147
|
+
this.device.read((err, data) => {
|
|
148
|
+
clearTimeout(timeoutId);
|
|
149
|
+
|
|
150
|
+
if (err) {
|
|
151
|
+
reject(err);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (data && data.length > 0) {
|
|
156
|
+
// First byte is Report ID (0x03), skip it
|
|
157
|
+
const payload = data.slice(1);
|
|
158
|
+
const status = payload[0];
|
|
159
|
+
|
|
160
|
+
this._log(`Response: status=${status === 0 ? 'OK' : 'ERROR ' + status}`);
|
|
161
|
+
|
|
162
|
+
resolve({ status, data: Buffer.from(payload) });
|
|
163
|
+
} else {
|
|
164
|
+
reject(new Error('Empty response'));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Send the command
|
|
169
|
+
try {
|
|
170
|
+
this.device.write([REPORT_ID_VENDOR, ...packet]);
|
|
171
|
+
this._log(`Sent cmd=0x${cmd.toString(16).padStart(2, '0')} args=[${args.map(a => '0x' + a.toString(16)).join(', ')}]`);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
clearTimeout(timeoutId);
|
|
174
|
+
reject(e);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Send command without waiting for response (for bootloader)
|
|
181
|
+
* @param {number} cmd - Command byte
|
|
182
|
+
* @param {number[]} args - Command arguments
|
|
183
|
+
*/
|
|
184
|
+
sendCommandNoWait(cmd, args = []) {
|
|
185
|
+
if (!this.device) {
|
|
186
|
+
throw new Error('Device not connected');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const packet = Buffer.alloc(64);
|
|
190
|
+
packet[0] = cmd;
|
|
191
|
+
args.forEach((arg, i) => {
|
|
192
|
+
packet[1 + i] = arg & 0xFF;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
this.device.write([REPORT_ID_VENDOR, ...packet]);
|
|
196
|
+
this._log(`Sent (no wait) cmd=0x${cmd.toString(16).padStart(2, '0')}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================
|
|
200
|
+
// HIGH-LEVEL API
|
|
201
|
+
// ============================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Ping the device
|
|
205
|
+
* @returns {Promise<{version: string}>} Firmware version
|
|
206
|
+
*/
|
|
207
|
+
async ping() {
|
|
208
|
+
const result = await this.sendCommand(CMD.PING);
|
|
209
|
+
|
|
210
|
+
// Check for PONG response
|
|
211
|
+
if (result.data[1] === 0x50 && result.data[2] === 0x4F &&
|
|
212
|
+
result.data[3] === 0x4E && result.data[4] === 0x47) {
|
|
213
|
+
return {
|
|
214
|
+
version: `${result.data[5]}.${result.data[6]}.${result.data[7]}`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { version: 'unknown' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get firmware version
|
|
223
|
+
* @returns {Promise<string>} Version string (e.g., "1.0.0")
|
|
224
|
+
*/
|
|
225
|
+
async getVersion() {
|
|
226
|
+
const result = await this.sendCommand(CMD.GET_VERSION);
|
|
227
|
+
return `${result.data[1]}.${result.data[2]}.${result.data[3]}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Enter bootloader mode (device will reboot)
|
|
232
|
+
* Connection will be lost after this command
|
|
233
|
+
*/
|
|
234
|
+
enterBootloader() {
|
|
235
|
+
this.sendCommandNoWait(CMD.BOOTLOADER);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================
|
|
239
|
+
// KEYBOARD API
|
|
240
|
+
// ============================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Press a key (and hold)
|
|
244
|
+
* @param {number} keycode - HID keycode (use KEY constants)
|
|
245
|
+
*/
|
|
246
|
+
async keyPress(keycode) {
|
|
247
|
+
await this.sendCommand(CMD.KEY_PRESS, [keycode]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Release a key
|
|
252
|
+
* @param {number} keycode - HID keycode
|
|
253
|
+
*/
|
|
254
|
+
async keyRelease(keycode) {
|
|
255
|
+
await this.sendCommand(CMD.KEY_RELEASE, [keycode]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Tap a key (press and release)
|
|
260
|
+
* @param {number} keycode - HID keycode
|
|
261
|
+
*/
|
|
262
|
+
async keyTap(keycode) {
|
|
263
|
+
await this.sendCommand(CMD.KEY_TAP, [keycode]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Set modifier keys (Ctrl, Shift, Alt, Win)
|
|
268
|
+
* @param {number} modifiers - Modifier flags (use MOD constants, can be combined with |)
|
|
269
|
+
*/
|
|
270
|
+
async setModifiers(modifiers) {
|
|
271
|
+
await this.sendCommand(CMD.KEY_MODIFIER, [modifiers]);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Release all keys and modifiers
|
|
276
|
+
*/
|
|
277
|
+
async releaseAll() {
|
|
278
|
+
await this.sendCommand(CMD.KEY_RELEASE_ALL);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Type a string (ASCII only)
|
|
283
|
+
* @param {string} text - Text to type (max 62 characters per call)
|
|
284
|
+
*/
|
|
285
|
+
async typeString(text) {
|
|
286
|
+
const bytes = Buffer.from(text, 'ascii');
|
|
287
|
+
const len = Math.min(bytes.length, 62);
|
|
288
|
+
await this.sendCommand(
|
|
289
|
+
CMD.KEY_TYPE_STRING,
|
|
290
|
+
[len, ...bytes.slice(0, len)],
|
|
291
|
+
{ timeout: TYPE_STRING_TIMEOUT }
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Execute a keyboard shortcut (e.g., Ctrl+C)
|
|
297
|
+
* @param {number} modifiers - Modifier flags
|
|
298
|
+
* @param {number} keycode - HID keycode
|
|
299
|
+
*/
|
|
300
|
+
async shortcut(modifiers, keycode) {
|
|
301
|
+
await this.setModifiers(modifiers);
|
|
302
|
+
await this.keyTap(keycode);
|
|
303
|
+
await this.releaseAll();
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ============================================================
|
|
307
|
+
// MOUSE API
|
|
308
|
+
// ============================================================
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Move mouse relative to current position
|
|
312
|
+
* @param {number} x - X movement (-127 to 127)
|
|
313
|
+
* @param {number} y - Y movement (-127 to 127)
|
|
314
|
+
*/
|
|
315
|
+
async mouseMove(x, y) {
|
|
316
|
+
// Convert signed to unsigned byte
|
|
317
|
+
await this.sendCommand(CMD.MOUSE_MOVE, [x & 0xFF, y & 0xFF]);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Click a mouse button
|
|
322
|
+
* @param {number} [button=MOUSE_BTN.LEFT] - Button to click
|
|
323
|
+
*/
|
|
324
|
+
async mouseClick(button = MOUSE_BTN.LEFT) {
|
|
325
|
+
await this.sendCommand(CMD.MOUSE_CLICK, [button]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Press a mouse button (and hold)
|
|
330
|
+
* @param {number} [button=MOUSE_BTN.LEFT] - Button to press
|
|
331
|
+
*/
|
|
332
|
+
async mousePress(button = MOUSE_BTN.LEFT) {
|
|
333
|
+
await this.sendCommand(CMD.MOUSE_PRESS, [button]);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Release a mouse button
|
|
338
|
+
* @param {number} [button=MOUSE_BTN.LEFT] - Button to release
|
|
339
|
+
*/
|
|
340
|
+
async mouseRelease(button = MOUSE_BTN.LEFT) {
|
|
341
|
+
await this.sendCommand(CMD.MOUSE_RELEASE, [button]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Scroll the mouse wheel
|
|
346
|
+
* @param {number} amount - Scroll amount (positive = up, negative = down)
|
|
347
|
+
*/
|
|
348
|
+
async mouseScroll(amount) {
|
|
349
|
+
await this.sendCommand(CMD.MOUSE_SCROLL, [amount & 0xFF]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Double-click a mouse button
|
|
354
|
+
* @param {number} [button=MOUSE_BTN.LEFT] - Button to click
|
|
355
|
+
* @param {number} [delay=50] - Delay between clicks (ms)
|
|
356
|
+
*/
|
|
357
|
+
async mouseDoubleClick(button = MOUSE_BTN.LEFT, delay = 50) {
|
|
358
|
+
await this.mouseClick(button);
|
|
359
|
+
await this._sleep(delay);
|
|
360
|
+
await this.mouseClick(button);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ============================================================
|
|
364
|
+
// UTILITY
|
|
365
|
+
// ============================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Execute device-side delay
|
|
369
|
+
* @param {number} ms - Delay in milliseconds
|
|
370
|
+
*/
|
|
371
|
+
async delay(ms) {
|
|
372
|
+
const high = (ms >> 8) & 0xFF;
|
|
373
|
+
const low = ms & 0xFF;
|
|
374
|
+
await this.sendCommand(CMD.DELAY, [high, low]);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Internal sleep helper
|
|
379
|
+
* @private
|
|
380
|
+
*/
|
|
381
|
+
_sleep(ms) {
|
|
382
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
module.exports = MacroController;
|
|
387
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MacroMate HID - Library for communicating with MacroMate device
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const MacroController = require('./controller');
|
|
6
|
+
const {
|
|
7
|
+
VID, PID, VENDOR_USAGE_PAGE,
|
|
8
|
+
CMD, MOUSE_BTN, MOD, KEY, STATUS,
|
|
9
|
+
DEFAULT_TIMEOUT, TYPE_STRING_TIMEOUT
|
|
10
|
+
} = require('./constants');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
// Main class
|
|
14
|
+
MacroController,
|
|
15
|
+
|
|
16
|
+
// Constants
|
|
17
|
+
KEY,
|
|
18
|
+
MOD,
|
|
19
|
+
MOUSE_BTN,
|
|
20
|
+
CMD,
|
|
21
|
+
STATUS,
|
|
22
|
+
|
|
23
|
+
// Device config (for advanced usage)
|
|
24
|
+
VID,
|
|
25
|
+
PID,
|
|
26
|
+
VENDOR_USAGE_PAGE,
|
|
27
|
+
DEFAULT_TIMEOUT,
|
|
28
|
+
TYPE_STRING_TIMEOUT,
|
|
29
|
+
};
|
|
30
|
+
|