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.
- package/dist/index.js +1 -6
- package/dist/{protocols/index.d.ts → protocols.d.ts} +2 -0
- package/dist/protocols.js +24 -0
- package/package.json +15 -10
- package/dist/protocols/index.js +0 -34
- package/dist/protocols/rdp/buffer.d.ts +0 -38
- package/dist/protocols/rdp/buffer.js +0 -169
- package/dist/protocols/rdp/caps.d.ts +0 -49
- package/dist/protocols/rdp/caps.js +0 -259
- package/dist/protocols/rdp/cert.d.ts +0 -18
- package/dist/protocols/rdp/cert.js +0 -134
- package/dist/protocols/rdp/client.d.ts +0 -56
- package/dist/protocols/rdp/client.js +0 -1069
- package/dist/protocols/rdp/cliprdr.d.ts +0 -27
- package/dist/protocols/rdp/cliprdr.js +0 -239
- package/dist/protocols/rdp/credssp.d.ts +0 -34
- package/dist/protocols/rdp/credssp.js +0 -253
- package/dist/protocols/rdp/crypto.d.ts +0 -63
- package/dist/protocols/rdp/crypto.js +0 -368
- package/dist/protocols/rdp/display.d.ts +0 -38
- package/dist/protocols/rdp/display.js +0 -588
- package/dist/protocols/rdp/gcc.d.ts +0 -23
- package/dist/protocols/rdp/gcc.js +0 -131
- package/dist/protocols/rdp/keymap.d.ts +0 -9
- package/dist/protocols/rdp/keymap.js +0 -131
- package/dist/protocols/rdp/mcs.d.ts +0 -40
- package/dist/protocols/rdp/mcs.js +0 -222
- package/dist/protocols/rdp/ntlm.d.ts +0 -61
- package/dist/protocols/rdp/ntlm.js +0 -304
- package/dist/protocols/rdp/pdu.d.ts +0 -155
- package/dist/protocols/rdp/pdu.js +0 -443
- package/dist/protocols/rdp/rail.d.ts +0 -119
- package/dist/protocols/rdp/rail.js +0 -382
- package/dist/protocols/rdp/rle.d.ts +0 -13
- package/dist/protocols/rdp/rle.js +0 -275
- package/dist/protocols/rdp/sec.d.ts +0 -59
- package/dist/protocols/rdp/sec.js +0 -155
- package/dist/protocols/rdp/session.d.ts +0 -62
- package/dist/protocols/rdp/session.js +0 -306
- package/dist/protocols/rdp/tls.d.ts +0 -18
- package/dist/protocols/rdp/tls.js +0 -353
- package/dist/protocols/rdp/vchannel.d.ts +0 -28
- package/dist/protocols/rdp/vchannel.js +0 -58
- package/dist/protocols/rdp/x224.d.ts +0 -40
- package/dist/protocols/rdp/x224.js +0 -109
- package/dist/protocols/ssh/kex.d.ts +0 -97
- package/dist/protocols/ssh/kex.js +0 -176
- package/dist/protocols/ssh/messages.d.ts +0 -50
- package/dist/protocols/ssh/messages.js +0 -54
- package/dist/protocols/ssh/session.d.ts +0 -66
- package/dist/protocols/ssh/session.js +0 -608
- package/dist/protocols/ssh/terminal.d.ts +0 -46
- package/dist/protocols/ssh/terminal.js +0 -190
- package/dist/protocols/ssh/transport.d.ts +0 -62
- package/dist/protocols/ssh/transport.js +0 -612
- package/dist/protocols/ssh/wire.d.ts +0 -52
- package/dist/protocols/ssh/wire.js +0 -188
- package/dist/protocols/types.d.ts +0 -127
- package/dist/protocols/types.js +0 -0
- package/dist/protocols/vnc/input.d.ts +0 -5
- package/dist/protocols/vnc/input.js +0 -15
- package/dist/protocols/vnc/session.d.ts +0 -12
- package/dist/protocols/vnc/session.js +0 -258
- package/dist/shared/connection.d.ts +0 -81
- package/dist/shared/connection.js +0 -113
- package/dist/shared/device.d.ts +0 -26
- package/dist/shared/device.js +0 -0
- package/dist/shared/hosts.d.ts +0 -63
- package/dist/shared/hosts.js +0 -132
- package/dist/shared/icons.d.ts +0 -46
- package/dist/shared/icons.js +0 -41
- package/dist/shared/protocol.d.ts +0 -28
- package/dist/shared/protocol.js +0 -39
- package/dist/shared/signals.d.ts +0 -37
- 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
|
-
};
|