-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoonboard-api.js
More file actions
159 lines (142 loc) · 5.14 KB
/
Copy pathgoonboard-api.js
File metadata and controls
159 lines (142 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
export const VID = 0x2B00;
export const PID = 0xB1E5;
export const FETCH_CONFIG_REPORT_ID = 0xC0;
export const NUM_ROWS = 6;
export const NUM_COLS = 16;
export const KEYMAP_SIZE = NUM_ROWS * NUM_COLS;
export const ACTUATION_SIZE = KEYMAP_SIZE;
export const ROTARY_SIZE = 3;
export const THRESHOLD_SIZE = 3;
export const SNAPTAP_SIZE = 6;
export const FULL_CONFIG_SIZE = KEYMAP_SIZE + ACTUATION_SIZE + ROTARY_SIZE + THRESHOLD_SIZE + SNAPTAP_SIZE;
let device = null;
// ---- pacing: serialize calls, enforce 10 ms between them ----
const MIN_API_GAP_MS = 10;
let _queue = Promise.resolve();
function sleep(ms) {
return new Promise(res => setTimeout(res, ms));
}
function pace(run) {
const task = async () => {
const result = await run();
await sleep(MIN_API_GAP_MS); // gap before next queued call
return result;
};
_queue = _queue.then(task, task); // keep queue alive even if a task rejects
return _queue;
}
// ----------------------------------------------------------------
export class KeyboardConfig {
constructor(keymap, actuations, rotary, thresholds, snaptap) {
this.keymap = keymap;
this.actuations = actuations;
this.rotary = rotary;
this.thresholds = thresholds;
this.snaptap = snaptap;
}
static async connectDevice() {
const devices = await navigator.hid.requestDevice({
filters: [{ vendorId: VID, productId: PID }]
});
for (const d of devices) {
const c = d.collections?.[0];
if (c && c.usagePage === 0xFF60 && c.usage === 0x61) {
device = d;
return;
}
}
throw new Error('RawHID interface not found (2B00:B1E5, usagePage 0xFF60, usage 0x61)');
}
static async invalidateDevice() {
device = null;
}
static async fetchFromDevice() {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
const dv = await device.receiveFeatureReport(FETCH_CONFIG_REPORT_ID);
if (device.opened) await device.close();
const data = new Uint8Array(dv.buffer);
if (data.length < FULL_CONFIG_SIZE) {
throw new Error(`Expected ${FULL_CONFIG_SIZE} bytes, got ${data.length}`);
}
const keymap = data.slice(1, KEYMAP_SIZE + 1);
const actuations = data.slice(KEYMAP_SIZE + 1, KEYMAP_SIZE + ACTUATION_SIZE + 1);
const rotary = data.slice(KEYMAP_SIZE + ACTUATION_SIZE + 1, KEYMAP_SIZE + ACTUATION_SIZE + ROTARY_SIZE + 1);
const thresholds = data.slice(
KEYMAP_SIZE + ACTUATION_SIZE + ROTARY_SIZE + 1,
KEYMAP_SIZE + ACTUATION_SIZE + ROTARY_SIZE + THRESHOLD_SIZE + 1
);
const snaptap = data.slice(-SNAPTAP_SIZE);
const ret = new KeyboardConfig(keymap, actuations, rotary, thresholds, snaptap);
return ret;
});
}
static async editKeymap(row, col, code) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xA0, row & 0xff, col & 0xff, code & 0xff])
);
if (device.opened) await device.close();
});
}
static async editActuation(row, col, mm) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xA1, row & 0xff, col & 0xff, mm & 0xff])
);
if (device.opened) await device.close();
});
}
static async editRotary(ctclkw, clkw, pb) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
console.log(device);
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xA2, ctclkw & 0xff, clkw & 0xff, pb & 0xff])
);
if (device.opened) await device.close();
});
}
static async editRapidTrigger(en, th, sc) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xD0, en & 0xff, th & 0xff, sc & 0xff])
);
if (device.opened) await device.close();
});
}
static async editSnapTapA(en, k1, k2) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xB0, en & 0xff, k1 & 0xff, k2 & 0xff])
);
if (device.opened) await device.close();
});
}
static async editSnapTapB(en, k1, k2) {
return pace(async () => {
if (!device) await KeyboardConfig.connectDevice();
if (!device.opened) await device.open();
await device.sendFeatureReport(
0xFE,
new Uint8Array([0xB1, en & 0xff, k1 & 0xff, k2 & 0xff])
);
if (device.opened) await device.close();
});
}
}