react-x11 2.13.0 → 2.14.0

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.
Files changed (63) hide show
  1. package/package.json +11 -8
  2. package/src/Reconciler.js +34 -21
  3. package/src/components/Select.js +8 -2
  4. package/src/events.js +8 -2
  5. package/src/index.d.ts +5 -0
  6. package/src/nodes/boxpaint.js +9 -0
  7. package/src/nodes/preedit.js +64 -14
  8. package/src/scale.js +52 -22
  9. package/src/screencolor.js +104 -17
  10. package/src/wayland/app.js +560 -0
  11. package/src/wayland/backendwindow.js +1123 -0
  12. package/src/wayland/clipboard.js +326 -0
  13. package/src/wayland/connection.js +482 -0
  14. package/src/wayland/context2d.js +2133 -0
  15. package/src/wayland/decorations.js +476 -0
  16. package/src/wayland/dmabuf.js +89 -0
  17. package/src/wayland/dnd.js +581 -0
  18. package/src/wayland/fdutil.js +108 -0
  19. package/src/wayland/framestyle.js +257 -0
  20. package/src/wayland/glarea.js +371 -0
  21. package/src/wayland/glcontext.js +415 -0
  22. package/src/wayland/glyphatlas.js +237 -0
  23. package/src/wayland/input.js +417 -0
  24. package/src/wayland/keysymnames.js +35 -0
  25. package/src/wayland/layershell.js +363 -0
  26. package/src/wayland/outputs.js +601 -0
  27. package/src/wayland/protocols/cursor-shape-v1.json +1 -0
  28. package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
  29. package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
  30. package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
  31. package/src/wayland/protocols/fractional-scale-v1.json +1 -0
  32. package/src/wayland/protocols/index.json +127 -0
  33. package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
  34. package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
  35. package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
  36. package/src/wayland/protocols/presentation-time.json +1 -0
  37. package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
  38. package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
  39. package/src/wayland/protocols/tablet-v2.json +1 -0
  40. package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
  41. package/src/wayland/protocols/viewporter.json +1 -0
  42. package/src/wayland/protocols/wayland.json +1 -0
  43. package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
  44. package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
  45. package/src/wayland/protocols/xdg-activation-v1.json +1 -0
  46. package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
  47. package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
  48. package/src/wayland/protocols/xdg-shell.json +1 -0
  49. package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
  50. package/src/wayland/readback.js +99 -0
  51. package/src/wayland/screencopy.js +584 -0
  52. package/src/wayland/seat.js +584 -0
  53. package/src/wayland/shm.js +226 -0
  54. package/src/wayland/ssd.js +106 -0
  55. package/src/wayland/surface.js +123 -0
  56. package/src/wayland/swapchain.js +411 -0
  57. package/src/wayland/tablet.js +522 -0
  58. package/src/wayland/target.js +263 -0
  59. package/src/wayland/text.js +113 -0
  60. package/src/wayland/textinput.js +671 -0
  61. package/src/wayland/touch.js +284 -0
  62. package/src/wayland/window.js +827 -0
  63. package/src/wayland/xkb.js +425 -0
@@ -0,0 +1,522 @@
1
+ // Graphics tablets: `zwp_tablet_manager_v2`, the seat's tablet seat, and
2
+ // its tools.
3
+ //
4
+ // A pen is not a pointer to Wayland, and almost exactly one to react-x11.
5
+ // The protocol gives every `wl_seat` a *tablet seat* that announces
6
+ // tablets, tools and pads as objects of their own. A tool has a type (pen,
7
+ // eraser, airbrush, the puck the protocol calls a "mouse"), the set of axes
8
+ // it can report, and a life of its own: `proximity_in` names the surface it
9
+ // is over, `motion`/`pressure`/`tilt`/... describe the frame, `down`/`up`
10
+ // say whether it touches the tablet, `button` is a barrel button, and
11
+ // `frame(time)` closes the group. As with `wl_pointer`, the client owns the
12
+ // cursor while the tool is over its surface — through cursor-shape's
13
+ // tablet-tool device, with the same shape names the pointer uses.
14
+ //
15
+ // What this does with it is emulate the pointer, the way X does: on X11 a
16
+ // tablet is an XI2 slave of the master pointer and every control in the
17
+ // tree sees a pen as presses and motion with no idea a pen was involved. So
18
+ // proximity is an `enter`, `down`/`up` are button 1 carrying the down's
19
+ // serial (what `xdg_toplevel.move` wants when the pen lands on the
20
+ // titlebar), the barrel buttons are 3 and 2 — BTN_STYLUS and BTN_STYLUS2,
21
+ // the desktop's own mapping — a puck's buttons are themselves, and
22
+ // `proximity_out` releases anything still held before it leaves. The
23
+ // emulated events are the seat's own pointer events and take the same
24
+ // route through `input.js`; the axes ride along on them in the DOM's names
25
+ // and ranges — `pointerType`, `pressure`, `tiltX`/`tiltY`, `rotation`,
26
+ // `distance`, `tangentialPressure` — on these events only, so a mouse event
27
+ // is unchanged and the X11 backend never sees a field it did not send.
28
+ //
29
+ // Everything a frame reports is accumulated and emitted at `frame`, in the
30
+ // order a pointer would have produced it: enter, motion, presses, releases,
31
+ // leave. Pads (the buttons, rings and strips on the tablet itself) are
32
+ // accepted and left alone: the tree has no vocabulary for them.
33
+
34
+ import { BUTTON_NUMBER, BUTTON_MASK, CURSOR_SHAPES, PRESSED } from './seat.js';
35
+ import { BTN_TOUCH } from './touch.js';
36
+
37
+ /** `zwp_tablet_tool_v2.type`. */
38
+ export const TOOL_TYPE = {
39
+ PEN: 0x140,
40
+ ERASER: 0x141,
41
+ BRUSH: 0x142,
42
+ PENCIL: 0x143,
43
+ AIRBRUSH: 0x144,
44
+ FINGER: 0x145,
45
+ MOUSE: 0x146,
46
+ LENS: 0x147,
47
+ };
48
+
49
+ const TOOL_NAME = {
50
+ [TOOL_TYPE.PEN]: 'pen',
51
+ [TOOL_TYPE.ERASER]: 'eraser',
52
+ [TOOL_TYPE.BRUSH]: 'brush',
53
+ [TOOL_TYPE.PENCIL]: 'pencil',
54
+ [TOOL_TYPE.AIRBRUSH]: 'airbrush',
55
+ [TOOL_TYPE.FINGER]: 'finger',
56
+ [TOOL_TYPE.MOUSE]: 'mouse',
57
+ [TOOL_TYPE.LENS]: 'lens',
58
+ };
59
+
60
+ /** The DOM `pointerType` a tool type stands for. */
61
+ const POINTER_TYPE = {
62
+ pen: 'pen',
63
+ eraser: 'eraser',
64
+ brush: 'pen',
65
+ pencil: 'pen',
66
+ airbrush: 'pen',
67
+ finger: 'touch',
68
+ mouse: 'mouse',
69
+ lens: 'mouse',
70
+ };
71
+
72
+ /** `zwp_tablet_tool_v2.capability`. */
73
+ export const TOOL_CAP = {
74
+ TILT: 1,
75
+ PRESSURE: 2,
76
+ DISTANCE: 3,
77
+ ROTATION: 4,
78
+ SLIDER: 5,
79
+ WHEEL: 6,
80
+ };
81
+
82
+ const CAP_NAME = {
83
+ [TOOL_CAP.TILT]: 'tilt',
84
+ [TOOL_CAP.PRESSURE]: 'pressure',
85
+ [TOOL_CAP.DISTANCE]: 'distance',
86
+ [TOOL_CAP.ROTATION]: 'rotation',
87
+ [TOOL_CAP.SLIDER]: 'slider',
88
+ [TOOL_CAP.WHEEL]: 'wheel',
89
+ };
90
+
91
+ /** The stylus buttons, as evdev numbers them. */
92
+ export const BTN_STYLUS = 0x14b;
93
+ export const BTN_STYLUS2 = 0x14c;
94
+ export const BTN_STYLUS3 = 0x149;
95
+
96
+ /**
97
+ * Tool button -> the button number the tree speaks: the barrel buttons as
98
+ * the desktop maps them (side button is a right click, the second a middle
99
+ * click, a third the "back" button), a puck's as themselves.
100
+ */
101
+ const TOOL_BUTTON_NUMBER = {
102
+ ...BUTTON_NUMBER,
103
+ [BTN_STYLUS]: 3,
104
+ [BTN_STYLUS2]: 2,
105
+ [BTN_STYLUS3]: 8,
106
+ };
107
+
108
+ /** pressure, distance and the slider are normalised to this by the protocol */
109
+ const AXIS_MAX = 65535;
110
+
111
+ export class WaylandTablet {
112
+ /**
113
+ * Bind the manager and open the seat's tablet seat, or answer null where
114
+ * the compositor has no tablet protocol — which is a missing capability,
115
+ * not an error.
116
+ *
117
+ * @param {import('./connection.js').WaylandConnection} conn
118
+ * @param {import('./seat.js').WaylandSeat} seat
119
+ */
120
+ static async bind(conn, seat) {
121
+ const manager = await conn.bind('zwp_tablet_manager_v2');
122
+ if (!manager) return null;
123
+ const tablet = new WaylandTablet(seat, manager);
124
+ seat.tablet = tablet;
125
+ return tablet;
126
+ }
127
+
128
+ constructor(seat, manager) {
129
+ this.seat = seat;
130
+ this.manager = manager;
131
+ this.tabletSeat = manager.$.get_tablet_seat(seat.seat.id);
132
+ /** tablets by proxy id: `{ id, name, vid, pid, path, bustype }` */
133
+ this.tablets = new Map();
134
+ /** tools by proxy id, once their description is complete */
135
+ this.tools = new Map();
136
+ this._pads = new Set();
137
+ // the seat's cursor is the cursor of every pointing device: a tool in
138
+ // proximity follows what the tree (or the frame) last asked for
139
+ this._onCursor = (name) => {
140
+ for (const tool of this.tools.values()) tool.applyCursor(name);
141
+ };
142
+ seat.on('cursor', this._onCursor);
143
+ this._wire();
144
+ }
145
+
146
+ _wire() {
147
+ const ts = this.tabletSeat;
148
+ ts.on('tablet_added', (proxy) => {
149
+ const info = {
150
+ id: proxy.id,
151
+ name: null,
152
+ vid: 0,
153
+ pid: 0,
154
+ path: null,
155
+ bustype: null,
156
+ };
157
+ proxy.on('name', (name) => (info.name = name));
158
+ proxy.on('id', (vid, pid) => Object.assign(info, { vid, pid }));
159
+ proxy.on('path', (path) => (info.path = path));
160
+ proxy.on('bustype', (bustype) => (info.bustype = bustype));
161
+ proxy.on('done', () => {
162
+ this.tablets.set(proxy.id, info);
163
+ this.seat.emit('tabletadded', info);
164
+ });
165
+ proxy.on('removed', () => {
166
+ this.tablets.delete(proxy.id);
167
+ proxy.$.destroy();
168
+ this.seat.emit('tabletremoved', info);
169
+ });
170
+ });
171
+ ts.on('tool_added', (proxy) => new TabletTool(this, proxy));
172
+ ts.on('pad_added', (proxy) => {
173
+ this._pads.add(proxy);
174
+ proxy.on('removed', () => {
175
+ this._pads.delete(proxy);
176
+ proxy.$.destroy();
177
+ });
178
+ });
179
+ }
180
+
181
+ destroy() {
182
+ this.seat.off('cursor', this._onCursor);
183
+ for (const tool of [...this.tools.values()]) tool._destroy();
184
+ this.tools.clear();
185
+ this.tablets.clear();
186
+ this._pads.clear();
187
+ this.tabletSeat.removeAllListeners();
188
+ try {
189
+ this.tabletSeat.$.destroy();
190
+ } catch {
191
+ /* the connection is going */
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * One tool, from `tool_added` to `removed`. Its description (`type`,
198
+ * `capability`, the hardware ids) arrives first and is complete at `done`;
199
+ * only then is it listed on the tablet seat and announced as `tooladded`.
200
+ */
201
+ export class TabletTool {
202
+ constructor(tablet, proxy) {
203
+ this.tablet = tablet;
204
+ this.seat = tablet.seat;
205
+ this.proxy = proxy;
206
+ this.id = proxy.id;
207
+ this.type = TOOL_TYPE.PEN;
208
+ /** 'pen' | 'eraser' | 'brush' | 'pencil' | 'airbrush' | 'finger' | 'mouse' | 'lens' */
209
+ this.typeName = 'pen';
210
+ /** the DOM `pointerType` the emulated events carry */
211
+ this.pointerType = 'pen';
212
+ /** axis names the tool reports: 'pressure', 'tilt', ... */
213
+ this.capabilities = new Set();
214
+ this.hardwareSerial = null;
215
+ this.wacomId = null;
216
+ /** the `wl_surface` id the tool is over, or null when out of proximity */
217
+ this.surface = null;
218
+ this.tabletId = null;
219
+ /** the `proximity_in` serial — what `set_cursor` for this tool needs */
220
+ this.proximitySerial = 0;
221
+ this.x = 0;
222
+ this.y = 0;
223
+ this.pressure = 0;
224
+ this.distance = 0;
225
+ this.tiltX = 0;
226
+ this.tiltY = 0;
227
+ this.rotation = 0;
228
+ this.slider = 0;
229
+ /** the tool touches the tablet */
230
+ this.down = false;
231
+ /** buttons held, in press order: `{ num, evdev }` */
232
+ this.held = [];
233
+ this.cursorDevice = this.seat._cursorShapes
234
+ ? this.seat._cursorShapes.$.get_tablet_tool_v2(proxy.id)
235
+ : null;
236
+ this._frame = null;
237
+ this._wire();
238
+ }
239
+
240
+ _wire() {
241
+ const p = this.proxy;
242
+ p.on('type', (type) => {
243
+ this.type = type;
244
+ this.typeName = TOOL_NAME[type] ?? 'pen';
245
+ this.pointerType = POINTER_TYPE[this.typeName] ?? 'pen';
246
+ });
247
+ p.on('hardware_serial', (hi, lo) => (this.hardwareSerial = hex64(hi, lo)));
248
+ p.on('hardware_id_wacom', (hi, lo) => (this.wacomId = hex64(hi, lo)));
249
+ p.on('capability', (cap) => this.capabilities.add(CAP_NAME[cap] ?? cap));
250
+ p.on('done', () => {
251
+ this.tablet.tools.set(this.id, this);
252
+ this.seat.emit('tooladded', this);
253
+ });
254
+ p.on('removed', () => this._remove());
255
+
256
+ // The frame's events, accumulated; `frame` turns them into pointer events.
257
+ p.on('proximity_in', (serial, tabletId, surfaceId) => {
258
+ const f = this._open();
259
+ f.proximityIn = serial;
260
+ f.tablet = tabletId;
261
+ f.surface = surfaceId;
262
+ });
263
+ p.on('proximity_out', () => (this._open().proximityOut = true));
264
+ p.on('down', (serial) => (this._open().down = serial));
265
+ p.on('up', () => (this._open().up = true));
266
+ p.on('motion', (x, y) => {
267
+ this.x = x;
268
+ this.y = y;
269
+ this._open().moved = true;
270
+ });
271
+ p.on('pressure', (v) => this._axis('pressure', v / AXIS_MAX));
272
+ p.on('distance', (v) => this._axis('distance', v / AXIS_MAX));
273
+ p.on('tilt', (tx, ty) => {
274
+ this._axis('tiltX', tx);
275
+ this._axis('tiltY', ty);
276
+ });
277
+ p.on('rotation', (degrees) => this._axis('rotation', degrees));
278
+ p.on('slider', (position) => this._axis('slider', position / AXIS_MAX));
279
+ p.on('wheel', (degrees, clicks) => {
280
+ const f = this._open();
281
+ f.wheel = {
282
+ degrees: (f.wheel?.degrees ?? 0) + degrees,
283
+ clicks: (f.wheel?.clicks ?? 0) + clicks,
284
+ };
285
+ });
286
+ p.on('button', (serial, button, state) =>
287
+ this._open().buttons.push({ serial, button, state }),
288
+ );
289
+ p.on('frame', (time) => this._flush(time));
290
+ }
291
+
292
+ _open() {
293
+ return (this._frame ??= {
294
+ proximityIn: null,
295
+ proximityOut: false,
296
+ down: null,
297
+ up: false,
298
+ moved: false,
299
+ axes: false,
300
+ buttons: [],
301
+ wheel: null,
302
+ });
303
+ }
304
+
305
+ _axis(name, value) {
306
+ this[name] = value;
307
+ this._open().axes = true;
308
+ }
309
+
310
+ /** The axes as the emulated events carry them. */
311
+ _device() {
312
+ return {
313
+ pointerType: this.pointerType,
314
+ toolType: this.typeName,
315
+ // the DOM's answer for a tool without a pressure axis: half while it
316
+ // is in contact, none otherwise
317
+ pressure: this.capabilities.has('pressure')
318
+ ? this.pressure
319
+ : this.held.length
320
+ ? 0.5
321
+ : 0,
322
+ tiltX: this.tiltX,
323
+ tiltY: this.tiltY,
324
+ rotation: this.rotation,
325
+ distance: this.distance,
326
+ tangentialPressure: this.slider,
327
+ };
328
+ }
329
+
330
+ /**
331
+ * The frame is closed: emit what it described, as the pointer events the
332
+ * router already routes, in the order a pointer would have sent them.
333
+ */
334
+ _flush(time) {
335
+ const f = this._frame;
336
+ this._frame = null;
337
+ if (!f) return;
338
+ const seat = this.seat;
339
+ const batch = [];
340
+ if (f.proximityIn != null) {
341
+ seat.lastSerial = f.proximityIn;
342
+ this.surface = f.surface;
343
+ this.tabletId = f.tablet;
344
+ this.proximitySerial = f.proximityIn;
345
+ batch.push({
346
+ type: 'enter',
347
+ serial: f.proximityIn,
348
+ surface: this.surface,
349
+ x: this.x,
350
+ y: this.y,
351
+ device: this._device(),
352
+ });
353
+ this.applyCursor(seat._cursor);
354
+ }
355
+ // nothing to say about a tool that is not over one of our surfaces
356
+ if (this.surface == null) return;
357
+ if (f.moved || f.axes) {
358
+ batch.push({
359
+ type: 'motion',
360
+ time,
361
+ x: this.x,
362
+ y: this.y,
363
+ surface: this.surface,
364
+ device: this._device(),
365
+ });
366
+ }
367
+ if (f.down != null) {
368
+ seat.lastSerial = f.down;
369
+ seat.lastPressSerial = f.down;
370
+ this.down = true;
371
+ this._press(batch, 1, BTN_TOUCH, f.down, time);
372
+ }
373
+ for (const b of f.buttons) {
374
+ seat.lastSerial = b.serial;
375
+ const num = TOOL_BUTTON_NUMBER[b.button] ?? 0;
376
+ if (!num) continue;
377
+ if (b.state === PRESSED) {
378
+ seat.lastPressSerial = b.serial;
379
+ this._press(batch, num, b.button, b.serial, time);
380
+ } else {
381
+ this._release(batch, num, b.serial, time);
382
+ }
383
+ }
384
+ if (f.up) {
385
+ this.down = false;
386
+ this._release(batch, 1, seat.lastSerial, time);
387
+ }
388
+ if (f.wheel) {
389
+ batch.push({
390
+ type: 'wheel',
391
+ time,
392
+ deltaX: 0,
393
+ deltaY: f.wheel.clicks,
394
+ pixelsX: 0,
395
+ pixelsY: f.wheel.degrees,
396
+ smooth: false,
397
+ x: this.x,
398
+ y: this.y,
399
+ surface: this.surface,
400
+ device: this._device(),
401
+ });
402
+ }
403
+ if (f.proximityOut) this._leave(batch, time);
404
+ this._emit(batch);
405
+ }
406
+
407
+ _press(batch, num, evdev, serial, time) {
408
+ if (this.held.some((h) => h.num === num)) return;
409
+ const seat = this.seat;
410
+ const before = seat.buttonMask;
411
+ seat.buttonMask |= BUTTON_MASK[num] ?? 0;
412
+ this.held.push({ num, evdev });
413
+ batch.push(this._button('buttonpress', num, evdev, serial, time, before));
414
+ }
415
+
416
+ _release(batch, num, serial, time) {
417
+ const i = this.held.findIndex((h) => h.num === num);
418
+ if (i < 0) return;
419
+ const seat = this.seat;
420
+ const before = seat.buttonMask;
421
+ seat.buttonMask &= ~(BUTTON_MASK[num] ?? 0);
422
+ const [{ evdev }] = this.held.splice(i, 1);
423
+ batch.push(this._button('buttonrelease', num, evdev, serial, time, before));
424
+ }
425
+
426
+ _button(type, num, evdev, serial, time, state) {
427
+ return {
428
+ type,
429
+ serial,
430
+ time,
431
+ button: num,
432
+ evdev,
433
+ x: this.x,
434
+ y: this.y,
435
+ surface: this.surface,
436
+ state,
437
+ device: this._device(),
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Out of proximity: whatever is still held is released first, newest
443
+ * first, then the pointer leaves — and if a real pointer is over one of
444
+ * our surfaces it is the pointer again, so the router's
445
+ * window-under-the-pointer is right.
446
+ */
447
+ _leave(batch, time) {
448
+ const seat = this.seat;
449
+ for (const { num } of [...this.held].reverse())
450
+ this._release(batch, num, seat.lastSerial, time);
451
+ this.down = false;
452
+ batch.push({
453
+ type: 'leave',
454
+ serial: seat.lastSerial,
455
+ surface: this.surface,
456
+ device: this._device(),
457
+ });
458
+ this.surface = null;
459
+ this.tabletId = null;
460
+ this.proximitySerial = 0;
461
+ if (seat.pointerSurface != null) {
462
+ batch.push({
463
+ type: 'enter',
464
+ serial: seat.lastSerial,
465
+ surface: seat.pointerSurface,
466
+ x: seat.pointerX,
467
+ y: seat.pointerY,
468
+ });
469
+ }
470
+ }
471
+
472
+ _emit(batch) {
473
+ if (batch.length === 0) return;
474
+ for (const ev of batch) this.seat.emit(ev.type, ev);
475
+ this.seat.emit('frame', batch);
476
+ }
477
+
478
+ /**
479
+ * The cursor over the surface the tool is in, by the same names the
480
+ * pointer uses. Without cursor-shape there is no way to name one, as for
481
+ * the pointer; `'none'` hides it with a null surface.
482
+ */
483
+ applyCursor(name) {
484
+ if (this.surface == null) return;
485
+ if (name === 'none') {
486
+ this.proxy.$.set_cursor(this.proximitySerial, null, 0, 0);
487
+ return;
488
+ }
489
+ if (!this.cursorDevice) return;
490
+ const shape = CURSOR_SHAPES[name] ?? CURSOR_SHAPES.default;
491
+ this.cursorDevice.$.set_shape(this.proximitySerial, shape);
492
+ }
493
+
494
+ /** The tool is gone (unplugged mid-stroke, say): finish what it held. */
495
+ _remove() {
496
+ if (this.surface != null) {
497
+ const batch = [];
498
+ this._leave(batch, this.seat.lastSerial);
499
+ this._emit(batch);
500
+ }
501
+ const known = this.tablet.tools.delete(this.id);
502
+ this._destroy();
503
+ if (known) this.seat.emit('toolremoved', this);
504
+ }
505
+
506
+ _destroy() {
507
+ this.proxy.removeAllListeners();
508
+ try {
509
+ this.cursorDevice?.$.destroy();
510
+ this.proxy.$.destroy();
511
+ } catch {
512
+ /* the connection is going */
513
+ }
514
+ }
515
+ }
516
+
517
+ /** Two 32-bit halves as one hex string, which is how the ids are read. */
518
+ function hex64(hi, lo) {
519
+ return hi
520
+ ? hi.toString(16) + lo.toString(16).padStart(8, '0')
521
+ : lo.toString(16);
522
+ }