@dyyz1993/agent-browser 0.9.2 → 0.11.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.
- package/dist/__tests__/utils/parseCli.d.ts +1 -0
- package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
- package/dist/__tests__/utils/parseCli.js +18 -10
- package/dist/__tests__/utils/parseCli.js.map +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +63 -3
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +46 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +343 -13
- package/dist/browser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +8 -3
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/connection.d.ts.map +1 -1
- package/dist/cli/connection.js +39 -1
- package/dist/cli/connection.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +27 -20
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +5 -0
- package/dist/cli/output.js.map +1 -1
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +147 -1
- package/dist/daemon.js.map +1 -1
- package/dist/message-bridge.d.ts.map +1 -1
- package/dist/message-bridge.js +22 -4
- package/dist/message-bridge.js.map +1 -1
- package/dist/openapi.d.ts +22 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +382 -0
- package/dist/openapi.js.map +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -1
- package/dist/recorder/inject.js +61 -134
- package/dist/stream-server-standalone.d.ts +10 -0
- package/dist/stream-server-standalone.d.ts.map +1 -1
- package/dist/stream-server-standalone.js +594 -74
- package/dist/stream-server-standalone.js.map +1 -1
- package/dist/stream-server.d.ts +67 -2
- package/dist/stream-server.d.ts.map +1 -1
- package/dist/stream-server.js +371 -51
- package/dist/stream-server.js.map +1 -1
- package/dist/swagger-ui.d.ts +6 -0
- package/dist/swagger-ui.d.ts.map +1 -0
- package/dist/swagger-ui.js +51 -0
- package/dist/swagger-ui.js.map +1 -0
- package/dist/test-live.d.ts +2 -0
- package/dist/test-live.d.ts.map +1 -0
- package/dist/test-live.js +333 -0
- package/dist/test-live.js.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer-html.d.ts.map +1 -1
- package/dist/viewer-html.js +270 -58
- package/dist/viewer-html.js.map +1 -1
- package/dist/viewer-script.d.ts +20 -2
- package/dist/viewer-script.d.ts.map +1 -1
- package/dist/viewer-script.js +858 -102
- package/dist/viewer-script.js.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +6 -32
- package/scripts/test-cli-help.sh +51 -0
- package/scripts/verify-form.sh +67 -0
- package/scripts/verify-login.sh +65 -0
- package/scripts/verify-recording.sh +80 -0
- package/scripts/verify-upload.sh +41 -0
- package/skills/agent-browser/SKILL.md +88 -1
- package/skills/agent-browser/references/commands.md +3 -0
- package/skills/agent-browser/references/network-monitoring.md +232 -0
- package/skills/agent-browser/references/profiling.md +120 -0
- package/skills/agent-browser/references/recorder.md +319 -0
- package/skills/agent-browser/templates/network-intercept-crawl.sh +255 -0
- package/dist/__tests__/test-iframe.d.ts +0 -2
- package/dist/__tests__/test-iframe.d.ts.map +0 -1
- package/dist/__tests__/test-iframe.js +0 -52
- package/dist/__tests__/test-iframe.js.map +0 -1
- package/dist/cli-new.d.ts +0 -3
- package/dist/cli-new.d.ts.map +0 -1
- package/dist/cli-new.js +0 -308
- package/dist/cli-new.js.map +0 -1
- package/dist/cli-old.d.ts +0 -3
- package/dist/cli-old.d.ts.map +0 -1
- package/dist/cli-old.js +0 -1101
- package/dist/cli-old.js.map +0 -1
- package/dist/recorder/binding.d.ts +0 -24
- package/dist/recorder/binding.d.ts.map +0 -1
- package/dist/recorder/binding.js +0 -215
- package/dist/recorder/binding.js.map +0 -1
- package/dist/recorder/index.d.ts +0 -4
- package/dist/recorder/index.d.ts.map +0 -1
- package/dist/recorder/index.js +0 -4
- package/dist/recorder/index.js.map +0 -1
- package/dist/recorder/recorder.d.ts +0 -19
- package/dist/recorder/recorder.d.ts.map +0 -1
- package/dist/recorder/recorder.js +0 -101
- package/dist/recorder/recorder.js.map +0 -1
- package/dist/recorder/store.d.ts +0 -22
- package/dist/recorder/store.d.ts.map +0 -1
- package/dist/recorder/store.js +0 -150
- package/dist/recorder/store.js.map +0 -1
- package/dist/recorder/types.d.ts +0 -73
- package/dist/recorder/types.d.ts.map +0 -1
- package/dist/recorder/types.js +0 -5
- package/dist/recorder/types.js.map +0 -1
package/dist/viewer-script.js
CHANGED
|
@@ -52,13 +52,18 @@ export function sendUserActivity(state, qualityBadge, ws) {
|
|
|
52
52
|
}, 2000);
|
|
53
53
|
qualityBadge.textContent = 'interacting';
|
|
54
54
|
}
|
|
55
|
-
export function screenToPage(screenX, screenY,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
export function screenToPage(screenX, screenY, rect, deviceWidth, deviceHeight, element) {
|
|
56
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
57
|
+
return { x: 0, y: 0 };
|
|
58
|
+
const scaleX = deviceWidth / rect.width;
|
|
59
|
+
const scaleY = deviceHeight / rect.height;
|
|
60
|
+
let pageX = Math.round((screenX - rect.left) * scaleX);
|
|
61
|
+
let pageY = Math.round((screenY - rect.top) * scaleY);
|
|
62
|
+
if (element) {
|
|
63
|
+
pageX += element.x;
|
|
64
|
+
pageY += element.y;
|
|
65
|
+
}
|
|
66
|
+
return { x: pageX, y: pageY };
|
|
62
67
|
}
|
|
63
68
|
export function updateModifiers(e) {
|
|
64
69
|
let modifiers = 0;
|
|
@@ -83,8 +88,11 @@ export function buildViewerScript() {
|
|
|
83
88
|
const urlParams = new URLSearchParams(location.search);
|
|
84
89
|
const instanceId = urlParams.get('instanceId');
|
|
85
90
|
const session = urlParams.get('session') || 'default';
|
|
91
|
+
const rawSelector = urlParams.get('selector');
|
|
92
|
+
const selector = rawSelector ? decodeURIComponent(rawSelector) : undefined;
|
|
86
93
|
const wsParam = instanceId ? 'instanceId=' + instanceId : 'session=' + session;
|
|
87
|
-
|
|
94
|
+
|
|
95
|
+
const wsUrl = wsProtocol + '//' + location.hostname + ':' + port + '?' + wsParam + (selector ? '&selector=' + encodeURIComponent(selector) : '');
|
|
88
96
|
|
|
89
97
|
// Background management
|
|
90
98
|
let shouldReconnect = true;
|
|
@@ -97,19 +105,78 @@ export function buildViewerScript() {
|
|
|
97
105
|
const statusText = document.getElementById('statusText');
|
|
98
106
|
const urlDisplay = document.getElementById('urlDisplay');
|
|
99
107
|
const qualityBadge = document.getElementById('qualityBadge');
|
|
100
|
-
const tabsContainer = document.getElementById('tabs');
|
|
101
108
|
const connecting = document.getElementById('connecting');
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
|
|
110
|
+
const ua = (navigator.userAgent || '').toLowerCase();
|
|
111
|
+
|
|
112
|
+
function detectDeviceMode() {
|
|
113
|
+
var uaMatch = /iphone|ipod|android(?=.*mobile)|mobile|tablet|ipad/i.test(ua);
|
|
114
|
+
var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
115
|
+
return uaMatch || hasTouch ? 'mobile' : 'desktop';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
var _deviceCurrent = detectDeviceMode();
|
|
119
|
+
|
|
120
|
+
const DeviceMode = {
|
|
121
|
+
_current: _deviceCurrent,
|
|
122
|
+
_listeners: [],
|
|
123
|
+
get current() { return this._current; },
|
|
124
|
+
onModeChange: function(fn) { this._listeners.push(fn); },
|
|
125
|
+
switchTo: function(mode) {
|
|
126
|
+
if (mode === this._current) return;
|
|
127
|
+
var prev = this._current;
|
|
128
|
+
this._current = mode;
|
|
129
|
+
if (mode === 'desktop') {
|
|
130
|
+
MobileModule.detach();
|
|
131
|
+
DesktopModule.attach();
|
|
132
|
+
} else {
|
|
133
|
+
DesktopModule.detach();
|
|
134
|
+
MobileModule.attach();
|
|
135
|
+
}
|
|
136
|
+
this._listeners.forEach(function(fn) { fn(mode, prev); });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
var hiddenInput = null;
|
|
141
|
+
|
|
142
|
+
const DesktopModule = {
|
|
143
|
+
attach: function() {
|
|
144
|
+
if (hiddenInput && hiddenInput.parentNode) return;
|
|
145
|
+
hiddenInput = document.createElement('input');
|
|
146
|
+
hiddenInput.type = 'text';
|
|
147
|
+
hiddenInput.style.cssText = 'position:fixed;right:8px;bottom:80px;opacity:0.01;width:1px;height:1px;border:none;outline:none;padding:0;margin:0;font-size:16px;pointer-events:none;';
|
|
148
|
+
hiddenInput.id = 'hidden-input';
|
|
149
|
+
hiddenInput.setAttribute('autocomplete', 'off');
|
|
150
|
+
hiddenInput.setAttribute('autocorrect', 'off');
|
|
151
|
+
hiddenInput.setAttribute('autocapitalize', 'off');
|
|
152
|
+
hiddenInput.setAttribute('spellcheck', 'false');
|
|
153
|
+
document.body.appendChild(hiddenInput);
|
|
154
|
+
focusHiddenInput();
|
|
155
|
+
},
|
|
156
|
+
detach: function() {
|
|
157
|
+
if (hiddenInput) { hiddenInput.blur(); if (hiddenInput.parentNode) hiddenInput.parentNode.removeChild(hiddenInput); hiddenInput = null; }
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const MobileModule = {
|
|
162
|
+
attach: function() {
|
|
163
|
+
if (touchpad) { touchpad.style.display = 'flex'; touchpad.style.position = 'relative'; touchpad.style.background = 'linear-gradient(180deg, #1a1a2e 0%, #16213e 100%)'; touchpad.style.borderTop = '2px solid #4ecca3'; touchpad.style.justifyContent = 'center'; touchpad.style.zIndex = '10'; }
|
|
164
|
+
setupToolbar();
|
|
165
|
+
if (!cursorInitialized) { cursorInitialized = true; setTimeout(initCursor, 50); }
|
|
166
|
+
},
|
|
167
|
+
detach: function() {
|
|
168
|
+
var ip = document.getElementById('input-panel');
|
|
169
|
+
if (ip) { ip.style.display = 'none'; ip.style.bottom = '0px'; }
|
|
170
|
+
if (cursor) cursor.style.display = 'block';
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const degradedToast = document.createElement('div');
|
|
175
|
+
degradedToast.id = 'degraded-toast';
|
|
176
|
+
degradedToast.style.cssText = 'position:fixed;top:10px;left:50%;transform:translateX(-50%);background:rgba(255,200,0,0.9);color:#000;padding:10px 20px;border-radius:4px;font-family:sans-serif;font-size:14px;z-index:9999;display:none;pointer-events:none;';
|
|
177
|
+
degradedToast.textContent = 'Element not found, showing full page';
|
|
178
|
+
document.body.appendChild(degradedToast);
|
|
179
|
+
|
|
113
180
|
let ws = null;
|
|
114
181
|
let metadata = { deviceWidth: 1280, deviceHeight: 720, pageScaleFactor: 1, format: 'jpeg' };
|
|
115
182
|
let userActivityTimeout = null;
|
|
@@ -121,7 +188,8 @@ export function buildViewerScript() {
|
|
|
121
188
|
let lastInputValue = '';
|
|
122
189
|
let fixedSize = false;
|
|
123
190
|
let isRecording = false;
|
|
124
|
-
|
|
191
|
+
var _inputPollRaf = null;
|
|
192
|
+
|
|
125
193
|
function connect() {
|
|
126
194
|
ws = new WebSocket(wsUrl);
|
|
127
195
|
ws.binaryType = 'arraybuffer';
|
|
@@ -185,7 +253,15 @@ export function buildViewerScript() {
|
|
|
185
253
|
switch (msg.type) {
|
|
186
254
|
case 'frame':
|
|
187
255
|
pendingBinary = true;
|
|
256
|
+
const prevElement = metadata.element;
|
|
188
257
|
metadata = msg.metadata;
|
|
258
|
+
if (prevElement && !metadata.element) {
|
|
259
|
+
metadata.element = prevElement;
|
|
260
|
+
}
|
|
261
|
+
if (metadata.element) {
|
|
262
|
+
metadata.deviceWidth = metadata.element.width;
|
|
263
|
+
metadata.deviceHeight = metadata.element.height;
|
|
264
|
+
}
|
|
189
265
|
if (msg.format) metadata.format = msg.format;
|
|
190
266
|
if (msg.state) {
|
|
191
267
|
qualityBadge.textContent = msg.state;
|
|
@@ -204,6 +280,17 @@ export function buildViewerScript() {
|
|
|
204
280
|
metadata.deviceWidth = msg.viewportWidth;
|
|
205
281
|
metadata.deviceHeight = msg.viewportHeight;
|
|
206
282
|
}
|
|
283
|
+
if (msg.element) {
|
|
284
|
+
metadata.element = msg.element;
|
|
285
|
+
} else {
|
|
286
|
+
metadata.element = undefined;
|
|
287
|
+
if (selector && msg.degraded) {
|
|
288
|
+
showDegradedMessage();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (screen.src && metadata.deviceWidth && metadata.deviceHeight) {
|
|
292
|
+
fitImageToContainer();
|
|
293
|
+
}
|
|
207
294
|
}
|
|
208
295
|
break;
|
|
209
296
|
|
|
@@ -212,16 +299,24 @@ export function buildViewerScript() {
|
|
|
212
299
|
document.title = msg.data.title + ' - Agent Browser Viewer';
|
|
213
300
|
break;
|
|
214
301
|
|
|
215
|
-
case '
|
|
216
|
-
|
|
302
|
+
case 'input_focused':
|
|
303
|
+
if (inputMode) return;
|
|
304
|
+
if (DeviceMode.current !== 'mobile') return;
|
|
305
|
+
var sel = msg.selector || (msg.id ? '#' + msg.id : '');
|
|
306
|
+
enterInputMode(msg.value || '', msg.inputType || msg.tag || '', msg.placeholder || '', sel);
|
|
217
307
|
break;
|
|
218
308
|
|
|
219
|
-
case '
|
|
220
|
-
|
|
309
|
+
case 'input_value':
|
|
310
|
+
if (!inputMode) {
|
|
311
|
+
var field = document.getElementById('input-field');
|
|
312
|
+
if (field && typeof msg.text === 'string') {
|
|
313
|
+
field.value = msg.text;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
221
316
|
break;
|
|
222
317
|
|
|
223
|
-
case '
|
|
224
|
-
|
|
318
|
+
case 'input_blur':
|
|
319
|
+
exitInputMode();
|
|
225
320
|
break;
|
|
226
321
|
}
|
|
227
322
|
};
|
|
@@ -230,27 +325,53 @@ export function buildViewerScript() {
|
|
|
230
325
|
function handleBinary(data) {
|
|
231
326
|
if (!pendingBinary) return;
|
|
232
327
|
pendingBinary = false;
|
|
233
|
-
|
|
328
|
+
|
|
234
329
|
const blob = new Blob([data], {
|
|
235
330
|
type: metadata.format === 'webp' ? 'image/webp' : 'image/jpeg'
|
|
236
331
|
});
|
|
237
332
|
const url = URL.createObjectURL(blob);
|
|
238
|
-
|
|
333
|
+
|
|
239
334
|
const cleanup = () => {
|
|
240
335
|
URL.revokeObjectURL(url);
|
|
241
336
|
connecting.style.display = 'none';
|
|
242
337
|
screen.style.display = 'block';
|
|
338
|
+
fitImageToContainer();
|
|
339
|
+
if (!cursorInitialized && DeviceMode.current === 'mobile') {
|
|
340
|
+
cursorInitialized = true;
|
|
341
|
+
setTimeout(initCursor, 50);
|
|
342
|
+
}
|
|
243
343
|
};
|
|
244
|
-
|
|
344
|
+
|
|
245
345
|
screen.onload = cleanup;
|
|
246
346
|
screen.onerror = cleanup;
|
|
247
347
|
screen.src = url;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function fitImageToContainer() {
|
|
351
|
+
if (!metadata.deviceWidth || !metadata.deviceHeight) return;
|
|
352
|
+
var container = screen.parentElement;
|
|
353
|
+
if (!container) return;
|
|
354
|
+
var cw = container.clientWidth;
|
|
355
|
+
var ch = container.clientHeight;
|
|
356
|
+
if (cw <= 0 || ch <= 0) return;
|
|
357
|
+
|
|
358
|
+
var imgW = metadata.deviceWidth;
|
|
359
|
+
var imgH = metadata.deviceHeight;
|
|
360
|
+
var imgRatio = imgW / imgH;
|
|
361
|
+
var contRatio = cw / ch;
|
|
362
|
+
|
|
363
|
+
var dw, dh;
|
|
364
|
+
if (imgRatio > contRatio) {
|
|
365
|
+
dw = cw;
|
|
366
|
+
dh = cw / imgRatio;
|
|
367
|
+
} else {
|
|
368
|
+
dh = ch;
|
|
369
|
+
dw = ch * imgRatio;
|
|
253
370
|
}
|
|
371
|
+
|
|
372
|
+
screen.style.width = Math.round(dw) + 'px';
|
|
373
|
+
screen.style.height = Math.round(dh) + 'px';
|
|
374
|
+
|
|
254
375
|
}
|
|
255
376
|
|
|
256
377
|
function safeSend(data) {
|
|
@@ -272,13 +393,19 @@ export function buildViewerScript() {
|
|
|
272
393
|
|
|
273
394
|
function screenToPage(screenX, screenY) {
|
|
274
395
|
const rect = screen.getBoundingClientRect();
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
396
|
+
if (rect.width <= 0 || rect.height <= 0) return { x: 0, y: 0 };
|
|
397
|
+
|
|
398
|
+
var scaleX = metadata.deviceWidth / rect.width;
|
|
399
|
+
var scaleY = metadata.deviceHeight / rect.height;
|
|
400
|
+
var pageX = Math.round((screenX - rect.left) * scaleX);
|
|
401
|
+
var pageY = Math.round((screenY - rect.top) * scaleY);
|
|
402
|
+
|
|
403
|
+
if (metadata.element) {
|
|
404
|
+
pageX += metadata.element.x;
|
|
405
|
+
pageY += metadata.element.y;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return { x: pageX, y: pageY };
|
|
282
409
|
}
|
|
283
410
|
|
|
284
411
|
function updateModifiers(e) {
|
|
@@ -288,7 +415,14 @@ export function buildViewerScript() {
|
|
|
288
415
|
if (e.metaKey) modifiers |= 4;
|
|
289
416
|
if (e.shiftKey) modifiers |= 8;
|
|
290
417
|
}
|
|
291
|
-
|
|
418
|
+
|
|
419
|
+
function showDegradedMessage() {
|
|
420
|
+
degradedToast.style.display = 'block';
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
degradedToast.style.display = 'none';
|
|
423
|
+
}, 3000);
|
|
424
|
+
}
|
|
425
|
+
|
|
292
426
|
function focusHiddenInput() {
|
|
293
427
|
hiddenInput.focus();
|
|
294
428
|
hiddenInput.select();
|
|
@@ -297,7 +431,7 @@ export function buildViewerScript() {
|
|
|
297
431
|
screen.addEventListener('dragstart', (e) => e.preventDefault());
|
|
298
432
|
|
|
299
433
|
screen.addEventListener('click', () => {
|
|
300
|
-
focusHiddenInput();
|
|
434
|
+
if (DeviceMode.current === 'desktop') focusHiddenInput();
|
|
301
435
|
});
|
|
302
436
|
|
|
303
437
|
screen.addEventListener('mousemove', (e) => {
|
|
@@ -443,7 +577,10 @@ export function buildViewerScript() {
|
|
|
443
577
|
return;
|
|
444
578
|
}
|
|
445
579
|
}
|
|
446
|
-
|
|
580
|
+
|
|
581
|
+
const mobileInputField = document.getElementById('input-field');
|
|
582
|
+
if (mobileInputField && e.target === mobileInputField) return;
|
|
583
|
+
|
|
447
584
|
if (isComposing) return;
|
|
448
585
|
|
|
449
586
|
sendUserActivity();
|
|
@@ -461,6 +598,8 @@ export function buildViewerScript() {
|
|
|
461
598
|
document.addEventListener('keyup', (e) => {
|
|
462
599
|
console.log('[Viewer] keyup, key:', e.key);
|
|
463
600
|
if (isComposing && e.target === hiddenInput) return;
|
|
601
|
+
const mobileInputField = document.getElementById('input-field');
|
|
602
|
+
if (mobileInputField && e.target === mobileInputField) return;
|
|
464
603
|
|
|
465
604
|
safeSend(JSON.stringify({
|
|
466
605
|
type: 'keyboard_up',
|
|
@@ -468,74 +607,691 @@ export function buildViewerScript() {
|
|
|
468
607
|
}));
|
|
469
608
|
});
|
|
470
609
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
610
|
+
const cursor = document.getElementById('cursor');
|
|
611
|
+
const touchpad = document.getElementById('touchpad');
|
|
612
|
+
const screenContainer = document.getElementById('screenContainer');
|
|
613
|
+
|
|
614
|
+
// Initialize modules based on detected mode
|
|
615
|
+
if (DeviceMode.current === 'desktop') {
|
|
616
|
+
DesktopModule.attach();
|
|
617
|
+
} else {
|
|
618
|
+
MobileModule.attach();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let cursorPos = { x: 0, y: 0 };
|
|
622
|
+
let dragMode = false;
|
|
623
|
+
let isMouseDown = false;
|
|
624
|
+
let lastTouchPos = null;
|
|
625
|
+
let touchMoved = false;
|
|
626
|
+
let twoFingerStartPos = null;
|
|
627
|
+
let longPressTimer = null;
|
|
628
|
+
let longPressHintTimer = null;
|
|
629
|
+
let cursorInitialized = false;
|
|
630
|
+
|
|
631
|
+
const CURSOR_SENSITIVITY = 1.5;
|
|
632
|
+
const WHEEL_SENSITIVITY = 2.0;
|
|
633
|
+
const LONG_PRESS_MS = 800;
|
|
634
|
+
const COOLDOWN_MS = 200;
|
|
635
|
+
const MOVE_THRESHOLD = 5;
|
|
636
|
+
const ACCELERATION = 0.8;
|
|
637
|
+
const ACCEL_MAX_VELOCITY = 3.0;
|
|
638
|
+
|
|
639
|
+
let lastMoveTime = 0;
|
|
640
|
+
|
|
641
|
+
function computeAcceleration(dx, dy) {
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const dt = now - lastMoveTime;
|
|
644
|
+
lastMoveTime = now;
|
|
645
|
+
if (dt <= 0 || dt > 200) return CURSOR_SENSITIVITY;
|
|
646
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
647
|
+
const velocity = Math.min(dist / dt, ACCEL_MAX_VELOCITY);
|
|
648
|
+
return CURSOR_SENSITIVITY * (1 + ACCELERATION * velocity);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function computeWheelAccel(dx, dy) {
|
|
652
|
+
const now = Date.now();
|
|
653
|
+
const dt = now - lastMoveTime;
|
|
654
|
+
lastMoveTime = now;
|
|
655
|
+
if (dt <= 0 || dt > 200) return WHEEL_SENSITIVITY;
|
|
656
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
657
|
+
const velocity = Math.min(dist / dt, ACCEL_MAX_VELOCITY);
|
|
658
|
+
return WHEEL_SENSITIVITY * (1 + ACCELERATION * velocity);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let screenRect = null;
|
|
662
|
+
let moveAllowed = false;
|
|
663
|
+
var inputMode = false;
|
|
664
|
+
let moveCooldownUntil = 0;
|
|
665
|
+
let lastWheelDeltaX = 0;
|
|
666
|
+
let lastWheelDeltaY = 0;
|
|
667
|
+
let momentumActive = false;
|
|
668
|
+
let keyboardVvHandler = null;
|
|
669
|
+
let _scrollGuard = null;
|
|
670
|
+
|
|
671
|
+
function updateScreenRect() {
|
|
672
|
+
screenRect = screen.getBoundingClientRect();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function enterInputMode(initialValue, inputType, placeholder, selector) {
|
|
676
|
+
if (inputMode) return;
|
|
677
|
+
inputMode = true;
|
|
678
|
+
|
|
679
|
+
cursor.style.display = 'none';
|
|
680
|
+
|
|
681
|
+
const ip = document.getElementById('input-panel');
|
|
682
|
+
const tp = document.getElementById('touchpad');
|
|
683
|
+
|
|
684
|
+
if (ip) {
|
|
685
|
+
ip.style.display = 'flex';
|
|
686
|
+
ip.style.bottom = '0px';
|
|
687
|
+
}
|
|
688
|
+
if (tp) tp.style.display = 'none';
|
|
689
|
+
|
|
690
|
+
var labelParts = [];
|
|
691
|
+
if (inputType) labelParts.push(inputType);
|
|
692
|
+
if (placeholder && placeholder !== initialValue) labelParts.push(placeholder);
|
|
693
|
+
var targetEl = document.getElementById('input-target');
|
|
694
|
+
if (targetEl) targetEl.textContent = labelParts.length > 0 ? labelParts.join(' | ') : 'input';
|
|
695
|
+
|
|
696
|
+
window._currentTargetSelector = selector || '';
|
|
697
|
+
|
|
698
|
+
var field = document.getElementById('input-field');
|
|
699
|
+
if (field) {
|
|
700
|
+
field.value = initialValue || '';
|
|
701
|
+
field.dataset.lastSent = initialValue || '';
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
window.scrollTo(0, 0);
|
|
705
|
+
document.body.scrollTop = 0;
|
|
706
|
+
document.documentElement.scrollTop = 0;
|
|
707
|
+
|
|
708
|
+
document.body.style.touchAction = 'none';
|
|
709
|
+
document.documentElement.style.touchAction = 'none';
|
|
710
|
+
|
|
711
|
+
setTimeout(function() {
|
|
712
|
+
if (!field) return;
|
|
713
|
+
|
|
714
|
+
// Capture original viewport height AFTER panel is visible, BEFORE focus
|
|
715
|
+
var origVh = window.visualViewport ? window.visualViewport.height : window.innerHeight;
|
|
716
|
+
|
|
717
|
+
// Register visualViewport listener for keyboard detection
|
|
718
|
+
if (window.visualViewport) {
|
|
719
|
+
var kbTolerance = Math.floor(window.innerHeight * 0.1);
|
|
720
|
+
|
|
721
|
+
keyboardVvHandler = function() {
|
|
722
|
+
if (!inputMode || !ip) return;
|
|
723
|
+
var currentH = window.visualViewport.height;
|
|
724
|
+
var kbHeight = Math.max(0, origVh - currentH);
|
|
725
|
+
// Fallback: use innerHeight difference
|
|
726
|
+
if (kbHeight < kbTolerance) {
|
|
727
|
+
kbHeight = Math.max(kbHeight, Math.max(0, window.innerHeight - currentH));
|
|
728
|
+
}
|
|
729
|
+
if (kbHeight > kbTolerance) {
|
|
730
|
+
ip.style.bottom = kbHeight + 'px';
|
|
731
|
+
} else {
|
|
732
|
+
ip.style.bottom = '0px';
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
window.visualViewport.addEventListener('resize', keyboardVvHandler);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
field.focus();
|
|
739
|
+
|
|
740
|
+
window.scrollTo(0, 0);
|
|
741
|
+
document.body.scrollTop = 0;
|
|
742
|
+
document.documentElement.scrollTop = 0;
|
|
743
|
+
|
|
744
|
+
// Delayed check: wait for keyboard animation to complete (~300ms)
|
|
745
|
+
setTimeout(function() {
|
|
746
|
+
if (keyboardVvHandler) keyboardVvHandler();
|
|
747
|
+
window.scrollTo(0, 0);
|
|
748
|
+
document.body.scrollTop = 0;
|
|
749
|
+
document.documentElement.scrollTop = 0;
|
|
750
|
+
}, 350);
|
|
751
|
+
|
|
752
|
+
// Scroll guard interval: continuously fight iOS auto-scroll (reference demo)
|
|
753
|
+
if (!_scrollGuard) {
|
|
754
|
+
_scrollGuard = setInterval(function() {
|
|
755
|
+
if (!inputMode) return;
|
|
756
|
+
if (window.scrollY > 0 || document.body.scrollTop > 0 ||
|
|
757
|
+
document.documentElement.scrollTop > 0) {
|
|
758
|
+
window.scrollTo(0, 0);
|
|
759
|
+
document.body.scrollTop = 0;
|
|
760
|
+
document.documentElement.scrollTop = 0;
|
|
761
|
+
}
|
|
762
|
+
}, 100);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// RAF poll: reliable value-change detection for IME/CJK/clipboard/paste
|
|
766
|
+
var _pollField = field;
|
|
767
|
+
var _lastPolled = _pollField.value || '';
|
|
768
|
+
window._fieldComposing = false;
|
|
769
|
+
|
|
770
|
+
_pollField.addEventListener('compositionstart', function() {
|
|
771
|
+
window._fieldComposing = true;
|
|
772
|
+
});
|
|
773
|
+
_pollField.addEventListener('compositionend', function() {
|
|
774
|
+
window._fieldComposing = false;
|
|
775
|
+
// Double-RAF: yields current frame + next paint cycle.
|
|
776
|
+
// On mobile browsers (iOS Safari, Android WebView), .value may
|
|
777
|
+
// not be updated until 1-2 frames after compositionend fires.
|
|
778
|
+
requestAnimationFrame(function() {
|
|
779
|
+
requestAnimationFrame(function() {
|
|
780
|
+
syncInputToRemote(_pollField);
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
(function startPoll() {
|
|
786
|
+
function poll() {
|
|
787
|
+
if (!inputMode || !_pollField) { _inputPollRaf = null; return; }
|
|
788
|
+
var cur = _pollField.value;
|
|
789
|
+
if (cur !== _lastPolled) {
|
|
790
|
+
_lastPolled = cur;
|
|
791
|
+
if (!window._fieldComposing) {
|
|
792
|
+
syncInputToRemote(_pollField);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
_inputPollRaf = requestAnimationFrame(poll);
|
|
796
|
+
}
|
|
797
|
+
_inputPollRaf = requestAnimationFrame(poll);
|
|
798
|
+
})();
|
|
799
|
+
}, 100);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function exitInputMode() {
|
|
803
|
+
if (!inputMode) return;
|
|
804
|
+
inputMode = false;
|
|
805
|
+
|
|
806
|
+
if (_inputPollRaf) { cancelAnimationFrame(_inputPollRaf); _inputPollRaf = null; }
|
|
807
|
+
window._fieldComposing = false;
|
|
808
|
+
|
|
809
|
+
cursor.style.display = 'block';
|
|
810
|
+
|
|
811
|
+
const field = document.getElementById('input-field');
|
|
812
|
+
if (field) { field.value = ''; field.blur(); delete field.dataset.lastSent; }
|
|
813
|
+
|
|
814
|
+
const ip = document.getElementById('input-panel');
|
|
815
|
+
const tp = document.getElementById('touchpad');
|
|
816
|
+
|
|
817
|
+
if (ip) {
|
|
818
|
+
ip.style.display = 'none';
|
|
819
|
+
ip.style.bottom = '0px';
|
|
820
|
+
}
|
|
821
|
+
if (tp) tp.style.display = DeviceMode.current === 'mobile' ? 'flex' : 'none';
|
|
822
|
+
|
|
823
|
+
// Cleanup visualViewport handler
|
|
824
|
+
if (keyboardVvHandler && window.visualViewport) {
|
|
825
|
+
window.visualViewport.removeEventListener('resize', keyboardVvHandler);
|
|
826
|
+
keyboardVvHandler = null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Cleanup scroll guard
|
|
830
|
+
if (_scrollGuard) {
|
|
831
|
+
clearInterval(_scrollGuard);
|
|
832
|
+
_scrollGuard = null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Restore touch action
|
|
836
|
+
document.body.style.touchAction = '';
|
|
837
|
+
document.documentElement.style.touchAction = '';
|
|
838
|
+
|
|
478
839
|
safeSend(JSON.stringify({
|
|
479
|
-
type: '
|
|
480
|
-
|
|
481
|
-
touchPoints: touchPoints
|
|
840
|
+
type: 'input_blur_element',
|
|
841
|
+
selector: window._currentTargetSelector || ''
|
|
482
842
|
}));
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
var _syncDebounceTimer = null;
|
|
846
|
+
function syncInputToRemote(field) {
|
|
847
|
+
if (!field || !inputMode) return;
|
|
848
|
+
var current = field.value;
|
|
849
|
+
var lastSent = field.dataset.lastSent || '';
|
|
850
|
+
if (current === lastSent) return;
|
|
851
|
+
|
|
852
|
+
var isFirstSync = !field.dataset.lastSent;
|
|
853
|
+
function doSend() {
|
|
854
|
+
safeSend(JSON.stringify({
|
|
855
|
+
type: 'input_fill',
|
|
856
|
+
text: current,
|
|
857
|
+
selector: window._currentTargetSelector || ''
|
|
858
|
+
}));
|
|
859
|
+
field.dataset.lastSent = current;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (isFirstSync) {
|
|
863
|
+
doSend();
|
|
864
|
+
} else {
|
|
865
|
+
clearTimeout(_syncDebounceTimer);
|
|
866
|
+
_syncDebounceTimer = setTimeout(doSend, 30);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function sendInputText() {
|
|
871
|
+
const field = document.getElementById('input-field');
|
|
872
|
+
if (!field || !field.value.trim()) return;
|
|
873
|
+
|
|
874
|
+
var finalText = field.value;
|
|
875
|
+
var sel = window._currentTargetSelector || '';
|
|
876
|
+
|
|
877
|
+
// Send final text via input_fill + Enter key
|
|
491
878
|
safeSend(JSON.stringify({
|
|
492
|
-
type: '
|
|
493
|
-
|
|
494
|
-
|
|
879
|
+
type: 'input_fill',
|
|
880
|
+
text: finalText,
|
|
881
|
+
selector: sel
|
|
495
882
|
}));
|
|
496
|
-
e.preventDefault();
|
|
497
|
-
}, { passive: false });
|
|
498
|
-
|
|
499
|
-
screen.addEventListener('touchend', (e) => {
|
|
500
|
-
const touchPoints = Array.from(e.changedTouches).map(t => {
|
|
501
|
-
const pos = screenToPage(t.clientX, t.clientY);
|
|
502
|
-
return { x: pos.x, y: pos.y, id: t.identifier };
|
|
503
|
-
});
|
|
504
883
|
safeSend(JSON.stringify({
|
|
505
|
-
type: '
|
|
506
|
-
eventType: '
|
|
507
|
-
|
|
884
|
+
type: 'input_keyboard',
|
|
885
|
+
eventType: 'keyDown',
|
|
886
|
+
key: 'Enter',
|
|
887
|
+
code: 'Enter',
|
|
888
|
+
modifiers: 0,
|
|
889
|
+
selector: sel
|
|
508
890
|
}));
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
};
|
|
521
|
-
tabsContainer.appendChild(tab);
|
|
522
|
-
}
|
|
523
|
-
tab.textContent = title || url || 'New Tab';
|
|
524
|
-
tab.title = url;
|
|
525
|
-
tab.classList.toggle('active', active);
|
|
891
|
+
safeSend(JSON.stringify({
|
|
892
|
+
type: 'input_keyboard',
|
|
893
|
+
eventType: 'keyUp',
|
|
894
|
+
key: 'Enter',
|
|
895
|
+
code: 'Enter',
|
|
896
|
+
modifiers: 0,
|
|
897
|
+
selector: sel
|
|
898
|
+
}));
|
|
899
|
+
|
|
900
|
+
field.value = '';
|
|
901
|
+
exitInputMode();
|
|
526
902
|
}
|
|
527
|
-
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
903
|
+
|
|
904
|
+
function startMomentum() {
|
|
905
|
+
momentumActive = true;
|
|
906
|
+
var frame = function() {
|
|
907
|
+
if (!momentumActive) return;
|
|
908
|
+
lastWheelDeltaX *= 0.92;
|
|
909
|
+
lastWheelDeltaY *= 0.92;
|
|
910
|
+
if (Math.abs(lastWheelDeltaX) < 0.5 && Math.abs(lastWheelDeltaY) < 0.5) {
|
|
911
|
+
momentumActive = false;
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
var pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
915
|
+
safeSend(JSON.stringify({
|
|
916
|
+
type: 'input_mouse',
|
|
917
|
+
eventType: 'mouseWheel',
|
|
918
|
+
x: pagePos.x,
|
|
919
|
+
y: pagePos.y,
|
|
920
|
+
deltaX: lastWheelDeltaX,
|
|
921
|
+
deltaY: lastWheelDeltaY,
|
|
922
|
+
modifiers: 0
|
|
923
|
+
}));
|
|
924
|
+
requestAnimationFrame(frame);
|
|
925
|
+
};
|
|
926
|
+
requestAnimationFrame(frame);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function initCursor() {
|
|
930
|
+
updateScreenRect();
|
|
931
|
+
cursorPos = { x: screenRect.left + screenRect.width / 2, y: screenRect.top + screenRect.height / 2 };
|
|
932
|
+
updateCursor();
|
|
933
|
+
cursor.style.display = 'block';
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function updateCursor() {
|
|
937
|
+
cursor.style.left = cursorPos.x + 'px';
|
|
938
|
+
cursor.style.top = cursorPos.y + 'px';
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function clampCursor(val, min, max) {
|
|
942
|
+
return Math.max(min, Math.min(max, val));
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function setCursorMode(mode) {
|
|
946
|
+
cursor.className = '';
|
|
947
|
+
if (mode === 'move') cursor.classList.add('cursor-move');
|
|
948
|
+
else if (mode === 'drag') cursor.classList.add('cursor-drag');
|
|
949
|
+
else if (mode === 'longpress') cursor.classList.add('cursor-longpress');
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function showModeBadge(text, color) {
|
|
953
|
+
var badge = document.getElementById('modeBadge');
|
|
954
|
+
if (!badge) return;
|
|
955
|
+
badge.textContent = text;
|
|
956
|
+
badge.style.background = color;
|
|
957
|
+
badge.style.display = 'block';
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function hideModeBadge() {
|
|
961
|
+
var badge = document.getElementById('modeBadge');
|
|
962
|
+
if (!badge) return;
|
|
963
|
+
badge.style.display = 'none';
|
|
531
964
|
}
|
|
965
|
+
|
|
966
|
+
// Touchpad toolbar setup (always available when mobile module is active)
|
|
967
|
+
var _toolbarSetupDone = false;
|
|
968
|
+
function setupToolbar() {
|
|
969
|
+
if (_toolbarSetupDone) return;
|
|
970
|
+
_toolbarSetupDone = true;
|
|
971
|
+
var toolbar = document.getElementById('touchpadToolbar');
|
|
972
|
+
if (toolbar) {
|
|
973
|
+
toolbar.addEventListener('click', function(e) {
|
|
974
|
+
var btn = e.target.closest ? e.target.closest('.tpk-key') : null;
|
|
975
|
+
if (!btn) return;
|
|
976
|
+
e.preventDefault();
|
|
977
|
+
e.stopPropagation();
|
|
978
|
+
sendUserActivity();
|
|
979
|
+
var key = btn.dataset.key || '';
|
|
980
|
+
var code = btn.dataset.code || '';
|
|
981
|
+
safeSend(JSON.stringify({ type: 'input_keyboard', eventType: 'keyDown', key: key, code: code, modifiers: 0 }));
|
|
982
|
+
safeSend(JSON.stringify({ type: 'input_keyboard', eventType: 'keyUp', key: key, code: code, modifiers: 0 }));
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
var expandBtn = document.getElementById('tpkExpand');
|
|
986
|
+
var collapseBtn = document.getElementById('tpkCollapse');
|
|
987
|
+
if (expandBtn) expandBtn.addEventListener('click', function(e) { e.stopPropagation(); toolbar.classList.remove('collapsed'); });
|
|
988
|
+
if (collapseBtn) collapseBtn.addEventListener('click', function(e) { e.stopPropagation(); toolbar.classList.add('collapsed'); });
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Touch event handlers (always registered; guarded by DeviceMode.current)
|
|
993
|
+
touchpad.addEventListener('touchstart', (e) => {
|
|
994
|
+
if (DeviceMode.current !== 'mobile') return;
|
|
995
|
+
e.preventDefault();
|
|
996
|
+
sendUserActivity();
|
|
997
|
+
|
|
998
|
+
if (e.touches.length === 2) {
|
|
999
|
+
clearTimeout(longPressTimer);
|
|
1000
|
+
longPressTimer = null;
|
|
1001
|
+
dragMode = false;
|
|
1002
|
+
moveAllowed = false;
|
|
1003
|
+
lastTouchPos = null;
|
|
1004
|
+
momentumActive = false;
|
|
1005
|
+
setCursorMode(null);
|
|
1006
|
+
hideModeBadge();
|
|
1007
|
+
const t0 = e.touches[0];
|
|
1008
|
+
const t1 = e.touches[1];
|
|
1009
|
+
twoFingerStartPos = {
|
|
1010
|
+
lastMidX: (t0.clientX + t1.clientX) / 2,
|
|
1011
|
+
lastMidY: (t0.clientY + t1.clientY) / 2,
|
|
1012
|
+
};
|
|
1013
|
+
lastMoveTime = Date.now();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
if (e.touches.length === 1) {
|
|
1018
|
+
if (Date.now() < moveCooldownUntil) return;
|
|
1019
|
+
moveAllowed = true;
|
|
1020
|
+
setCursorMode('move');
|
|
1021
|
+
showModeBadge('MOVE', 'rgba(68, 140, 255, 0.7)');
|
|
1022
|
+
const t = e.touches[0];
|
|
1023
|
+
lastTouchPos = { x: t.clientX, y: t.clientY };
|
|
1024
|
+
lastMoveTime = Date.now();
|
|
1025
|
+
touchMoved = false;
|
|
1026
|
+
|
|
1027
|
+
clearTimeout(longPressTimer);
|
|
1028
|
+
clearTimeout(longPressHintTimer);
|
|
1029
|
+
longPressTimer = setTimeout(() => {
|
|
1030
|
+
longPressTimer = null;
|
|
1031
|
+
longPressHintTimer = null;
|
|
1032
|
+
dragMode = true;
|
|
1033
|
+
isMouseDown = true;
|
|
1034
|
+
touchMoved = true;
|
|
1035
|
+
setCursorMode('drag');
|
|
1036
|
+
showModeBadge('DRAG', 'rgba(255, 165, 0, 0.8)');
|
|
1037
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1038
|
+
safeSend(JSON.stringify({
|
|
1039
|
+
type: 'input_mouse',
|
|
1040
|
+
eventType: 'mousePressed',
|
|
1041
|
+
x: pagePos.x,
|
|
1042
|
+
y: pagePos.y,
|
|
1043
|
+
button: 'left',
|
|
1044
|
+
clickCount: 1,
|
|
1045
|
+
modifiers: 0
|
|
1046
|
+
}));
|
|
1047
|
+
}, LONG_PRESS_MS);
|
|
1048
|
+
}
|
|
1049
|
+
}, { passive: false });
|
|
1050
|
+
|
|
1051
|
+
touchpad.addEventListener('touchend', (e) => {
|
|
1052
|
+
if (DeviceMode.current !== 'mobile') return;
|
|
1053
|
+
e.preventDefault();
|
|
1054
|
+
clearTimeout(longPressTimer);
|
|
1055
|
+
clearTimeout(longPressHintTimer);
|
|
1056
|
+
if (e.touches.length === 0) {
|
|
1057
|
+
if (twoFingerStartPos) {
|
|
1058
|
+
moveCooldownUntil = Date.now() + COOLDOWN_MS;
|
|
1059
|
+
if (Math.abs(lastWheelDeltaX) > 2 || Math.abs(lastWheelDeltaY) > 2) {
|
|
1060
|
+
startMomentum();
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
moveAllowed = false;
|
|
1064
|
+
setCursorMode(null);
|
|
1065
|
+
hideModeBadge();
|
|
1066
|
+
if (dragMode) {
|
|
1067
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1068
|
+
safeSend(JSON.stringify({
|
|
1069
|
+
type: 'input_mouse',
|
|
1070
|
+
eventType: 'mouseReleased',
|
|
1071
|
+
x: pagePos.x,
|
|
1072
|
+
y: pagePos.y,
|
|
1073
|
+
button: 'left',
|
|
1074
|
+
clickCount: 1,
|
|
1075
|
+
modifiers: 0
|
|
1076
|
+
}));
|
|
1077
|
+
dragMode = false;
|
|
1078
|
+
isMouseDown = false;
|
|
1079
|
+
} else if (!touchMoved) {
|
|
1080
|
+
// Single tap/click: send click event, then attempt focus
|
|
1081
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1082
|
+
|
|
1083
|
+
safeSend(JSON.stringify({
|
|
1084
|
+
type: 'input_mouse',
|
|
1085
|
+
eventType: 'mousePressed',
|
|
1086
|
+
x: pagePos.x,
|
|
1087
|
+
y: pagePos.y,
|
|
1088
|
+
button: 'left',
|
|
1089
|
+
clickCount: 1,
|
|
1090
|
+
modifiers: 0
|
|
1091
|
+
}));
|
|
1092
|
+
safeSend(JSON.stringify({
|
|
1093
|
+
type: 'input_mouse',
|
|
1094
|
+
eventType: 'mouseReleased',
|
|
1095
|
+
x: pagePos.x,
|
|
1096
|
+
y: pagePos.y,
|
|
1097
|
+
button: 'left',
|
|
1098
|
+
clickCount: 1,
|
|
1099
|
+
modifiers: 0
|
|
1100
|
+
}));
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
}
|
|
1104
|
+
lastTouchPos = null;
|
|
1105
|
+
twoFingerStartPos = null;
|
|
1106
|
+
touchMoved = false;
|
|
1107
|
+
}
|
|
1108
|
+
}, { passive: false });
|
|
1109
|
+
|
|
1110
|
+
touchpad.addEventListener('touchmove', (e) => {
|
|
1111
|
+
if (DeviceMode.current !== 'mobile') return;
|
|
1112
|
+
e.preventDefault();
|
|
1113
|
+
|
|
1114
|
+
if (e.touches.length === 2 && twoFingerStartPos) {
|
|
1115
|
+
clearTimeout(longPressTimer);
|
|
1116
|
+
longPressTimer = null;
|
|
1117
|
+
dragMode = false;
|
|
1118
|
+
moveAllowed = false;
|
|
1119
|
+
lastTouchPos = null;
|
|
1120
|
+
const t0 = e.touches[0];
|
|
1121
|
+
const t1 = e.touches[1];
|
|
1122
|
+
const midX = (t0.clientX + t1.clientX) / 2;
|
|
1123
|
+
const midY = (t0.clientY + t1.clientY) / 2;
|
|
1124
|
+
const rawDX = midX - twoFingerStartPos.lastMidX;
|
|
1125
|
+
const rawDY = midY - twoFingerStartPos.lastMidY;
|
|
1126
|
+
const wMult = computeWheelAccel(rawDX, rawDY);
|
|
1127
|
+
const deltaX = rawDX * wMult;
|
|
1128
|
+
const deltaY = rawDY * wMult;
|
|
1129
|
+
lastWheelDeltaX = deltaX;
|
|
1130
|
+
lastWheelDeltaY = deltaY;
|
|
1131
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1132
|
+
safeSend(JSON.stringify({
|
|
1133
|
+
type: 'input_mouse',
|
|
1134
|
+
eventType: 'mouseWheel',
|
|
1135
|
+
x: pagePos.x,
|
|
1136
|
+
y: pagePos.y,
|
|
1137
|
+
deltaX: deltaX,
|
|
1138
|
+
deltaY: deltaY,
|
|
1139
|
+
modifiers: 0
|
|
1140
|
+
}));
|
|
1141
|
+
twoFingerStartPos.lastMidX = midX;
|
|
1142
|
+
twoFingerStartPos.lastMidY = midY;
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (e.touches.length === 1 && lastTouchPos && moveAllowed) {
|
|
1147
|
+
const t = e.touches[0];
|
|
1148
|
+
const dx = t.clientX - lastTouchPos.x;
|
|
1149
|
+
const dy = t.clientY - lastTouchPos.y;
|
|
1150
|
+
|
|
1151
|
+
if (!touchMoved && Math.sqrt(dx * dx + dy * dy) > MOVE_THRESHOLD) {
|
|
1152
|
+
touchMoved = true;
|
|
1153
|
+
clearTimeout(longPressTimer);
|
|
1154
|
+
clearTimeout(longPressHintTimer);
|
|
1155
|
+
longPressTimer = null;
|
|
1156
|
+
longPressHintTimer = null;
|
|
1157
|
+
setCursorMode('move');
|
|
1158
|
+
showModeBadge('MOVE', 'rgba(68, 140, 255, 0.7)');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
lastTouchPos = { x: t.clientX, y: t.clientY };
|
|
1162
|
+
|
|
1163
|
+
if (touchMoved) {
|
|
1164
|
+
sendUserActivity();
|
|
1165
|
+
if (!screenRect) updateScreenRect();
|
|
1166
|
+
const accel = computeAcceleration(dx, dy);
|
|
1167
|
+
cursorPos.x = clampCursor(cursorPos.x + dx * accel, screenRect.left, screenRect.right);
|
|
1168
|
+
cursorPos.y = clampCursor(cursorPos.y + dy * accel, screenRect.top, screenRect.bottom);
|
|
1169
|
+
updateCursor();
|
|
1170
|
+
|
|
1171
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1172
|
+
var dbg = document.getElementById('debug-overlay');
|
|
1173
|
+
if (dbg && (!window._moveDebugCount)) window._moveDebugCount = 0;
|
|
1174
|
+
if (dbg && window._moveDebugCount < 8) {
|
|
1175
|
+
window._moveDebugCount++;
|
|
1176
|
+
dbg.textContent += ' | cur:' + Math.round(cursorPos.x) + ',' + Math.round(cursorPos.y)
|
|
1177
|
+
+ ' -> page:' + pagePos.x + ',' + pagePos.y;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
safeSend(JSON.stringify({
|
|
1181
|
+
type: 'input_mouse',
|
|
1182
|
+
eventType: 'mouseMoved',
|
|
1183
|
+
x: pagePos.x,
|
|
1184
|
+
y: pagePos.y,
|
|
1185
|
+
button: dragMode ? 'left' : 'none',
|
|
1186
|
+
clickCount: 1,
|
|
1187
|
+
modifiers: 0
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}, { passive: false });
|
|
1192
|
+
|
|
1193
|
+
touchpad.addEventListener('touchcancel', () => {
|
|
1194
|
+
if (DeviceMode.current !== 'mobile') return;
|
|
1195
|
+
clearTimeout(longPressTimer);
|
|
1196
|
+
clearTimeout(longPressHintTimer);
|
|
1197
|
+
momentumActive = false;
|
|
1198
|
+
if (dragMode) {
|
|
1199
|
+
const pagePos = screenToPage(cursorPos.x, cursorPos.y);
|
|
1200
|
+
safeSend(JSON.stringify({
|
|
1201
|
+
type: 'input_mouse',
|
|
1202
|
+
eventType: 'mouseReleased',
|
|
1203
|
+
x: pagePos.x,
|
|
1204
|
+
y: pagePos.y,
|
|
1205
|
+
button: 'left',
|
|
1206
|
+
clickCount: 1,
|
|
1207
|
+
modifiers: 0
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
dragMode = false;
|
|
1211
|
+
isMouseDown = false;
|
|
1212
|
+
moveAllowed = false;
|
|
1213
|
+
setCursorMode(null);
|
|
1214
|
+
hideModeBadge();
|
|
1215
|
+
moveCooldownUntil = twoFingerStartPos ? Date.now() + COOLDOWN_MS : 0;
|
|
1216
|
+
lastTouchPos = null;
|
|
1217
|
+
twoFingerStartPos = null;
|
|
1218
|
+
touchMoved = false;
|
|
1219
|
+
}, { passive: false });
|
|
532
1220
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1221
|
+
var inputSendBtn = document.getElementById('input-send');
|
|
1222
|
+
if (inputSendBtn) inputSendBtn.addEventListener('click', function(e) {
|
|
1223
|
+
e.preventDefault();
|
|
1224
|
+
e.stopPropagation();
|
|
1225
|
+
sendInputText();
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
document.addEventListener('pointerdown', function(e) {
|
|
1229
|
+
if (!inputMode) return;
|
|
1230
|
+
var panel = document.getElementById('input-panel');
|
|
1231
|
+
if (panel && !panel.contains(e.target)) {
|
|
1232
|
+
exitInputMode();
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
var inputField = document.getElementById('input-field');
|
|
1237
|
+
if (inputField) {
|
|
1238
|
+
inputField.addEventListener('input', function(e) {
|
|
1239
|
+
if (window._fieldComposing) return;
|
|
1240
|
+
syncInputToRemote(inputField);
|
|
1241
|
+
});
|
|
1242
|
+
inputField.addEventListener('compositionend', function(e) {
|
|
1243
|
+
window._fieldComposing = false;
|
|
1244
|
+
requestAnimationFrame(function() {
|
|
1245
|
+
requestAnimationFrame(function() {
|
|
1246
|
+
syncInputToRemote(inputField);
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
inputField.addEventListener('keydown', function(e) {
|
|
1251
|
+
if (e.key === 'Enter') {
|
|
1252
|
+
e.preventDefault();
|
|
1253
|
+
sendInputText();
|
|
1254
|
+
} else if (e.key === 'Escape') {
|
|
1255
|
+
e.preventDefault();
|
|
1256
|
+
exitInputMode();
|
|
1257
|
+
} else if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
1258
|
+
// Let it fall through - the field value will change,
|
|
1259
|
+
// then syncInputToRemote will pick up the change and send input_fill
|
|
1260
|
+
}
|
|
536
1261
|
});
|
|
537
1262
|
}
|
|
538
1263
|
|
|
1264
|
+
// Image sizing: re-fit on container resize (phone rotation, window resize)
|
|
1265
|
+
var resizeTimer = null;
|
|
1266
|
+
window.addEventListener('resize', () => {
|
|
1267
|
+
clearTimeout(resizeTimer);
|
|
1268
|
+
resizeTimer = setTimeout(function() {
|
|
1269
|
+
fitImageToContainer();
|
|
1270
|
+
var newMode = detectDeviceMode();
|
|
1271
|
+
if (newMode !== DeviceMode.current) DeviceMode.switchTo(newMode);
|
|
1272
|
+
}, 100);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
window.addEventListener('orientationchange', () => {
|
|
1276
|
+
setTimeout(function() {
|
|
1277
|
+
var newMode = detectDeviceMode();
|
|
1278
|
+
if (newMode !== DeviceMode.current) DeviceMode.switchTo(newMode);
|
|
1279
|
+
}, 200);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
// matchMedia pointer:coarse as additional trigger
|
|
1283
|
+
if (window.matchMedia) {
|
|
1284
|
+
try {
|
|
1285
|
+
var mql = window.matchMedia('(pointer:coarse)');
|
|
1286
|
+
if (mql && typeof mql.addEventListener === 'function') {
|
|
1287
|
+
mql.addEventListener('change', function(e) {
|
|
1288
|
+
var newMode = e.matches ? 'mobile' : 'desktop';
|
|
1289
|
+
DeviceMode.switchTo(newMode);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
} catch(err) {}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
539
1295
|
// Recorder functionality
|
|
540
1296
|
const recordBtn = document.getElementById('recordBtn');
|
|
541
1297
|
const recordText = document.getElementById('recordText');
|
|
@@ -579,7 +1335,7 @@ export function buildViewerScript() {
|
|
|
579
1335
|
}
|
|
580
1336
|
});
|
|
581
1337
|
|
|
582
|
-
focusHiddenInput();
|
|
1338
|
+
if (DeviceMode.current === 'desktop') focusHiddenInput();
|
|
583
1339
|
connect();
|
|
584
1340
|
`;
|
|
585
1341
|
}
|