Feature: Web remote desktop cursor sync with remote host

This commit is contained in:
yuanyuanxiang
2026-04-27 12:12:23 +02:00
parent b98607d24d
commit 1cc66aff56
4 changed files with 79 additions and 2 deletions

View File

@@ -1254,6 +1254,10 @@ VOID CScreenSpyDlg::DrawNextScreenDiff(bool keyFrame)
m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2+sizeof(POINT))[0]; m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2+sizeof(POINT))[0];
if (bOldCursorIndex != m_bCursorIndex) { if (bOldCursorIndex != m_bCursorIndex) {
bChange = TRUE; bChange = TRUE;
// 通知 Web 客户端光标变化
if (WebService().IsRunning()) {
WebService().BroadcastCursor(m_ClientID, m_bCursorIndex);
}
if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构 if (m_bIsCtrl && !m_bIsTraceCursor) {//替换指定窗口所属类的WNDCLASSEX结构
HCURSOR cursor; HCURSOR cursor;
if (m_bCursorIndex == 254) { // -2: 使用自定义光标 if (m_bCursorIndex == 254) { // -2: 使用自定义光标
@@ -1429,6 +1433,10 @@ VOID CScreenSpyDlg::DrawScrollFrame()
m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2 + sizeof(POINT))[0]; m_bCursorIndex = m_ContextObject->InDeCompressedBuffer.GetBuffer(2 + sizeof(POINT))[0];
if (bOldCursorIndex != m_bCursorIndex) { if (bOldCursorIndex != m_bCursorIndex) {
bChange = TRUE; bChange = TRUE;
// 通知 Web 客户端光标变化
if (WebService().IsRunning()) {
WebService().BroadcastCursor(m_ClientID, m_bCursorIndex);
}
} }
// 读取滚动参数 // 读取滚动参数

View File

@@ -1232,6 +1232,18 @@ inline std::string GetWebPageHTML() {
updateScreenStatus('connected'); updateScreenStatus('connected');
initDecoder(msg.width, msg.height); initDecoder(msg.width, msg.height);
break; break;
case 'cursor':
// Update remote cursor style (only for desktop in control mode)
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;
}
break;
case 'device_offline': case 'device_offline':
// Only handle if this is the device we're currently viewing // Only handle if this is the device we're currently viewing
if (!token) break; if (!token) break;
@@ -1748,6 +1760,28 @@ inline std::string GetWebPageHTML() {
// Control mode state (mouse/keyboard control) // Control mode state (mouse/keyboard control)
let controlEnabled = false; let controlEnabled = false;
// Remote cursor mapping (Windows cursor index -> CSS cursor)
// Index matches CursorInfo.h: IDC_APPSTARTING(0) to IDC_WAIT(15), 254=custom, 255=unsupported
const cursorMap = [
'progress', // 0: IDC_APPSTARTING
'default', // 1: IDC_ARROW
'crosshair', // 2: IDC_CROSS
'pointer', // 3: IDC_HAND
'help', // 4: IDC_HELP
'text', // 5: IDC_IBEAM
'default', // 6: IDC_ICON (no direct CSS equivalent)
'not-allowed', // 7: IDC_NO
'default', // 8: IDC_SIZE (deprecated, use default)
'move', // 9: IDC_SIZEALL
'nesw-resize', // 10: IDC_SIZENESW
'ns-resize', // 11: IDC_SIZENS
'nwse-resize', // 12: IDC_SIZENWSE
'ew-resize', // 13: IDC_SIZEWE
'default', // 14: IDC_UPARROW (no direct CSS equivalent)
'wait' // 15: IDC_WAIT
];
let currentCursorIndex = 1; // Default: arrow
// Floating toolbar state // Floating toolbar state
let toolbarVisible = false; let toolbarVisible = false;
let toolbarHideTimer = null; let toolbarHideTimer = null;
@@ -1899,8 +1933,18 @@ inline std::string GetWebPageHTML() {
const canvas = document.getElementById('screen-canvas'); const canvas = document.getElementById('screen-canvas');
const cursorOverlay = document.getElementById('cursor-overlay'); const cursorOverlay = document.getElementById('cursor-overlay');
// Touch devices: hide browser cursor, show overlay (touchpad mode) // Touch devices: hide browser cursor, show overlay (touchpad mode)
// Desktop: keep browser cursor visible, no overlay needed (remote shows cursor) // Desktop: use remote cursor style when control enabled
canvas.style.cursor = (controlEnabled && isTouchDevice) ? 'none' : 'default'; 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 {
canvas.style.cursor = 'default';
}
cursorOverlay.classList.toggle('active', controlEnabled && isTouchDevice); cursorOverlay.classList.toggle('active', controlEnabled && isTouchDevice);
} }
@@ -2726,6 +2770,7 @@ inline std::string GetWebPageHTML() {
if (qcMouse) qcMouse.classList.remove('active'); if (qcMouse) qcMouse.classList.remove('active');
document.getElementById('screen-canvas').style.cursor = 'default'; document.getElementById('screen-canvas').style.cursor = 'default';
document.getElementById('cursor-overlay').classList.remove('active'); document.getElementById('cursor-overlay').classList.remove('active');
currentCursorIndex = 1; // Reset to default arrow
// Reset zoom state // Reset zoom state
zoomState.scale = 1; zoomState.scale = 1;

View File

@@ -1475,6 +1475,27 @@ void CWebService::NotifyResolutionChange(uint64_t device_id, int width, int heig
} }
} }
void CWebService::BroadcastCursor(uint64_t device_id, uint8_t cursor_index) {
if (m_bStopping) return;
// Build JSON message
Json::Value res;
res["cmd"] = "cursor";
res["index"] = cursor_index;
Json::StreamWriterBuilder builder;
builder["indentation"] = "";
std::string json = Json::writeString(builder, res);
// Send to all watching clients
std::lock_guard<std::mutex> lock(m_ClientsMutex);
for (auto& [ws_ptr, client] : m_Clients) {
if (client.watch_device_id == device_id) {
SendText(ws_ptr, json);
}
}
}
bool CWebService::StartRemoteDesktop(uint64_t device_id) { bool CWebService::StartRemoteDesktop(uint64_t device_id) {
if (!m_pParentDlg) return false; if (!m_pParentDlg) return false;

View File

@@ -96,6 +96,9 @@ public:
// Resolution change notification // Resolution change notification
void NotifyResolutionChange(uint64_t device_id, int width, int height); void NotifyResolutionChange(uint64_t device_id, int width, int height);
// Cursor change notification (called from ScreenSpyDlg)
void BroadcastCursor(uint64_t device_id, uint8_t cursor_index);
// Get count of web clients watching a device // Get count of web clients watching a device
int GetWebClientCount(uint64_t device_id); int GetWebClientCount(uint64_t device_id);