ai-remote 0.4.2 → 0.4.4

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 (75) hide show
  1. package/dist/index.js +1 -6
  2. package/dist/{protocols/index.d.ts → protocols.d.ts} +2 -0
  3. package/dist/protocols.js +24 -0
  4. package/package.json +15 -10
  5. package/dist/protocols/index.js +0 -34
  6. package/dist/protocols/rdp/buffer.d.ts +0 -38
  7. package/dist/protocols/rdp/buffer.js +0 -169
  8. package/dist/protocols/rdp/caps.d.ts +0 -49
  9. package/dist/protocols/rdp/caps.js +0 -259
  10. package/dist/protocols/rdp/cert.d.ts +0 -18
  11. package/dist/protocols/rdp/cert.js +0 -134
  12. package/dist/protocols/rdp/client.d.ts +0 -56
  13. package/dist/protocols/rdp/client.js +0 -1069
  14. package/dist/protocols/rdp/cliprdr.d.ts +0 -27
  15. package/dist/protocols/rdp/cliprdr.js +0 -239
  16. package/dist/protocols/rdp/credssp.d.ts +0 -34
  17. package/dist/protocols/rdp/credssp.js +0 -253
  18. package/dist/protocols/rdp/crypto.d.ts +0 -63
  19. package/dist/protocols/rdp/crypto.js +0 -368
  20. package/dist/protocols/rdp/display.d.ts +0 -38
  21. package/dist/protocols/rdp/display.js +0 -588
  22. package/dist/protocols/rdp/gcc.d.ts +0 -23
  23. package/dist/protocols/rdp/gcc.js +0 -131
  24. package/dist/protocols/rdp/keymap.d.ts +0 -9
  25. package/dist/protocols/rdp/keymap.js +0 -131
  26. package/dist/protocols/rdp/mcs.d.ts +0 -40
  27. package/dist/protocols/rdp/mcs.js +0 -222
  28. package/dist/protocols/rdp/ntlm.d.ts +0 -61
  29. package/dist/protocols/rdp/ntlm.js +0 -304
  30. package/dist/protocols/rdp/pdu.d.ts +0 -155
  31. package/dist/protocols/rdp/pdu.js +0 -443
  32. package/dist/protocols/rdp/rail.d.ts +0 -119
  33. package/dist/protocols/rdp/rail.js +0 -382
  34. package/dist/protocols/rdp/rle.d.ts +0 -13
  35. package/dist/protocols/rdp/rle.js +0 -275
  36. package/dist/protocols/rdp/sec.d.ts +0 -59
  37. package/dist/protocols/rdp/sec.js +0 -155
  38. package/dist/protocols/rdp/session.d.ts +0 -62
  39. package/dist/protocols/rdp/session.js +0 -306
  40. package/dist/protocols/rdp/tls.d.ts +0 -18
  41. package/dist/protocols/rdp/tls.js +0 -353
  42. package/dist/protocols/rdp/vchannel.d.ts +0 -28
  43. package/dist/protocols/rdp/vchannel.js +0 -58
  44. package/dist/protocols/rdp/x224.d.ts +0 -40
  45. package/dist/protocols/rdp/x224.js +0 -109
  46. package/dist/protocols/ssh/kex.d.ts +0 -97
  47. package/dist/protocols/ssh/kex.js +0 -176
  48. package/dist/protocols/ssh/messages.d.ts +0 -50
  49. package/dist/protocols/ssh/messages.js +0 -54
  50. package/dist/protocols/ssh/session.d.ts +0 -66
  51. package/dist/protocols/ssh/session.js +0 -608
  52. package/dist/protocols/ssh/terminal.d.ts +0 -46
  53. package/dist/protocols/ssh/terminal.js +0 -190
  54. package/dist/protocols/ssh/transport.d.ts +0 -62
  55. package/dist/protocols/ssh/transport.js +0 -612
  56. package/dist/protocols/ssh/wire.d.ts +0 -52
  57. package/dist/protocols/ssh/wire.js +0 -188
  58. package/dist/protocols/types.d.ts +0 -127
  59. package/dist/protocols/types.js +0 -0
  60. package/dist/protocols/vnc/input.d.ts +0 -5
  61. package/dist/protocols/vnc/input.js +0 -15
  62. package/dist/protocols/vnc/session.d.ts +0 -12
  63. package/dist/protocols/vnc/session.js +0 -258
  64. package/dist/shared/connection.d.ts +0 -81
  65. package/dist/shared/connection.js +0 -113
  66. package/dist/shared/device.d.ts +0 -26
  67. package/dist/shared/device.js +0 -0
  68. package/dist/shared/hosts.d.ts +0 -63
  69. package/dist/shared/hosts.js +0 -132
  70. package/dist/shared/icons.d.ts +0 -46
  71. package/dist/shared/icons.js +0 -41
  72. package/dist/shared/protocol.d.ts +0 -28
  73. package/dist/shared/protocol.js +0 -39
  74. package/dist/shared/signals.d.ts +0 -37
  75. package/dist/shared/signals.js +0 -41
@@ -1,588 +0,0 @@
1
- import { decodeBitmapRect } from "./rle.js";
2
- import { TOGGLE_FLAG } from "./pdu.js";
3
- const IS_MAC = /Mac|iPod|iPhone|iPad/.test(
4
- globalThis.navigator?.platform || globalThis.navigator?.userAgent || ""
5
- );
6
- const COMMAND_TO_CONTROL = {
7
- MetaLeft: "ControlLeft",
8
- MetaRight: "ControlRight"
9
- };
10
- const WHEEL_STEP_PIXELS = 60;
11
- const WHEEL_LINE_PIXELS = 16;
12
- const TOUCH_SCROLL_STEP = 20;
13
- const MAX_ZOOM = 8;
14
- const MIN_ZOOM = 1;
15
- class RdpDisplay {
16
- constructor(container) {
17
- this.container = container;
18
- this.canvas = document.createElement("canvas");
19
- this.canvas.tabIndex = 0;
20
- this.canvas.style.display = "block";
21
- this.canvas.style.position = "absolute";
22
- this.canvas.style.outline = "none";
23
- this.canvas.style.imageRendering = "auto";
24
- this.canvas.style.boxShadow = "none";
25
- this.context = this.canvas.getContext("2d", { alpha: false });
26
- this.viewportElement = document.createElement("div");
27
- this.viewportElement.style.position = "relative";
28
- this.viewportElement.style.overflow = "hidden";
29
- this.viewportElement.style.margin = "auto";
30
- this.viewportElement.style.background = "#000";
31
- this.viewportElement.style.boxShadow = "0 10px 30px rgba(0, 0, 0, 0.8)";
32
- this.viewportElement.appendChild(this.canvas);
33
- container.replaceChildren(this.viewportElement);
34
- this.scrollHost = container.parentElement || container;
35
- this.width = 0;
36
- this.height = 0;
37
- this.viewport = { x: 0, y: 0, width: 0, height: 0 };
38
- this.scale = 1;
39
- this.scaleViewport = true;
40
- this.zoom = 1;
41
- this.userZoomed = false;
42
- this.viewOnly = false;
43
- this.client = null;
44
- this.palette = null;
45
- this.cursorCache = /* @__PURE__ */ new Map();
46
- this.heldKeys = /* @__PURE__ */ new Set();
47
- this.keyboardSynced = false;
48
- this.onWindowResize = () => this.applyScale();
49
- window.addEventListener("resize", this.onWindowResize);
50
- this.resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => this.applyScale()) : null;
51
- this.resizeObserver?.observe(this.scrollHost);
52
- }
53
- resize(width, height) {
54
- this.width = width;
55
- this.height = height;
56
- this.canvas.width = width;
57
- this.canvas.height = height;
58
- this.context.fillStyle = "#000";
59
- this.context.fillRect(0, 0, width, height);
60
- this.setViewport({ x: 0, y: 0, width, height });
61
- }
62
- /**
63
- * Choose the part of the desktop that is on screen. Everything else about
64
- * the session -- painting, input coordinates, the remote pointer -- keeps
65
- * working in desktop coordinates, so only the framing changes here.
66
- */
67
- setViewport({ x = 0, y = 0, width, height } = {}) {
68
- if (!this.width || !this.height) return;
69
- const left = Math.max(0, Math.min(this.width - 1, Math.floor(x)));
70
- const top = Math.max(0, Math.min(this.height - 1, Math.floor(y)));
71
- this.viewport = {
72
- x: left,
73
- y: top,
74
- width: Math.max(1, Math.min(this.width - left, Math.floor(width || this.width))),
75
- height: Math.max(1, Math.min(this.height - top, Math.floor(height || this.height)))
76
- };
77
- this.canvas.dataset.viewport = [
78
- this.viewport.x,
79
- this.viewport.y,
80
- this.viewport.width,
81
- this.viewport.height
82
- ].join(",");
83
- this.applyScale();
84
- }
85
- /** The space a fitted desktop has to fit into: the window, not the content. */
86
- #available() {
87
- const host = this.scrollHost;
88
- return {
89
- width: host.clientWidth || this.container.clientWidth || 1,
90
- height: host.clientHeight || this.container.clientHeight || 1
91
- };
92
- }
93
- /** The scale at which the whole desktop is on screen, letterboxed. */
94
- #fitScale(available = this.#available()) {
95
- const fitted = Math.min(
96
- available.width / this.viewport.width,
97
- available.height / this.viewport.height
98
- );
99
- return Number.isFinite(fitted) && fitted > 0 ? fitted : 1;
100
- }
101
- /**
102
- * The zoom to open a session at.
103
- *
104
- * On a laptop the fitted view is the right one: the whole desktop is on
105
- * screen and readable, and that is where this stays. On a phone it is not --
106
- * a 1024x768 desktop fitted to a 375pt-wide window lands at about a third of
107
- * full size, a band of unreadable pixels across the middle of a tall black
108
- * screen. When fitting would land under LEGIBLE_SCALE the desktop is scaled
109
- * to fill the window instead: the short edge matches exactly and the long one
110
- * is panned. Everything is still reachable, and a pinch still gets back to
111
- * the whole desktop. Legible first and complete second is the right way round
112
- * on a screen this size.
113
- */
114
- #defaultZoom() {
115
- return 1;
116
- }
117
- applyScale() {
118
- if (!this.width || !this.height) return;
119
- const viewport = this.viewport;
120
- const available = this.#available();
121
- const fitted = this.#fitScale(available);
122
- if (!this.userZoomed) this.zoom = this.#defaultZoom();
123
- const scale = (this.scaleViewport ? fitted : 1) * this.zoom;
124
- this.scale = scale;
125
- this.viewportElement.style.width = `${viewport.width * scale}px`;
126
- this.viewportElement.style.height = `${viewport.height * scale}px`;
127
- this.canvas.style.width = `${this.width * scale}px`;
128
- this.canvas.style.height = `${this.height * scale}px`;
129
- this.canvas.style.left = `${-viewport.x * scale}px`;
130
- this.canvas.style.top = `${-viewport.y * scale}px`;
131
- }
132
- /**
133
- * Zoom about a point, keeping whatever is under it in place. `focal` is in
134
- * client coordinates -- the midpoint between two fingers, usually.
135
- */
136
- setZoom(next, focal = null) {
137
- const clamped = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, next));
138
- if (!Number.isFinite(clamped) || clamped === this.zoom) return;
139
- const host = this.scrollHost;
140
- const before = this.scale;
141
- const bounds = host.getBoundingClientRect();
142
- const anchorX = focal ? focal.x - bounds.left : bounds.width / 2;
143
- const anchorY = focal ? focal.y - bounds.top : bounds.height / 2;
144
- const contentX = host.scrollLeft + anchorX;
145
- const contentY = host.scrollTop + anchorY;
146
- this.zoom = clamped;
147
- this.applyScale();
148
- const ratio = before > 0 ? this.scale / before : 1;
149
- host.scrollLeft = contentX * ratio - anchorX;
150
- host.scrollTop = contentY * ratio - anchorY;
151
- }
152
- /** Move the visible window over the desktop. Returns what it actually used. */
153
- panBy(dx, dy) {
154
- const host = this.scrollHost;
155
- const beforeX = host.scrollLeft;
156
- const beforeY = host.scrollTop;
157
- host.scrollLeft = beforeX - dx;
158
- host.scrollTop = beforeY - dy;
159
- return {
160
- x: beforeX - host.scrollLeft,
161
- y: beforeY - host.scrollTop
162
- };
163
- }
164
- /** True when the desktop is larger than the window on either axis. */
165
- canPan() {
166
- if (this.zoom <= 1.05) return false;
167
- const host = this.scrollHost;
168
- return host.scrollWidth > host.clientWidth + 2 || host.scrollHeight > host.clientHeight + 2;
169
- }
170
- setScaleViewport(enabled) {
171
- this.scaleViewport = enabled;
172
- if (!enabled) this.userZoomed = false;
173
- this.applyScale();
174
- }
175
- /** Back to the opening view: whole desktop fitted on screen. */
176
- resetZoom() {
177
- this.userZoomed = false;
178
- this.zoom = 1;
179
- this.applyScale();
180
- const host = this.scrollHost;
181
- host.scrollLeft = 0;
182
- host.scrollTop = 0;
183
- }
184
- drawRects(rects, palette) {
185
- if (palette) this.palette = palette;
186
- for (const rect of rects) {
187
- if (rect.width === 0 || rect.height === 0) continue;
188
- try {
189
- const rgba = decodeBitmapRect(rect, this.palette);
190
- const image = new ImageData(rgba, rect.width, rect.height);
191
- this.context.putImageData(image, rect.left, rect.top);
192
- } catch (error) {
193
- console.warn("[RDP] Dropped an undecodable bitmap rectangle", {
194
- geometry: `${rect.width}x${rect.height}+${rect.left}+${rect.top}`,
195
- bitsPerPixel: rect.bitsPerPixel,
196
- compressed: rect.compressed,
197
- error: error instanceof Error ? error.message : String(error)
198
- });
199
- }
200
- }
201
- }
202
- // --- cursor ---------------------------------------------------------------
203
- /**
204
- * Turn an RDP pointer bitmap into a CSS cursor. RDP stores both masks
205
- * bottom-up with rows padded to an even number of bytes, and a set AND-mask
206
- * bit means "leave this pixel alone", which becomes transparency here.
207
- */
208
- #cursorFromPointer(pointer) {
209
- const { width, height, xorBpp, xorMask, andMask, hotSpotX, hotSpotY } = pointer;
210
- if (!width || !height) return null;
211
- const xorBytesPerRow = Math.ceil(width * xorBpp / 8 / 2) * 2;
212
- const andBytesPerRow = Math.ceil(width / 8 / 2) * 2;
213
- const rgba = new Uint8ClampedArray(width * height * 4);
214
- for (let y = 0; y < height; y++) {
215
- const sourceRow = height - 1 - y;
216
- for (let x = 0; x < width; x++) {
217
- const andByte = andMask[sourceRow * andBytesPerRow + (x >> 3)];
218
- const transparent = andByte !== void 0 && (andByte & 128 >> (x & 7)) !== 0;
219
- let r = 0;
220
- let g = 0;
221
- let b = 0;
222
- let a = transparent ? 0 : 255;
223
- if (xorBpp === 1) {
224
- const bit = xorMask[sourceRow * xorBytesPerRow + (x >> 3)] & 128 >> (x & 7);
225
- r = g = b = bit ? 255 : 0;
226
- } else {
227
- const bytesPerPixel = xorBpp / 8;
228
- const offset = sourceRow * xorBytesPerRow + x * bytesPerPixel;
229
- if (xorBpp === 16) {
230
- const value = xorMask[offset] | xorMask[offset + 1] << 8;
231
- r = (value >> 11 & 31) * 255 / 31;
232
- g = (value >> 5 & 63) * 255 / 63;
233
- b = (value & 31) * 255 / 31;
234
- } else {
235
- b = xorMask[offset] || 0;
236
- g = xorMask[offset + 1] || 0;
237
- r = xorMask[offset + 2] || 0;
238
- if (xorBpp === 32 && xorMask[offset + 3] !== void 0 && andMask.length === 0) {
239
- a = xorMask[offset + 3];
240
- }
241
- }
242
- }
243
- const target = (y * width + x) * 4;
244
- rgba[target] = r;
245
- rgba[target + 1] = g;
246
- rgba[target + 2] = b;
247
- rgba[target + 3] = a;
248
- }
249
- }
250
- const cursorCanvas = document.createElement("canvas");
251
- cursorCanvas.width = width;
252
- cursorCanvas.height = height;
253
- cursorCanvas.getContext("2d").putImageData(new ImageData(rgba, width, height), 0, 0);
254
- const hotX = Math.min(hotSpotX || 0, width - 1);
255
- const hotY = Math.min(hotSpotY || 0, height - 1);
256
- return `url(${cursorCanvas.toDataURL("image/png")}) ${hotX} ${hotY}, default`;
257
- }
258
- applyPointer(pointer) {
259
- if (pointer.kind === "system") {
260
- this.canvas.style.cursor = pointer.hidden ? "none" : "default";
261
- return;
262
- }
263
- if (pointer.kind === "position") {
264
- return;
265
- }
266
- if (pointer.kind === "cached") {
267
- const cached = this.cursorCache.get(pointer.cacheIndex);
268
- if (cached) this.canvas.style.cursor = cached;
269
- return;
270
- }
271
- if (pointer.kind === "bitmap") {
272
- try {
273
- const cursor = this.#cursorFromPointer(pointer);
274
- if (!cursor) return;
275
- this.cursorCache.set(pointer.cacheIndex, cursor);
276
- this.canvas.style.cursor = cursor;
277
- } catch (error) {
278
- console.warn("[RDP] Could not build a cursor from the host pointer update", error);
279
- }
280
- }
281
- }
282
- // --- input ----------------------------------------------------------------
283
- #toRemote(event) {
284
- const bounds = this.canvas.getBoundingClientRect();
285
- const scaleX = this.width / (bounds.width || 1);
286
- const scaleY = this.height / (bounds.height || 1);
287
- return {
288
- x: Math.max(0, Math.min(this.width - 1, (event.clientX - bounds.left) * scaleX)),
289
- y: Math.max(0, Math.min(this.height - 1, (event.clientY - bounds.top) * scaleY))
290
- };
291
- }
292
- #syncKeyboardOnce(event) {
293
- if (this.keyboardSynced || !this.client) return;
294
- this.keyboardSynced = true;
295
- let flags = 0;
296
- if (event.getModifierState?.("CapsLock")) flags |= TOGGLE_FLAG.CAPS_LOCK;
297
- if (event.getModifierState?.("NumLock")) flags |= TOGGLE_FLAG.NUM_LOCK;
298
- if (event.getModifierState?.("ScrollLock")) flags |= TOGGLE_FLAG.SCROLL_LOCK;
299
- this.client.sendKeyboardSync(flags);
300
- }
301
- attachInput(client) {
302
- this.client = client;
303
- this.keyboardSynced = false;
304
- const canSend = () => this.client && this.client.connected && !this.viewOnly;
305
- let pendingMove = null;
306
- let moveScheduled = false;
307
- let pendingWheelDelta = 0;
308
- let touchIsDown = false;
309
- let lastTouchX = 0;
310
- let lastTouchY = 0;
311
- let lastTouchScrollY = 0;
312
- let lastTouchScrollX = 0;
313
- let gestureStartMid = { x: 0, y: 0 };
314
- let gestureStartDistance = 0;
315
- let lastPinchDistance = 0;
316
- let pendingTouchWheelDelta = 0;
317
- let touchMode = null;
318
- const touchMidpoint = (touches) => ({
319
- x: (touches[0].clientX + touches[1].clientX) / 2,
320
- y: (touches[0].clientY + touches[1].clientY) / 2
321
- });
322
- const touchDistance = (touches) => Math.hypot(
323
- touches[0].clientX - touches[1].clientX,
324
- touches[0].clientY - touches[1].clientY
325
- );
326
- const flushMove = () => {
327
- moveScheduled = false;
328
- if (pendingMove && canSend()) this.client.sendMouseMove(pendingMove.x, pendingMove.y);
329
- pendingMove = null;
330
- };
331
- this.handlers = {
332
- mousemove: (event) => {
333
- if (!canSend()) return;
334
- pendingMove = this.#toRemote(event);
335
- if (!moveScheduled) {
336
- moveScheduled = true;
337
- requestAnimationFrame(flushMove);
338
- }
339
- },
340
- mousedown: (event) => {
341
- this.canvas.focus();
342
- if (!canSend()) return;
343
- event.preventDefault();
344
- const { x, y } = this.#toRemote(event);
345
- this.client.sendMouseButton(event.button, true, x, y);
346
- },
347
- mouseup: (event) => {
348
- if (!canSend()) return;
349
- event.preventDefault();
350
- const { x, y } = this.#toRemote(event);
351
- this.client.sendMouseButton(event.button, false, x, y);
352
- },
353
- contextmenu: (event) => event.preventDefault(),
354
- wheel: (event) => {
355
- if (!canSend()) return;
356
- event.preventDefault();
357
- let delta = event.deltaY;
358
- if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) delta *= WHEEL_LINE_PIXELS;
359
- if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
360
- delta *= Math.max(1, this.canvas.clientHeight || this.height);
361
- }
362
- if (!delta) return;
363
- if (pendingWheelDelta && Math.sign(pendingWheelDelta) !== Math.sign(delta)) {
364
- pendingWheelDelta = 0;
365
- }
366
- pendingWheelDelta += delta;
367
- if (Math.abs(pendingWheelDelta) < WHEEL_STEP_PIXELS) return;
368
- const direction = Math.sign(pendingWheelDelta);
369
- pendingWheelDelta -= direction * WHEEL_STEP_PIXELS;
370
- const { x, y } = this.#toRemote(event);
371
- this.client.sendWheel(direction, x, y);
372
- },
373
- touchstart: (event) => {
374
- this.canvas.focus();
375
- if (!canSend()) return;
376
- event.preventDefault();
377
- if (event.touches.length === 1) {
378
- touchMode = "tap";
379
- const touch = event.touches[0];
380
- lastTouchX = touch.clientX;
381
- lastTouchY = touch.clientY;
382
- const { x, y } = this.#toRemote(touch);
383
- this.client.sendMouseMove(x, y);
384
- this.client.sendMouseButton(0, true, x, y);
385
- touchIsDown = true;
386
- } else if (event.touches.length === 2) {
387
- touchMode = "undecided";
388
- if (touchIsDown) {
389
- const { x, y } = this.#toRemote({ clientX: lastTouchX, clientY: lastTouchY });
390
- this.client.sendMouseButton(0, false, x, y);
391
- touchIsDown = false;
392
- }
393
- const mid = touchMidpoint(event.touches);
394
- const dist = touchDistance(event.touches);
395
- gestureStartMid = { x: mid.x, y: mid.y };
396
- gestureStartDistance = dist;
397
- lastTouchScrollY = mid.y;
398
- lastTouchScrollX = mid.x;
399
- lastPinchDistance = dist;
400
- pendingTouchWheelDelta = 0;
401
- }
402
- },
403
- touchmove: (event) => {
404
- if (!canSend()) return;
405
- event.preventDefault();
406
- if (event.touches.length === 1 && touchMode === "tap") {
407
- const touch = event.touches[0];
408
- lastTouchX = touch.clientX;
409
- lastTouchY = touch.clientY;
410
- const { x, y } = this.#toRemote(touch);
411
- this.client.sendMouseMove(x, y);
412
- } else if (event.touches.length === 2) {
413
- const mid = touchMidpoint(event.touches);
414
- const currX = mid.x;
415
- const currY = mid.y;
416
- const distance = touchDistance(event.touches);
417
- const deltaY = lastTouchScrollY - currY;
418
- const deltaX = lastTouchScrollX - currX;
419
- lastTouchScrollY = currY;
420
- lastTouchScrollX = currX;
421
- const totalMove = Math.hypot(currX - gestureStartMid.x, currY - gestureStartMid.y);
422
- const pinchDelta = Math.abs(distance - gestureStartDistance);
423
- const pinchRatio = gestureStartDistance > 0 ? distance / gestureStartDistance : 1;
424
- if (touchMode === "undecided" || touchMode === "gesture") {
425
- if (pinchDelta > 20 && Math.abs(pinchRatio - 1) > 0.15 && pinchDelta > totalMove * 0.8) {
426
- touchMode = "pinch";
427
- } else if (totalMove > 8) {
428
- touchMode = "scroll";
429
- }
430
- }
431
- if (touchMode === "pinch") {
432
- if (lastPinchDistance > 0 && Math.abs(distance - lastPinchDistance) > 1) {
433
- this.userZoomed = true;
434
- this.setZoom(this.zoom * (distance / lastPinchDistance), { x: currX, y: currY });
435
- }
436
- lastPinchDistance = distance;
437
- } else if (touchMode === "scroll") {
438
- lastPinchDistance = distance;
439
- if (this.canPan()) {
440
- this.panBy(deltaX * -1, deltaY * -1);
441
- pendingTouchWheelDelta = 0;
442
- return;
443
- }
444
- if (deltaY && pendingTouchWheelDelta && Math.sign(pendingTouchWheelDelta) !== Math.sign(deltaY)) {
445
- pendingTouchWheelDelta = 0;
446
- }
447
- pendingTouchWheelDelta += deltaY;
448
- while (Math.abs(pendingTouchWheelDelta) >= TOUCH_SCROLL_STEP) {
449
- const direction = Math.sign(pendingTouchWheelDelta);
450
- pendingTouchWheelDelta -= direction * TOUCH_SCROLL_STEP;
451
- const { x, y } = this.#toRemote({ clientX: currX, clientY: currY });
452
- this.client.sendWheel(direction, x, y);
453
- }
454
- }
455
- }
456
- },
457
- touchend: (event) => {
458
- if (!canSend()) return;
459
- event.preventDefault();
460
- if (event.touches.length === 0) {
461
- if (touchIsDown) {
462
- const { x, y } = this.#toRemote({ clientX: lastTouchX, clientY: lastTouchY });
463
- this.client.sendMouseButton(0, false, x, y);
464
- touchIsDown = false;
465
- }
466
- if (touchMode === "pinch" && this.zoom <= 1.05) {
467
- this.resetZoom();
468
- }
469
- touchMode = null;
470
- pendingTouchWheelDelta = 0;
471
- lastPinchDistance = 0;
472
- } else if (event.touches.length === 1) {
473
- if (touchMode === "gesture" || touchMode === "pinch" || touchMode === "scroll" || touchMode === "undecided") {
474
- if (touchMode === "pinch" && this.zoom <= 1.05) {
475
- this.resetZoom();
476
- }
477
- touchMode = null;
478
- lastPinchDistance = 0;
479
- pendingTouchWheelDelta = 0;
480
- }
481
- }
482
- },
483
- touchcancel: (event) => {
484
- if (!canSend()) return;
485
- if (touchIsDown) {
486
- const { x, y } = this.#toRemote({ clientX: lastTouchX, clientY: lastTouchY });
487
- this.client.sendMouseButton(0, false, x, y);
488
- touchIsDown = false;
489
- }
490
- if (touchMode === "pinch" && this.zoom <= 1.05) {
491
- this.resetZoom();
492
- }
493
- touchMode = null;
494
- pendingTouchWheelDelta = 0;
495
- lastPinchDistance = 0;
496
- },
497
- keydown: (event) => {
498
- if (!canSend()) return;
499
- this.#syncKeyboardOnce(event);
500
- if (this.#isPasteShortcut(event)) return;
501
- const code = this.#translateCode(event.code);
502
- if (!this.client.sendKeyByCode(code, true)) return;
503
- event.preventDefault();
504
- if (IS_MAC && event.metaKey && !COMMAND_TO_CONTROL[event.code]) {
505
- this.client.sendKeyByCode(code, false);
506
- return;
507
- }
508
- this.heldKeys.add(code);
509
- },
510
- keyup: (event) => {
511
- if (!canSend()) return;
512
- if (this.#isPasteShortcut(event)) return;
513
- const code = this.#translateCode(event.code);
514
- if (this.client.sendKeyByCode(code, false)) {
515
- this.heldKeys.delete(code);
516
- event.preventDefault();
517
- }
518
- },
519
- paste: (event) => {
520
- if (!canSend()) return;
521
- const target = event.target;
522
- const onTheScreen = target instanceof Node && this.container.contains(target);
523
- if (document.activeElement !== this.canvas && !onTheScreen) return;
524
- event.preventDefault();
525
- const text = event.clipboardData?.getData("text/plain") || "";
526
- if (text) this.client.pasteToHost(text);
527
- },
528
- blur: () => this.releaseAllKeys()
529
- };
530
- this.canvas.addEventListener("mousemove", this.handlers.mousemove);
531
- this.canvas.addEventListener("mousedown", this.handlers.mousedown);
532
- this.canvas.addEventListener("mouseup", this.handlers.mouseup);
533
- this.canvas.addEventListener("contextmenu", this.handlers.contextmenu);
534
- this.canvas.addEventListener("wheel", this.handlers.wheel, { passive: false });
535
- this.canvas.addEventListener("touchstart", this.handlers.touchstart, { passive: false });
536
- this.canvas.addEventListener("touchmove", this.handlers.touchmove, { passive: false });
537
- this.canvas.addEventListener("touchend", this.handlers.touchend, { passive: false });
538
- this.canvas.addEventListener("touchcancel", this.handlers.touchcancel, { passive: false });
539
- this.canvas.addEventListener("keydown", this.handlers.keydown);
540
- this.canvas.addEventListener("keyup", this.handlers.keyup);
541
- this.canvas.addEventListener("blur", this.handlers.blur);
542
- document.addEventListener("paste", this.handlers.paste);
543
- }
544
- /** Command on a Mac stands in for Control everywhere the host is concerned. */
545
- #translateCode(code) {
546
- return IS_MAC ? COMMAND_TO_CONTROL[code] || code : code;
547
- }
548
- #isPasteShortcut(event) {
549
- if (event.code !== "KeyV") return false;
550
- return IS_MAC ? event.metaKey : event.ctrlKey;
551
- }
552
- /** A key held while focus moves away would otherwise stay down on the host. */
553
- releaseAllKeys() {
554
- if (!this.client) return;
555
- for (const code of this.heldKeys) this.client.sendKeyByCode(code, false);
556
- this.heldKeys.clear();
557
- }
558
- focus() {
559
- this.canvas.focus();
560
- }
561
- blur() {
562
- this.canvas.blur();
563
- }
564
- destroy() {
565
- this.releaseAllKeys();
566
- window.removeEventListener("resize", this.onWindowResize);
567
- this.resizeObserver?.disconnect();
568
- if (this.handlers) {
569
- this.canvas.removeEventListener("mousemove", this.handlers.mousemove);
570
- this.canvas.removeEventListener("mousedown", this.handlers.mousedown);
571
- this.canvas.removeEventListener("mouseup", this.handlers.mouseup);
572
- this.canvas.removeEventListener("contextmenu", this.handlers.contextmenu);
573
- this.canvas.removeEventListener("wheel", this.handlers.wheel);
574
- this.canvas.removeEventListener("touchstart", this.handlers.touchstart);
575
- this.canvas.removeEventListener("touchmove", this.handlers.touchmove);
576
- this.canvas.removeEventListener("touchend", this.handlers.touchend);
577
- this.canvas.removeEventListener("touchcancel", this.handlers.touchcancel);
578
- this.canvas.removeEventListener("keydown", this.handlers.keydown);
579
- this.canvas.removeEventListener("keyup", this.handlers.keyup);
580
- this.canvas.removeEventListener("blur", this.handlers.blur);
581
- document.removeEventListener("paste", this.handlers.paste);
582
- }
583
- this.client = null;
584
- }
585
- }
586
- export {
587
- RdpDisplay
588
- };
@@ -1,23 +0,0 @@
1
- /** RDP 5.0 and later. Anything newer would oblige us to speak newer PDUs. */
2
- export declare const RDP_VERSION_5_PLUS = 524292;
3
- export declare const ENCRYPTION_LEVEL: {
4
- NONE: number;
5
- LOW: number;
6
- CLIENT_COMPATIBLE: number;
7
- HIGH: number;
8
- FIPS: number;
9
- };
10
- export declare function clientData(options: any): Uint8Array<ArrayBufferLike>;
11
- /**
12
- * Walk the host's answer. Only the security and network blocks carry anything
13
- * the client must act on; the core block is kept for logging.
14
- */
15
- export declare function parseServerData(blocks: any): {
16
- version: any;
17
- encryptionMethod: number;
18
- encryptionLevel: number;
19
- serverRandom: any;
20
- publicKey: any;
21
- ioChannelId: number;
22
- channelIds: any[];
23
- };