Skip to content

fix(webrtc): resolve native cursor drift with absolute positioning - #594

Merged
zortos293 merged 1 commit into
mainfrom
capy/fix-native-cursor-drift
Jul 5, 2026
Merged

fix(webrtc): resolve native cursor drift with absolute positioning#594
zortos293 merged 1 commit into
mainfrom
capy/fix-native-cursor-drift

Conversation

@zortos293

@zortos293 zortos293 commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

This PR fixes cursor drift between the native WebRTC overlay cursor and the server-side cursor by implementing the official GFN client's absolute-position input path (type 5) while the client-rendered cursor is visible. Previously, we only sent relative deltas (type 7), which accumulated rounding and clamp errors that caused visible drift. The official client sends absolute coordinates normalized to a client-defined extent to pin the server cursor to the overlay position exactly.

Input Protocol:

  • Added INPUT_MOUSE_ABS constant (type 5) to inputProtocol.ts
  • Implemented encodeMouseAbsolute with official 26-byte layout: [type 4B LE][x 2B BE][y 2B BE][reserved 2B BE][width 2B BE][height 2B BE][reserved 4B BE][timestamp 8B BE]
  • Added clampU16 helper to constrain coordinates to 0–65535
  • Updated isPartiallyReliableHidTransferEligible to include INPUT_MOUSE_ABS

Cursor Overlay:

  • Exposed isCursorVisible() on GfnCursorOverlayController to query visibility state
  • Added getAbsolutePosition() to return the clamped overlay cursor position and viewport extent in CSS pixels

WebRTC Client:

  • Added pendingMouseAbs state to track the latest absolute position awaiting transmission
  • Modified queueMouseMovement to send absolute positions when the overlay cursor is visible instead of relative deltas
  • Updated flushMouse with an absolute-position path that bypasses relative scaling and marks the server cursor at the exact overlay position
  • Extended pointer-lock entry alignment to use absolute positions when the overlay is visible, avoiding simulated relative moves

Tests:

  • Added test for absolute mouse packet encoding matching the official layout
  • Added test for v3 wrapper with clamped coordinates (negative → 0, overflow → 65535)
  • Updated PR eligibility test to include INPUT_MOUSE_ABS

Open OPE-247 OPE-247


Note

Medium Risk
Touches the live WebRTC mouse input path and coalescing behavior; incorrect coordinates could misplace the remote cursor, but scope is limited to local-cursor mode and is covered by new encoder tests.

Overview
Fixes cursor drift between the client-rendered overlay and the remote cursor by sending absolute mouse position (input type 5) while the overlay is visible, instead of only relative deltas (type 7) that accumulate rounding error.

The input layer adds INPUT_MOUSE_ABS, a 26-byte encodeMouseAbsolute layout aligned with the official GFN client, and treats absolute moves like relative moves for partially reliable HID transfer. The cursor overlay exposes isCursorVisible() and getAbsolutePosition() (clamped position plus viewport extent in CSS pixels).

GfnWebRtcClient queues pendingMouseAbs on pointer movement when the overlay is shown (latest position wins, relative residue cleared), flushes absolute packets ahead of relative scaling, and uses absolute alignment on pointer-lock entry instead of simulated relative jumps.

Reviewed by Cursor Bugbot for commit a553ab3. Configure here.

Summary by CodeRabbit

  • New Features

    • Added support for absolute mouse positioning when the on-screen cursor overlay is visible.
    • Improved cursor tracking so the pointer stays aligned with the streamed view more reliably.
  • Bug Fixes

    • Fixed cases where mouse movement could drift or desync during cursor-overlay and pointer-lock transitions.
    • Added safer handling for out-of-bounds cursor positions and invalid viewport sizes.

…rsor is visible

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@zortos293 zortos293 added the capy Generated by capy.ai label Jul 5, 2026 — with Image Capy AI
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Note: range_2119 above should be range_46d8bf45ef75.

Walkthrough

Adds absolute cursor position reporting to the cursor overlay controller, introduces a new absolute mouse input type (INPUT_MOUSE_ABS) with payload interface and encoder in the input protocol, and updates the WebRTC client to send absolute mouse packets pinning server cursor position when the overlay cursor is visible.

Changes

Absolute mouse cursor support

Layer / File(s) Summary
Overlay absolute position and visibility accessors
opennow-stable/src/renderer/src/gfn/cursorChannel.ts
Adds isCursorVisible() and getAbsolutePosition() methods exposing cursor visibility and clamped absolute cursor position within the letterboxed viewport.
Absolute mouse input protocol and encoder
opennow-stable/src/renderer/src/gfn/inputProtocol.ts, opennow-stable/src/renderer/src/gfn/inputProtocol.test.ts
Adds INPUT_MOUSE_ABS constant, MouseAbsolutePayload interface, encodeMouseAbsolute with clampU16 helper, updates HID transfer eligibility, and adds encoder/eligibility tests.
WebRTC client absolute cursor pinning
opennow-stable/src/renderer/src/gfn/webrtcClient.ts
Adds pendingMouseAbs state with resets on cleanup/install, reworks mouse flush/batching to prioritize absolute packets, and updates pointer-lock alignment and movement queuing to pin server cursor via absolute packets when overlay is visible.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Overlay as GfnCursorOverlayController
  participant Client as GfnWebRtcClient
  participant Encoder as InputEncoder
  participant Server

  Overlay->>Client: getAbsolutePosition()
  Client->>Client: queueMouseMovement sets pendingMouseAbs
  Client->>Encoder: encodeMouseAbsolute(pendingMouseAbs)
  Encoder-->>Client: absolute packet
  Client->>Server: send INPUT_MOUSE_ABS packet
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing WebRTC cursor drift by using absolute positioning.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch capy/fix-native-cursor-drift

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a553ab3. Configure here.

