From 4cf39a6ed3156681df4122c8a2b5bef76aae4f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 1 Aug 2026 22:19:56 +0000 Subject: [PATCH] desktop: show a dot cursor when the server sends no cursor shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pointer was invisible, not misplaced. noVNC hides the browser's own cursor over the canvas and draws the remote cursor in its place, but initialises that image to RFB.cursors.none — so until the server sends a shape, nothing is drawn AND the real cursor stays hidden. The pointer simply vanishes. Measured before changing anything: against a 1109x715 session the pointer covered x 5..1108 and y 11..714, a clean 1:1 map with no clamping or scaling. Clicks were landing exactly where they were aimed the whole time; the cursor just could not be seen, which is indistinguishable from a desync when you are the one trying to click something. showDotCursor renders a dot in exactly that case. It does not mask a real problem: when the server does supply a shape, the shape still wins. Co-Authored-By: Claude Opus 5 (1M context) --- .../officerdev/src/apps/Desktop/DesktopView.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/workspaces/officerdev/src/apps/Desktop/DesktopView.tsx b/src/workspaces/officerdev/src/apps/Desktop/DesktopView.tsx index bcc6e906..91eea6ea 100644 --- a/src/workspaces/officerdev/src/apps/Desktop/DesktopView.tsx +++ b/src/workspaces/officerdev/src/apps/Desktop/DesktopView.tsx @@ -27,6 +27,7 @@ const buildWsUrl = () => { type RFBInstance = { resizeSession: boolean; scaleViewport: boolean; + showDotCursor: boolean; focusOnClick: boolean; disconnect: () => void; sendCredentials: (creds: { password: string }) => void; @@ -148,6 +149,13 @@ export const DesktopView = ({ className, style }: DesktopViewProps) => { rfb.resizeSession = true; rfb.scaleViewport = false; rfb.focusOnClick = true; + // noVNC hides the browser's own cursor over the canvas and draws the REMOTE cursor in its place. + // Until the server sends a cursor shape its image is RFB.cursors.none — so nothing is drawn, the + // real cursor is hidden, and the pointer becomes invisible. Pointer coordinates are unaffected + // (measured: x 5..1108, y 11..714 against a 1109x715 session — a perfect 1:1 map), which is why + // this looked like a desync when it was only ever a visibility problem: the clicks were landing + // correctly all along. A dot is better than nothing when the server has not supplied a shape. + rfb.showDotCursor = true; rfbRef.current = rfb; rfb.addEventListener('connect', () => {