From ccab37658abaf9b41ec9c86e35ef9f5168209187 Mon Sep 17 00:00:00 2001
From: yuanyuanxiang <962914132@qq.com>
Date: Sun, 17 May 2026 20:02:10 +0200
Subject: [PATCH] Improve(Web): Touch-mode visual cursor follows remote IDC_*
state
---
server/web/index.html | 124 ++++++++++++++++++++++++++++++++++--------
1 file changed, 102 insertions(+), 22 deletions(-)
diff --git a/server/web/index.html b/server/web/index.html
index 420ae6b..e88a887 100644
--- a/server/web/index.html
+++ b/server/web/index.html
@@ -951,6 +951,9 @@
flex-direction: column;
}
}
+ /* Cursor overlay (touchpad mode). Per-variant classes are swapped via JS based on
+ the remote IDC_* index. Each variant overrides --hx/--hy so the icon's tip
+ (hotspot), not the div's top-left, lands on the reported cursor position. */
.cursor-overlay {
position: fixed;
width: 24px;
@@ -958,15 +961,65 @@
pointer-events: none;
display: none;
z-index: 1002;
- background: url('data:image/svg+xml,') no-repeat;
+ background-repeat: no-repeat;
background-size: contain;
filter: drop-shadow(2px 2px 3px rgba(0,0,0,0.6));
transform-origin: 0 0;
- /* SVG arrow tip is at (4,4) inside the 24x24 viewBox; shift overlay so the tip,
- not the div's top-left, lands on the reported cursor position. */
- transform: translate(-4px, -4px);
+ --hx: 4px;
+ --hy: 4px;
+ transform: translate(calc(-1 * var(--hx)), calc(-1 * var(--hy)));
+ /* IDC_ARROW (default) — also the fallback for unmapped indices. */
+ background-image: url('data:image/svg+xml,');
}
.cursor-overlay.active { display: block; }
+ /* IDC_IBEAM (5) — text input */
+ .cursor-overlay.cursor-ibeam {
+ --hx: 8px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_HAND (3) — Windows-style pointing hand: tall index finger, thumb on the left,
+ three visible bent fingers (middle/ring/pinky) with descending heights, rounded palm. */
+ .cursor-overlay.cursor-hand {
+ --hx: 11.5px; --hy: 1.5px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_WAIT (15) — busy hourglass */
+ .cursor-overlay.cursor-wait {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_NO (7) — forbidden */
+ .cursor-overlay.cursor-no {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_SIZEALL (9) — 4-way move */
+ .cursor-overlay.cursor-move {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_SIZENS (11) — vertical resize */
+ .cursor-overlay.cursor-sizens {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_SIZEWE (13) — horizontal resize */
+ .cursor-overlay.cursor-sizewe {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_SIZENWSE (12) — diagonal NW-SE resize (\\). Rotating vertical NS arrow -45° (CCW)
+ tilts the top-left to upper-left and bottom-right to lower-right. */
+ .cursor-overlay.cursor-sizenwse {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
+ /* IDC_SIZENESW (10) — diagonal NE-SW resize (//). Rotating vertical NS arrow +45° (CW)
+ tilts the top toward upper-right and bottom toward lower-left. */
+ .cursor-overlay.cursor-sizenesw {
+ --hx: 12px; --hy: 12px;
+ background-image: url('data:image/svg+xml,');
+ }
/* Input shortcut bar - below canvas, portrait mode only */
.input-shortcuts {
position: absolute;
@@ -1465,15 +1518,11 @@
initDecoder(msg.width, msg.height);
break;
case 'cursor':
- // Update remote cursor style (only for desktop in control mode)
+ // Update remote cursor visual: native CSS cursor on desktop,
+ // overlay variant on touch. 254=custom (unsupported), 255=unsupported.
currentCursorIndex = msg.index;
- if (controlEnabled && !isTouchDevice) {
- const canvas = document.getElementById('screen-canvas');
- // 254=custom cursor (not supported in web), 255=unsupported -> default
- const cssCursor = (msg.index >= 0 && msg.index < cursorMap.length)
- ? cursorMap[msg.index]
- : 'default';
- canvas.style.cursor = cssCursor;
+ if (controlEnabled) {
+ applyRemoteCursor(msg.index);
}
break;
case 'device_offline':
@@ -2274,6 +2323,39 @@
];
let currentCursorIndex = 1; // Default: arrow
+ // IDC_* index -> CSS class on .cursor-overlay (touch-mode visual cursor).
+ // Indices not listed (0,2,4,6,8,14, custom) fall back to default arrow.
+ const cursorOverlayClassMap = {
+ 3: 'cursor-hand',
+ 5: 'cursor-ibeam',
+ 7: 'cursor-no',
+ 9: 'cursor-move',
+ 10: 'cursor-sizenesw',
+ 11: 'cursor-sizens',
+ 12: 'cursor-sizenwse',
+ 13: 'cursor-sizewe',
+ 15: 'cursor-wait'
+ };
+
+ // Apply the remote cursor: native CSS cursor on desktop, overlay variant on touch.
+ function applyRemoteCursor(index) {
+ if (isTouchDevice) {
+ const overlay = document.getElementById('cursor-overlay');
+ if (!overlay) return;
+ const wasActive = overlay.classList.contains('active');
+ overlay.className = 'cursor-overlay' + (wasActive ? ' active' : '');
+ const cls = cursorOverlayClassMap[index];
+ if (cls) overlay.classList.add(cls);
+ } else {
+ const canvas = document.getElementById('screen-canvas');
+ if (!canvas) return;
+ const cssCursor = (index >= 0 && index < cursorMap.length)
+ ? cursorMap[index]
+ : 'default';
+ canvas.style.cursor = cssCursor;
+ }
+ }
+
// Floating toolbar state
let toolbarVisible = false;
let toolbarHideTimer = null;
@@ -2424,20 +2506,17 @@
// Cursor handling
const canvas = document.getElementById('screen-canvas');
const cursorOverlay = document.getElementById('cursor-overlay');
- // Touch devices: hide browser cursor, show overlay (touchpad mode)
- // Desktop: use remote cursor style when control enabled
+ // Touch: hide native cursor and show our overlay (touchpad mode).
+ // Desktop: native CSS cursor is set by applyRemoteCursor below.
if (controlEnabled && isTouchDevice) {
canvas.style.cursor = 'none';
- } else if (controlEnabled && !isTouchDevice) {
- // Apply current remote cursor
- const cssCursor = (currentCursorIndex >= 0 && currentCursorIndex < cursorMap.length)
- ? cursorMap[currentCursorIndex]
- : 'default';
- canvas.style.cursor = cssCursor;
- } else {
+ } else if (!controlEnabled) {
canvas.style.cursor = 'default';
}
cursorOverlay.classList.toggle('active', controlEnabled && isTouchDevice);
+ if (controlEnabled) {
+ applyRemoteCursor(currentCursorIndex);
+ }
}
// Update cursor overlay position (accounting for zoom/pan transform)
@@ -3283,7 +3362,8 @@
const qcMouse = document.getElementById('qc-mouse');
if (qcMouse) qcMouse.classList.remove('active');
document.getElementById('screen-canvas').style.cursor = 'default';
- document.getElementById('cursor-overlay').classList.remove('active');
+ // Strip any cursor-* variant on the overlay, leaving the bare default arrow.
+ document.getElementById('cursor-overlay').className = 'cursor-overlay';
currentCursorIndex = 1; // Reset to default arrow
// Reset zoom state