// Movement was already consumed by the overlay position; drop any
// stale relative residue so it cannot double-apply after this packet.
this.pendingMouseDxFloat = 0;
this.pendingMouseDyFloat = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale absolute mouse pending

Medium Severity

After the overlay cursor becomes hidden, pendingMouseAbs can still hold the last visible-cursor position. The next flushMouse run prefers that stale absolute packet, sends it, and zeroes accumulated relative deltas, so pointer-lock movement in hidden-cursor mode is dropped and the server may jump to an outdated position.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a553ab3. Configure here.

@zortos293
zortos293 merged commit c56fd5d into main Jul 5, 2026
20 of 21 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a553ab3921

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3767 to +3769
this.pendingMouseAbs = abs;
this.pendingMouseDxFloat = 0;
this.pendingMouseDyFloat = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve queued deltas when cursor modes switch

When the cursor becomes visible while a relative mouse batch is still pending (for example, a game toggles from hidden/raw input back to a menu before mouseFlushIntervalMs fires), this branch replaces the batch with an absolute packet and clears pendingMouseDxFloat/pendingMouseDyFloat, so the last raw movement is never sent. The reverse transition can also lose deltas because flushMouse prioritizes a stale pendingMouseAbs and then zeroes any relative movement queued after the cursor hid. Flush or keep separate pending state when switching between absolute and relative modes instead of clearing the other mode's movement.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@opennow-stable/src/renderer/src/gfn/webrtcClient.ts`:
- Around line 3676-3684: Clear any queued absolute mouse move before sending the
direct overlay alignment in gfn/webrtcClient.ts. In the overlayAbs branch of the
cursor handling path, make sure any existing pendingMouseAbs state or scheduled
flush is canceled/reset before calling inputEncoder.encodeMouseAbsolute and
sendReliable, so a later flush cannot replay a stale position. Use the
pendingMouseAbs handling logic around the absolute-move queue and the
direct-send branch to keep them in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 781eb9ac-c1b4-42ae-ae67-f89bfa6ac6f5

📥 Commits

Reviewing files that changed from the base of the PR and between bd3cac8 and a553ab3.

📒 Files selected for processing (4)
  • opennow-stable/src/renderer/src/gfn/cursorChannel.ts
  • opennow-stable/src/renderer/src/gfn/inputProtocol.test.ts
  • opennow-stable/src/renderer/src/gfn/inputProtocol.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.ts

Comment on lines +3676 to +3684
if (overlayAbs) {
// Overlay cursor is visible: pin the server cursor with one
// absolute packet instead of simulating relative moves.
const movePayload = this.inputEncoder.encodeMouseAbsolute({
...overlayAbs,
timestampUs: timestampUs(),
});
this.sendReliable(movePayload);
markServerCursorAt(overlayAbs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clear stale pending absolute movement before direct alignment send.

Line 3679 sends an absolute alignment packet directly, but any older pendingMouseAbs queued by Line 3767 remains scheduled and can later flush, moving the server cursor back to a stale overlay position.

Suggested fix
           if (overlayAbs) {
             // Overlay cursor is visible: pin the server cursor with one
             // absolute packet instead of simulating relative moves.
+            this.pendingMouseAbs = null;
+            this.pendingMouseDxFloat = 0;
+            this.pendingMouseDyFloat = 0;
+            this.pendingMouseTimestampUs = null;
+            this.mouseCoalescedBatchEntries = 0;
             const movePayload = this.inputEncoder.encodeMouseAbsolute({
               ...overlayAbs,
               timestampUs: timestampUs(),
             });
             this.sendReliable(movePayload);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (overlayAbs) {
// Overlay cursor is visible: pin the server cursor with one
// absolute packet instead of simulating relative moves.
const movePayload = this.inputEncoder.encodeMouseAbsolute({
...overlayAbs,
timestampUs: timestampUs(),
});
this.sendReliable(movePayload);
markServerCursorAt(overlayAbs);
if (overlayAbs) {
// Overlay cursor is visible: pin the server cursor with one
// absolute packet instead of simulating relative moves.
this.pendingMouseAbs = null;
this.pendingMouseDxFloat = 0;
this.pendingMouseDyFloat = 0;
this.pendingMouseTimestampUs = null;
this.mouseCoalescedBatchEntries = 0;
const movePayload = this.inputEncoder.encodeMouseAbsolute({
...overlayAbs,
timestampUs: timestampUs(),
});
this.sendReliable(movePayload);
markServerCursorAt(overlayAbs);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@opennow-stable/src/renderer/src/gfn/webrtcClient.ts` around lines 3676 -
3684, Clear any queued absolute mouse move before sending the direct overlay
alignment in gfn/webrtcClient.ts. In the overlayAbs branch of the cursor
handling path, make sure any existing pendingMouseAbs state or scheduled flush
is canceled/reset before calling inputEncoder.encodeMouseAbsolute and
sendReliable, so a later flush cannot replay a stale position. Use the
pendingMouseAbs handling logic around the absolute-move queue and the
direct-send branch to keep them in sync.

zortos293 added a commit that referenced this pull request Jul 5, 2026
…rsor is visible (#594) (#595)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 1 comment

Comment thread opennow-stable/src/renderer/src/gfn/webrtcClient.ts
Chizuui pushed a commit to Chizuui/OpenNOW-Modified that referenced this pull request Aug 11, 2026
…rsor is visible (OpenCloudGaming#594) (OpenCloudGaming#595)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Chizuui pushed a commit to Chizuui/OpenNOW-Modified that referenced this pull request Aug 11, 2026
…rsor is visible (OpenCloudGaming#594)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant