Skip to content

Fix native streamer clipboard, shortcuts, stats overlay, and more -> Main - #585

Merged
zortos293 merged 12 commits into
mainfrom
dev
Jul 4, 2026
Merged

Fix native streamer clipboard, shortcuts, stats overlay, and more -> Main#585
zortos293 merged 12 commits into
mainfrom
dev

Conversation

@zortos293

@zortos293 zortos293 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Note

Medium Risk
Changes touch low-level WebRTC input encoding and Win32 capture paths used during live streaming; regressions could affect keyboard, paste, or pointer-lock behavior on Windows native sessions.

Overview
Native Windows streamer input is aligned closer with official GeForce NOW behavior: Ctrl+V triggers a host clipboard-paste event, Caps/Num/Scroll lock state is synced via a new lock-keys packet on capture and key handling, and pointer-lock capture changes are reported to the Electron side. Reliable keyboard/button traffic can be batched under protocol v3 with send-time timestamp restamping; mouse and gamepad packets get the same outer-timestamp treatment.

User-facing copy and packaging: English strings add a native cursor overlay toggle (WebRTC cursor_channel), a What's new entry in settings, and a releaseHighlights block for in-app release notes. README and the bug template now call out iOS, Android, and Switch download paths and de-emphasize in-repo iOS. Nix bumps npmDepsHash and symlinks locales into the build so packaged desktop builds pick up Crowdin files.

Reviewed by Cursor Bugbot for commit cc9d9b9. Configure here.

Summary by CodeRabbit

  • New Features
    • Added an in-app “What’s new” / release highlights modal with versioned content, “View on GitHub”, and dismiss/ack flow.
    • Added Settings for native cursor overlay and enhanced clipboard paste during streaming.
  • Bug Fixes
    • Improved native streaming input handling (clipboard paste signaling, input-capture updates, lock-keys sync, and smoother reliable/partial mouse batching).
    • Refined Escape/fullscreen behavior using a pointer-lock grace window.
    • Linux AppImage updates now disable when the install location isn’t writable.
  • Documentation
    • Refreshed README badges and rewritten Overview/Downloads information.

zortos293 and others added 9 commits June 30, 2026 21:42
…ld failures (#571)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Implement native WebRTC cursor overlay matching GFN protocol

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

* Fix WebRTC cursor overlay lock synchronization

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

* Fix WebRTC cursor-channel position sync

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

* Add WebRTC native cursor overlay setting

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

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Merge dev into main (#557)

* Revert "Merge dev into main (#557)"

This reverts commit a3c9b7a.

* chore(release): prepare v0.5.0

* Add Android and iOS options to bug report template

* Guard Linux AppImage updates on read-only installs

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kiefer <67562560+Kief5555@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Implement GFN-style clipboard paste protocol to match official client behavior

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

* Fix clipboard paste no-op when unavailable

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

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(readme): fix mobile/platform references and add download badges

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

* docs: point Android builds to Discord

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

---------

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

capy-ai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3277178-a64c-499f-a21e-1df1dbfe8f6a

📥 Commits

Reviewing files that changed from the base of the PR and between 4c3726a and ebf1f3c.

📒 Files selected for processing (8)
  • locales/en.json
  • opennow-stable/src/main/chromiumCommandLine.test.ts
  • opennow-stable/src/main/chromiumCommandLine.ts
  • opennow-stable/src/main/index.ts
  • opennow-stable/src/main/settings.ts
  • opennow-stable/src/renderer/src/App.tsx
  • opennow-stable/src/renderer/src/components/SettingsPage.tsx
  • opennow-stable/src/shared/gfn.ts
✅ Files skipped from review due to trivial changes (1)
  • locales/en.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • opennow-stable/src/main/settings.ts
  • opennow-stable/src/renderer/src/App.tsx

📝 Walkthrough

Walkthrough

This PR adds native input protocol batching, cursor overlay support, clipboard paste support, release highlights UI and IPC, Escape/pointer-lock guarding, Linux AppImage updater checks, SDP and lag tuning, shortcut normalization, and docs/build updates.

Changes

Native Input Protocol v3 Batching

Layer / File(s) Summary
Rust input encoding and packet combining
native/opennow-streamer/src/input.rs
Adds lock-keys sync encoding, v3 wrapper constants, packet restamping and combining helpers, and tests for v3 packet behavior.
Protocol event variants
native/opennow-streamer/src/protocol.rs
Adds clipboard-paste and input-capture-changed variants to the native streamer protocol enum.
GStreamer input bridge batching and dispatch
native/opennow-streamer/src/gstreamer_input.rs
Updates native window event batching, local/control forwarding, mouse packetization, gamepad timestamp restamping, and handshake/heartbeat integration.
Renderer input protocol session clock and modifiers
opennow-stable/src/renderer/src/gfn/inputProtocol.ts, opennow-stable/src/renderer/src/gfn/inputProtocol.test.ts
Adds session-relative timestamps, v3 outer-header restamping, packet combining, and Mac-aware modifier handling with tests.
Reliable input send path
opennow-stable/src/renderer/src/gfn/webrtcClient.ts, opennow-stable/src/renderer/src/gfn/webrtcClient.test.ts
Adds reliable single-input sending, coalesced pointer subsampling, mouse flush scheduling, and lag classification changes with tests.

Native Cursor Overlay

Layer / File(s) Summary
Cursor channel parsing and overlay controller
opennow-stable/src/renderer/src/gfn/cursorChannel.ts, opennow-stable/src/renderer/src/gfn/cursorChannel.test.ts
Adds cursor channel message parsing, overlay canvas rendering, cursor positioning, and related tests.
WebRTC cursor overlay integration
opennow-stable/src/renderer/src/gfn/webrtcClient.ts
Adds cursor overlay transport state, cursor channel creation and cleanup, and pointer-lock synchronization.
Cursor overlay setting and UI
opennow-stable/src/main/settings.ts, opennow-stable/src/renderer/src/components/SettingsPage.tsx, opennow-stable/src/renderer/src/App.tsx, opennow-stable/src/shared/gfn.ts, locales/en.json
Adds the nativeCursorOverlay setting, locale strings, and settings toggle row.

Clipboard Paste Support

Layer / File(s) Summary
Clipboard control protocol
opennow-stable/src/renderer/src/gfn/clipboardProtocol.ts, opennow-stable/src/renderer/src/gfn/clipboardProtocol.test.ts
Adds clipboard paste message types, payload helpers, byte validation, and protocol tests.
WebRTC clipboard API and control-channel handling
opennow-stable/src/renderer/src/gfn/webrtcClient.ts
Adds clipboard enable/refresh/paste APIs, server paste request handling, and control-channel refresh and cleanup.
Main and renderer clipboard-paste wiring
native/opennow-streamer/src/gstreamer_input.rs, native/opennow-streamer/src/protocol.rs, opennow-stable/src/shared/nativeStreamer.ts, opennow-stable/src/shared/gfn.ts, opennow-stable/src/main/nativeStreamer/manager.ts, opennow-stable/src/renderer/src/App.tsx
Adds clipboard-paste and input-capture events across the native streamer bridge, shared types, and App state and fallback handling.

Release Highlights

Layer / File(s) Summary
Release highlights core logic
opennow-stable/src/main/releaseHighlights.ts
Implements version normalization, semver comparison, GitHub fetching, cache persistence, and payload fallback handling.
Main process IPC and updater wiring
opennow-stable/src/shared/ipc.ts, opennow-stable/src/main/updater.ts, opennow-stable/src/main/index.ts, opennow-stable/src/main/settings.ts, opennow-stable/src/shared/gfn.ts
Adds release-highlights IPC channels, startup show logic, acknowledgement storage, and updater cache writes.
Preload API, modal, settings entry, and styling
opennow-stable/src/preload/index.ts, opennow-stable/src/renderer/src/App.tsx, opennow-stable/src/renderer/src/components/ReleaseHighlightsModal.tsx, opennow-stable/src/renderer/src/components/SettingsPage.tsx, opennow-stable/src/renderer/src/styles.css, locales/en.json
Exposes preload APIs, renders the release-highlights modal in App and SettingsPage, and adds CSS and locale strings.

Escape Fullscreen / Pointer-Lock Guard

Layer / File(s) Summary
Escape guard logic and tests
opennow-stable/src/main/escapeFullscreenGuard.ts, opennow-stable/src/main/escapeFullscreenGuard.test.ts
Adds the escape fullscreen guard module and unit tests for Escape detection, capture decisions, and grace timing.
Main, preload, and renderer Escape wiring
opennow-stable/src/main/index.ts, opennow-stable/src/preload/index.ts, opennow-stable/src/renderer/src/components/StreamView.tsx, opennow-stable/src/renderer/src/gfn/webrtcClient.ts
Wires the guard into main-process pointer-lock handling, preload notifications, StreamView tracking, and native Escape signaling.

Linux AppImage Updater Write-Permission Check

Layer / File(s) Summary
AppImage permission check and tests
opennow-stable/src/main/linuxUpdaterSupport.ts, opennow-stable/src/main/linuxUpdaterSupport.test.ts
Adds AppImage replacement checks, helper injection points, and tests for writable and non-writable install directories.

Streaming Quality Tuning

Layer / File(s) Summary
SDP bitrate and pacing tuning
opennow-stable/src/renderer/src/gfn/sdp.ts, opennow-stable/src/renderer/src/gfn/sdp.test.ts
Adds bitrate clamping, pacing thresholds, VQOS attributes, and updated SDP assertions.
Lag classification and diagnostics merge
opennow-stable/src/renderer/src/gfn/webrtcClient.ts, opennow-stable/src/renderer/src/gfn/webrtcClient.test.ts, opennow-stable/src/renderer/src/lib/streamDiagnostics.ts
Updates lag classification logic and diagnostics merging fields, with tests covering new reasons and thresholds.

Keyboard Shortcut Token Normalization

Layer / File(s) Summary
Shortcut token normalization
opennow-stable/src/renderer/src/shortcuts.ts, opennow-stable/src/renderer/src/shortcuts.test.ts
Adds named-key allowlisting, function-key and numpad normalizers, and updated tests for shortcut parsing.

Docs and Build Housekeeping

Layer / File(s) Summary
README and build config updates
README.md, flake.nix, opennow-stable/scripts/ensure-electron-installed.mjs
Updates badges, download copy, repository layout, npm hash, locale symlink setup, and Electron install skip handling.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainProcess
  participant ReleaseHighlights
  participant GitHubAPI
  participant Renderer
  MainProcess->>ReleaseHighlights: shouldShowReleaseHighlights(current, lastSeen)
  ReleaseHighlights->>GitHubAPI: fetch release body
  GitHubAPI-->>ReleaseHighlights: body or null
  ReleaseHighlights-->>MainProcess: ReleaseHighlightsPayload
  MainProcess->>Renderer: RELEASE_HIGHLIGHTS_SHOW payload
  Renderer->>MainProcess: RELEASE_HIGHLIGHTS_ACK
Loading
sequenceDiagram
  participant Renderer
  participant WebRTCClient
  participant NativeInputBridge
  participant GfnServer
  Renderer->>WebRTCClient: user input (key/mouse)
  WebRTCClient->>WebRTCClient: sendReliableSingleInput / mouse flush
  WebRTCClient->>GfnServer: batched/restamped v3 packets
  NativeInputBridge->>WebRTCClient: native-input-ready, native-clipboard-paste, native-input-capture-changed
  WebRTCClient->>Renderer: update nativeInputBridgeReady/nativeInputCaptureActive
Loading

Possibly related PRs

  • OpenCloudGaming/OpenNOW#519: Both PRs modify the native streamer wire protocol in native/opennow-streamer/src/protocol.rs and related event wiring.
  • OpenCloudGaming/OpenNOW#580: Both PRs modify the Linux AppImage updater logic by changing getLinuxUpdaterSupport and its tests.
🚥 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 references the main themes of the PR, especially native streamer input/clipboard and shortcut-related changes.
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 dev

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

@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: cc9d9b9c77

ℹ️ 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 +3793 to +3795
if (isCapsLockToggle) {
// Official GFN gg(): CapsLock keyup sends synthetic keydown then keyup (vk 160).
const capsVk = 0xa0;

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 Release CapsLock's tracked key on keyup

When CapsLock is pressed while streaming, the keydown path has already added/sent the real CapsLock VK (0x14) to pressedKeys; this keyup branch sends a synthetic 0xa0 down/up and returns without deleting or sending keyup for the tracked CapsLock key. After that, later CapsLock presses are treated as duplicates and ignored, and the remote session can see CapsLock as held until focus/cleanup releases all keys.

Useful? React with 👍 / 👎.

Comment on lines +500 to +504
for batch in reliable_single_batches {
for payload in finalize_reliable_single_input_packets(&batch, send_timestamp_us) {
let _ = input_channels.send_packet(&payload, false);
}
}

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 native mouse/click ordering

For the Windows native input bridge, a drained batch such as mouse-move then mouse-click is now split into reliable batches and pending_mouse_packets, but this loop sends all reliable clicks/keys before the pending mouse packets loop below. That reverses the order that was just observed locally, so clicks can land at the old remote cursor position after a move-and-click gesture; flush the pending mouse packets before the reliable batch that followed them or otherwise preserve event order.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
opennow-stable/src/renderer/src/App.tsx (1)

1536-1577: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate nativeCursorOverlay changes to the active stream. updateSetting() never applies this toggle to the live GfnWebRtcClient, and the client only reads it from constructor options, so changing it mid-stream has no effect until reconnect. Add a live setter or recreate the client when this setting changes.

🤖 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/App.tsx` around lines 1536 - 1577, The
updateSetting callback in App.tsx is missing live handling for
nativeCursorOverlay, so toggling it only updates saved settings and not the
active GfnWebRtcClient. Add this setting to the same live-propagation path used
for mouseSensitivity and clipboardPaste by either calling a new client setter
from clientRef.current or recreating the client when nativeCursorOverlay
changes. Ensure the logic is placed in updateSetting alongside the other
per-setting branches so the active stream reflects the change immediately.
🧹 Nitpick comments (8)
opennow-stable/src/renderer/src/gfn/clipboardProtocol.ts (1)

52-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider renaming clampClipboardText for clarity.

The function doesn't "clamp" (truncate to fit); it rejects (null) when the text exceeds maxBytes or is empty. A name like validateClipboardText would better reflect the reject-on-overflow semantics and avoid confusing future maintainers who might expect truncation behavior.

🤖 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/clipboardProtocol.ts` around lines 52 -
57, The helper named clampClipboardText does not actually clamp/truncate; it
validates and rejects invalid input by returning null for empty or oversized
text. Rename this function to something that reflects its reject-on-overflow
behavior, such as validateClipboardText, and update any references to the
function symbol in clipboardProtocol.ts so future readers do not expect
truncation semantics.
opennow-stable/src/renderer/src/shortcuts.test.ts (1)

33-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding boundary tests for function-key/numpad normalization.

No test exercises F1/F24 acceptance, F25 rejection, or numpad digit tokens, which would have caught the canonicalization gap in normalizeFunctionKeyToken (shortcuts.ts Lines 56-64).

✅ Example additional tests
+test("normalization accepts valid function keys and rejects out-of-range ones", () => {
+  assert.equal(normalizeShortcut("Ctrl+F24").valid, true);
+  assert.equal(normalizeShortcut("Ctrl+F25").valid, false);
+});
+
+test("normalization accepts numpad digit tokens", () => {
+  assert.equal(normalizeShortcut("Ctrl+Numpad5").key, "NUMPAD5");
+});
🤖 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/shortcuts.test.ts` around lines 33 - 36, The
shortcut normalization tests currently cover unknown tokens but miss boundary
cases in `normalizeShortcut` and `normalizeFunctionKeyToken`. Add tests that
verify function-key acceptance for `F1` and `F24`, rejection for `F25`, and
canonicalization/acceptance of numpad digit tokens. Place these near the
existing `shortcuts.test.ts` cases so the behavior of `normalizeShortcut` stays
pinned across these edge inputs.
opennow-stable/scripts/ensure-electron-installed.mjs (1)

18-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider logging on skip path.

The early exit is silent; adding a short log line would help debug CI/Nix builds when the download is unexpectedly skipped or not skipped.

♻️ Optional diagnostic log
 if (process.env.ELECTRON_SKIP_BINARY_DOWNLOAD || process.env.npm_config_electron_skip_binary_download) {
+  console.log("[ensure-electron-installed] Skipping Electron binary download (env override set).");
   process.exit(0);
 }
🤖 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/scripts/ensure-electron-installed.mjs` around lines 18 - 21,
The skip path in ensure-electron-installed.mjs exits silently when
ELECTRON_SKIP_BINARY_DOWNLOAD or npm_config_electron_skip_binary_download is
set, so add a short diagnostic log before the process.exit(0) in that branch.
Update the script’s top-level conditional to emit a clear message indicating
Electron download/install was skipped and include which skip flag triggered it,
so CI/Nix runs can confirm the control flow.
opennow-stable/src/main/releaseHighlights.ts (2)

44-57: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Hand-rolled prerelease comparator can misorder differently-tagged prereleases.

comparePrerelease only compares the trailing numeric suffix of each string (.replace(/^.*?(\d+)$/, "$1")). Per the SemVer spec, prerelease identifiers are compared component-by-component, and numeric identifiers always have lower precedence than alphanumeric ones. This implementation instead compares raw trailing digits, so e.g. "beta.1" vs "alpha.3" yields 1 - 3 < 0, incorrectly ranking beta.1 below alpha.3, whereas spec-correct ordering ranks 1.0.0-alpha < 1.0.0-beta.2 because "alpha" < "beta" lexically. This could cause shouldShowReleaseHighlights to skip showing "What's New" for a genuinely newer prerelease (or vice versa) if the project ever mixes prerelease tag names (alpha/beta/rc).

Consider using the well-maintained semver package (semver.gt/semver.compare) instead of a hand-rolled comparator to guarantee spec-correct precedence.

Please confirm whether the project ever uses mixed prerelease tag names (e.g. alpha → beta → rc) for OpenNOW releases; if versions only ever use a single prerelease scheme (e.g. always beta.N), the practical blast radius is smaller, though the correctness gap remains.

🤖 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/main/releaseHighlights.ts` around lines 44 - 57, The
prerelease ordering logic in comparePrerelease is too simplistic and can
misorder mixed prerelease tags. Replace the hand-rolled trailing-number
comparison in comparePrerelease with semver-based comparison (for example via
semver.compare/semver.gt) so prerelease identifiers are ordered
component-by-component per SemVer. Keep shouldShowReleaseHighlights using this
comparator, and ensure the new logic correctly handles alpha/beta/rc-style
version strings.

96-119: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Synchronous file I/O in the Electron main process.

readCache/writeCacheEntry use readFileSync/writeFileSync, which block the single main-process thread (and therefore all pending IPC dispatch) while the file is read/written. The cache file is small today, but consider fs/promises (readFile/writeFile) to avoid blocking the event loop, especially since writeCacheEntry is also invoked from the update-downloaded handler in updater.ts.

♻️ Suggested async rewrite
-import { readFileSync, writeFileSync, existsSync } from "node:fs";
+import { existsSync } from "node:fs";
+import { readFile, writeFile } from "node:fs/promises";
...
-function readCache(): ReleaseNotesCache {
+async function readCache(): Promise<ReleaseNotesCache> {
   try {
     const path = getCachePath();
     if (!existsSync(path)) return {};
-    return JSON.parse(readFileSync(path, "utf-8")) as ReleaseNotesCache;
+    return JSON.parse(await readFile(path, "utf-8")) as ReleaseNotesCache;
   } catch {
     return {};
   }
 }

-export function writeCacheEntry(version: string, body: string): void {
+export async function writeCacheEntry(version: string, body: string): Promise<void> {
   try {
-    const cache = readCache();
+    const cache = await readCache();
     cache[version] = body;
-    writeFileSync(getCachePath(), JSON.stringify(cache, null, 2), "utf-8");
+    await writeFile(getCachePath(), JSON.stringify(cache, null, 2), "utf-8");
   } catch (error) {
     console.warn("[ReleaseHighlights] Failed to write cache:", error);
   }
 }
🤖 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/main/releaseHighlights.ts` around lines 96 - 119, The
cache helpers in releaseHighlights.ts are blocking the Electron main process by
using synchronous file I/O in readCache, writeCacheEntry, and readCacheEntry.
Refactor these helpers to use fs/promises readFile and writeFile with
async/await, and update callers so the cache read/write path no longer blocks
IPC or the update-downloaded flow. Keep the same behavior and symbols
(readCache, writeCacheEntry, readCacheEntry, getCachePath), but make the cache
access asynchronous end-to-end.
opennow-stable/src/main/index.ts (1)

1153-1165: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Ack handler is fine; GET handler input validation should be tightened at this boundary too.

RELEASE_HIGHLIGHTS_GET forwards version straight to getReleaseHighlightsPayload without validating its shape. See the corresponding comment in releaseHighlights.ts on fetchFromGitHub for the downstream URL-construction risk this enables; consider validating/sanitizing version here as the first line of defense since this is the actual IPC trust boundary.

🤖 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/main/index.ts` around lines 1153 - 1165, Tighten the
RELEASE_HIGHLIGHTS_GET IPC boundary in ipcMain.handle so version is
validated/sanitized before calling getReleaseHighlightsPayload. The current
handler in the RELEASE_HIGHLIGHTS_GET branch only strips a leading v and
forwards the value, so add a shape check there to accept only expected
release-version strings and fall back to app.getVersion() or reject invalid
input. Keep RELEASE_HIGHLIGHTS_ACK unchanged.
opennow-stable/src/main/linuxUpdaterSupport.ts (1)

104-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Diagnostics logging bypasses shared logger.

The console.debug call here for a diagnostics feature (why AppImage updates were disabled) bypasses @shared/logger and won't be captured by the logs:export IPC channel, making it harder for users/support to retrieve this reason from exported logs.

♻️ Suggested fix
+import { logger } from "`@shared/logger`";
...
   } catch (error) {
     const reason = error instanceof Error ? error.message : String(error);
-    console.debug("[AppUpdater] AppImage install directory is not writable:", appImageDirectory, reason);
+    logger.debug("[AppUpdater] AppImage install directory is not writable:", appImageDirectory, reason);
     return false;
   }

As per coding guidelines, "Use the shared logger (@shared/logger) and export logs via logs:export IPC channel; avoid bypassing this path for diagnostics features."

🤖 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/main/linuxUpdaterSupport.ts` around lines 104 - 116, The
diagnostics message in canReplaceAppImage is using console.debug, which bypasses
the shared logging path and won’t appear in logs:export. Update the
AppImage-writability failure branch to log through `@shared/logger` instead of
console.debug, keeping the same context (appImageDirectory and reason) so the
updater diagnostics are captured by the shared log export flow.

Source: Coding guidelines

opennow-stable/src/renderer/src/App.tsx (1)

4439-4453: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate release-highlights dismiss handler.

This onDismiss block (ack-if-auto → clear payload → clear auto flag) is identical to the one at Lines 4178-4192 in the unauthenticated render path. Extract into a single handleDismissReleaseHighlights callback and reuse it in both places.

♻️ Proposed extraction
+  const handleDismissReleaseHighlights = useCallback((): void => {
+    if (releaseHighlightsIsAuto) {
+      void window.openNow.ackReleaseHighlights().catch((err) => {
+        console.warn("[App] Failed to ack release highlights:", err);
+      });
+    }
+    setReleaseHighlightsPayload(null);
+    setReleaseHighlightsIsAuto(false);
+  }, [releaseHighlightsIsAuto]);

Then use onDismiss={handleDismissReleaseHighlights} at both render sites.

🤖 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/App.tsx` around lines 4439 - 4453, The
release-highlights dismissal logic is duplicated in App’s two render paths, so
extract the shared ack-if-auto then clear-payload and clear-auto-flag sequence
into a single handleDismissReleaseHighlights callback in App and reuse it for
both ReleaseHighlightsModal onDismiss props. Keep the existing behavior intact
by moving the current window.openNow.ackReleaseHighlights call,
setReleaseHighlightsPayload(null), and setReleaseHighlightsIsAuto(false) into
that shared handler.
🤖 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 `@native/opennow-streamer/src/gstreamer_input.rs`:
- Around line 457-497: The batching in
gstreamer_input::collect_reliable_single_input_packets is being flushed too
eagerly before every non-MouseMove event, which prevents final coalescing in
finalize_reliable_single_input_packets. Update the loop over other_events so
flush_current_reliable_singles and collect_pending_mouse_move_packets only run
when pending_mouse_move is set or a MouseMove actually needs to break the batch,
while still preserving the current handling of
encode_native_window_input_payload for the non-mouse events.

In `@opennow-stable/src/main/releaseHighlights.ts`:
- Around line 78-84: The token lookup logic is duplicated in pickRuntimeToken
and updater.ts, so extract the OPENNOW_GH_TOKEN/GH_TOKEN environment-variable
resolution into a shared main-process helper module and have both call sites use
it. Keep the existing behavior of trimming values and returning the first
non-empty token, and update releaseHighlights.ts and the updater path to import
the shared helper so any future env var list changes stay in sync.

In `@opennow-stable/src/renderer/src/App.tsx`:
- Around line 343-366: The nativeCursorOverlay setting is only applied when
creating GfnWebRtcClient, so changes made during an active session are not
reflected immediately. Update App.tsx so the runtime toggle is propagated to the
current active client instance as well, using the existing GfnWebRtcClient setup
path and any state/effect wiring around sendStreamClipboardPaste or the client
creation logic to apply the new value without waiting for a stream restart.

In `@opennow-stable/src/renderer/src/gfn/cursorChannel.ts`:
- Around line 347-393: applyCursor() in cursorChannel.ts should avoid calling
refresh() when the incoming cursor is the same object as this.currentCursor and
no new image load completed, since that triggers unnecessary
rasterizeCurrentCursor()/canvas.toDataURL work on every message. Update the
logic so identical cursor shapes only reuse the existing raster and just
re-apply position/visibility state, while still refreshing after a genuinely new
cursor or a successful image decode/load. Use this.currentCursor,
imageLoadGeneration, applyCursorVisibility(), and refresh() as the key points to
gate the redraw.

In `@opennow-stable/src/renderer/src/gfn/webrtcClient.ts`:
- Around line 375-398: The subsampling in subsampleCoalescedPointerEvents is
dropping real movement by keeping only every stride-th sample, which makes
queueMouseMovement’s accumulated motion smaller than the physical input. Change
the logic in subsampleCoalescedPointerEvents so it preserves the full coalesced
delta across the batch by aggregating movementX/movementY into the output events
instead of sampling a subset, while still limiting packet count. Keep the
existing function signature and return shape, and make sure the MouseDeltaFilter
and later flush paths continue to receive the complete summed movement from the
coalesced samples.

In `@opennow-stable/src/renderer/src/shortcuts.ts`:
- Around line 56-64: normalizeFunctionKeyToken currently validates the
function-key range but returns the original token, so inputs like F01 stay
non-canonical and won’t match real key events. Update normalizeFunctionKeyToken
in shortcuts.ts to return the canonical function-key form derived from the
parsed index (for example, using the numeric value rather than the raw upper
string), and keep normalizeShortcut relying on that helper so persisted shortcut
strings and event.code comparisons use the same F1–F24 representation.

---

Outside diff comments:
In `@opennow-stable/src/renderer/src/App.tsx`:
- Around line 1536-1577: The updateSetting callback in App.tsx is missing live
handling for nativeCursorOverlay, so toggling it only updates saved settings and
not the active GfnWebRtcClient. Add this setting to the same live-propagation
path used for mouseSensitivity and clipboardPaste by either calling a new client
setter from clientRef.current or recreating the client when nativeCursorOverlay
changes. Ensure the logic is placed in updateSetting alongside the other
per-setting branches so the active stream reflects the change immediately.

---

Nitpick comments:
In `@opennow-stable/scripts/ensure-electron-installed.mjs`:
- Around line 18-21: The skip path in ensure-electron-installed.mjs exits
silently when ELECTRON_SKIP_BINARY_DOWNLOAD or
npm_config_electron_skip_binary_download is set, so add a short diagnostic log
before the process.exit(0) in that branch. Update the script’s top-level
conditional to emit a clear message indicating Electron download/install was
skipped and include which skip flag triggered it, so CI/Nix runs can confirm the
control flow.

In `@opennow-stable/src/main/index.ts`:
- Around line 1153-1165: Tighten the RELEASE_HIGHLIGHTS_GET IPC boundary in
ipcMain.handle so version is validated/sanitized before calling
getReleaseHighlightsPayload. The current handler in the RELEASE_HIGHLIGHTS_GET
branch only strips a leading v and forwards the value, so add a shape check
there to accept only expected release-version strings and fall back to
app.getVersion() or reject invalid input. Keep RELEASE_HIGHLIGHTS_ACK unchanged.

In `@opennow-stable/src/main/linuxUpdaterSupport.ts`:
- Around line 104-116: The diagnostics message in canReplaceAppImage is using
console.debug, which bypasses the shared logging path and won’t appear in
logs:export. Update the AppImage-writability failure branch to log through
`@shared/logger` instead of console.debug, keeping the same context
(appImageDirectory and reason) so the updater diagnostics are captured by the
shared log export flow.

In `@opennow-stable/src/main/releaseHighlights.ts`:
- Around line 44-57: The prerelease ordering logic in comparePrerelease is too
simplistic and can misorder mixed prerelease tags. Replace the hand-rolled
trailing-number comparison in comparePrerelease with semver-based comparison
(for example via semver.compare/semver.gt) so prerelease identifiers are ordered
component-by-component per SemVer. Keep shouldShowReleaseHighlights using this
comparator, and ensure the new logic correctly handles alpha/beta/rc-style
version strings.
- Around line 96-119: The cache helpers in releaseHighlights.ts are blocking the
Electron main process by using synchronous file I/O in readCache,
writeCacheEntry, and readCacheEntry. Refactor these helpers to use fs/promises
readFile and writeFile with async/await, and update callers so the cache
read/write path no longer blocks IPC or the update-downloaded flow. Keep the
same behavior and symbols (readCache, writeCacheEntry, readCacheEntry,
getCachePath), but make the cache access asynchronous end-to-end.

In `@opennow-stable/src/renderer/src/App.tsx`:
- Around line 4439-4453: The release-highlights dismissal logic is duplicated in
App’s two render paths, so extract the shared ack-if-auto then clear-payload and
clear-auto-flag sequence into a single handleDismissReleaseHighlights callback
in App and reuse it for both ReleaseHighlightsModal onDismiss props. Keep the
existing behavior intact by moving the current
window.openNow.ackReleaseHighlights call, setReleaseHighlightsPayload(null), and
setReleaseHighlightsIsAuto(false) into that shared handler.

In `@opennow-stable/src/renderer/src/gfn/clipboardProtocol.ts`:
- Around line 52-57: The helper named clampClipboardText does not actually
clamp/truncate; it validates and rejects invalid input by returning null for
empty or oversized text. Rename this function to something that reflects its
reject-on-overflow behavior, such as validateClipboardText, and update any
references to the function symbol in clipboardProtocol.ts so future readers do
not expect truncation semantics.

In `@opennow-stable/src/renderer/src/shortcuts.test.ts`:
- Around line 33-36: The shortcut normalization tests currently cover unknown
tokens but miss boundary cases in `normalizeShortcut` and
`normalizeFunctionKeyToken`. Add tests that verify function-key acceptance for
`F1` and `F24`, rejection for `F25`, and canonicalization/acceptance of numpad
digit tokens. Place these near the existing `shortcuts.test.ts` cases so the
behavior of `normalizeShortcut` stays pinned across these edge inputs.
🪄 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: 63f82929-17f9-43ba-aa11-84002c12c6c5

📥 Commits

Reviewing files that changed from the base of the PR and between 181b747 and c3c6086.

⛔ Files ignored due to path filters (1)
  • opennow-stable/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (39)
  • README.md
  • flake.nix
  • locales/en.json
  • native/opennow-streamer/src/gstreamer_input.rs
  • native/opennow-streamer/src/gstreamer_platform.rs
  • native/opennow-streamer/src/input.rs
  • native/opennow-streamer/src/protocol.rs
  • opennow-stable/scripts/ensure-electron-installed.mjs
  • opennow-stable/src/main/escapeFullscreenGuard.test.ts
  • opennow-stable/src/main/escapeFullscreenGuard.ts
  • opennow-stable/src/main/index.ts
  • opennow-stable/src/main/linuxUpdaterSupport.test.ts
  • opennow-stable/src/main/linuxUpdaterSupport.ts
  • opennow-stable/src/main/nativeStreamer/manager.ts
  • opennow-stable/src/main/releaseHighlights.ts
  • opennow-stable/src/main/settings.ts
  • opennow-stable/src/main/updater.ts
  • opennow-stable/src/preload/index.ts
  • opennow-stable/src/renderer/src/App.tsx
  • opennow-stable/src/renderer/src/components/ReleaseHighlightsModal.tsx
  • opennow-stable/src/renderer/src/components/SettingsPage.tsx
  • opennow-stable/src/renderer/src/components/StreamView.tsx
  • opennow-stable/src/renderer/src/gfn/clipboardProtocol.test.ts
  • opennow-stable/src/renderer/src/gfn/clipboardProtocol.ts
  • opennow-stable/src/renderer/src/gfn/cursorChannel.test.ts
  • 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/sdp.test.ts
  • opennow-stable/src/renderer/src/gfn/sdp.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.test.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.ts
  • opennow-stable/src/renderer/src/lib/streamDiagnostics.ts
  • opennow-stable/src/renderer/src/shortcuts.test.ts
  • opennow-stable/src/renderer/src/shortcuts.ts
  • opennow-stable/src/renderer/src/styles.css
  • opennow-stable/src/shared/gfn.ts
  • opennow-stable/src/shared/ipc.ts
  • opennow-stable/src/shared/nativeStreamer.ts

Comment thread native/opennow-streamer/src/gstreamer_input.rs
Comment thread opennow-stable/src/main/releaseHighlights.ts Outdated
Comment thread opennow-stable/src/renderer/src/App.tsx
Comment thread opennow-stable/src/renderer/src/gfn/cursorChannel.ts
Comment thread opennow-stable/src/renderer/src/gfn/webrtcClient.ts Outdated
Comment thread opennow-stable/src/renderer/src/shortcuts.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@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.

🧹 Nitpick comments (1)
opennow-stable/src/main/updater.ts (1)

280-290: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unnecessary type erasure via unknown cast.

UpdateDownloadedEvent extends UpdateInfo, and UpdateInfo.releaseNotes is already typed as null | string | ReleaseNoteInfo[] in electron-updater 6.8.3, so casting to { releaseNotes?: unknown } throws away existing type information for no benefit — info.releaseNotes can be used directly with the same typeof/Array.isArray narrowing.

♻️ Proposed simplification
-    const releaseNotesRaw = (info as UpdateDownloadedEvent & { releaseNotes?: unknown }).releaseNotes;
+    const releaseNotesRaw = info.releaseNotes;
🤖 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/main/updater.ts` around lines 280 - 290, The
`releaseNotesRaw` handling in `updater.ts` is erasing useful types by casting
`info` to `{ releaseNotes?: unknown }` even though `UpdateDownloadedEvent`
already carries `UpdateInfo.releaseNotes` with the correct union type. Remove
the `unknown` cast and read `releaseNotes` directly from `info`, then keep the
existing `typeof` and `Array.isArray` narrowing in the
`downloadedVersion`/`releaseNotesRaw` block so the code stays type-safe without
discarding the built-in `UpdateInfo` typing.
🤖 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.

Nitpick comments:
In `@opennow-stable/src/main/updater.ts`:
- Around line 280-290: The `releaseNotesRaw` handling in `updater.ts` is erasing
useful types by casting `info` to `{ releaseNotes?: unknown }` even though
`UpdateDownloadedEvent` already carries `UpdateInfo.releaseNotes` with the
correct union type. Remove the `unknown` cast and read `releaseNotes` directly
from `info`, then keep the existing `typeof` and `Array.isArray` narrowing in
the `downloadedVersion`/`releaseNotesRaw` block so the code stays type-safe
without discarding the built-in `UpdateInfo` typing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 226cfe59-c9de-4b07-a3f1-900f94103373

📥 Commits

Reviewing files that changed from the base of the PR and between c3c6086 and 4c3726a.

📒 Files selected for processing (15)
  • native/opennow-streamer/src/gstreamer_input.rs
  • opennow-stable/scripts/ensure-electron-installed.mjs
  • opennow-stable/src/main/githubRuntimeToken.ts
  • opennow-stable/src/main/index.ts
  • opennow-stable/src/main/linuxUpdaterSupport.ts
  • opennow-stable/src/main/releaseHighlights.ts
  • opennow-stable/src/main/updater.ts
  • opennow-stable/src/renderer/src/App.tsx
  • opennow-stable/src/renderer/src/gfn/clipboardProtocol.test.ts
  • opennow-stable/src/renderer/src/gfn/clipboardProtocol.ts
  • opennow-stable/src/renderer/src/gfn/cursorChannel.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.test.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.ts
  • opennow-stable/src/renderer/src/shortcuts.test.ts
  • opennow-stable/src/renderer/src/shortcuts.ts
✅ Files skipped from review due to trivial changes (1)
  • opennow-stable/src/main/githubRuntimeToken.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • opennow-stable/scripts/ensure-electron-installed.mjs
  • opennow-stable/src/renderer/src/gfn/clipboardProtocol.test.ts
  • opennow-stable/src/renderer/src/shortcuts.ts
  • opennow-stable/src/main/releaseHighlights.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.test.ts
  • opennow-stable/src/main/index.ts
  • opennow-stable/src/main/linuxUpdaterSupport.ts
  • opennow-stable/src/renderer/src/App.tsx
  • opennow-stable/src/renderer/src/gfn/cursorChannel.ts
  • opennow-stable/src/renderer/src/gfn/webrtcClient.ts

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@zortos293
zortos293 merged commit 840df79 into main Jul 4, 2026
20 checks passed
Chizuui pushed a commit to Chizuui/OpenNOW-Modified that referenced this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants