Skip to content

feat: Windows RDP sidecar#2

Open
Meanski wants to merge 8 commits into
mainfrom
feat/windows-rdp-sidecar
Open

feat: Windows RDP sidecar#2
Meanski wants to merge 8 commits into
mainfrom
feat/windows-rdp-sidecar

Conversation

@Meanski

@Meanski Meanski commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Implements the Windows half of the RDP feature, per `native/rdp-spike/HANDOFF.md`.

What's in here

  • sidecar.c — `_setmode(_fileno(stdout), _O_BINARY)` on Windows so the binary frame stream isn't corrupted by `\n`→`\r\n` translation.
  • native/rdp-spike/CMakeLists.txt — static Windows build via vcpkg (`freerdp:x64-windows-static`) + static CRT → `rdp-sidecar.exe`.
  • release.yml — builds the Windows sidecar (vcpkg + cmake, cached) before electron-builder; publish step is now gated to tag pushes so a `workflow_dispatch` on a branch can verify the build without creating a release.
  • electron-builder.yml — `win.extraResources` bundles `rdp-sidecar.exe`.
  • verify-sidecar.js — `win32` branch (existence + best-effort `dumpbin` DLL-dependency scan).
  • UI — new `rdpSupported` helper (darwin + win32) replaces the darwin-only gate in AddConnectionModal, Sidebar, Dashboard.

Verification

  • Local: `electron-vite build`, `tsc -p tsconfig.web.json --noEmit`, and the test suite (295 tests) pass.
  • The actual `rdp-sidecar.exe` compiles on the windows-latest CI runner (no local MSVC available). vcpkg FreeRDP target names / static link set may need a CI iteration or two to settle.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Remote Desktop (RDP) connection support is now available on Windows (not just macOS).
  • Chores

    • Updated Windows builds and app packaging to bundle the required Remote Desktop helper executable.
    • Improved release pipeline behavior to always upload build outputs as workflow artifacts, while attaching them to GitHub releases only for version tags.
  • Bug Fixes

    • Ensures the packaged Remote Desktop helper is present and usable on Windows during the build/verification process.

Implements the Windows half of the RDP feature per native/rdp-spike/HANDOFF.md.

- sidecar.c: _setmode(stdout, _O_BINARY) on Windows so \n isn't rewritten
  to \r\n and the binary frame stream isn't corrupted.
- native/rdp-spike/CMakeLists.txt: static Windows build via vcpkg
  (x64-windows-static FreeRDP) + static CRT, producing rdp-sidecar.exe.
- release.yml: build the Windows sidecar (vcpkg + cmake, cached) before
  electron-builder; only publish artifacts on a tag push so dispatch
  builds can verify the sidecars without creating a release.
- electron-builder.yml: win.extraResources bundles rdp-sidecar.exe.
- verify-sidecar.js: win32 branch (exists + best-effort dumpbin dep scan).
- UI: rdpSupported helper (darwin + win32) replaces the darwin-only gate
  in AddConnectionModal, Sidebar, and Dashboard.

Sidecar binary builds on the windows-latest CI runner (no local MSVC).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Meanski, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 10 minutes and 21 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b449cbe-82b6-44ab-96fa-ab0a9a919b8e

📥 Commits

Reviewing files that changed from the base of the PR and between 04b267d and e2c8e29.

📒 Files selected for processing (6)
  • native/rdp-spike/sidecar.c
  • src/main/ipc/rdp.ts
  • src/preload/index.d.ts
  • src/preload/index.ts
  • src/renderer/src/components/RDP/RdpView.tsx
  • src/renderer/src/lib/rdpKeymap.ts
📝 Walkthrough

Walkthrough

This PR extends the RDP sidecar from macOS-only to also support Windows. It adds a CMake build definition and Windows-specific initialization to sidecar.c (stdout binary mode, Winsock startup/cleanup), a CI pipeline step using vcpkg to build a statically linked rdp-sidecar.exe, electron-builder packaging of that exe, a dumpbin-based static-linkage verification script, and a frontend rdpSupported flag replacing all macOS-only platform checks.

Changes

Windows RDP Sidecar End-to-End

Layer / File(s) Summary
Native sidecar CMake build and Windows-specific initialization
native/rdp-spike/CMakeLists.txt, native/rdp-spike/sidecar.c
New CMakeLists.txt compiles rdp-sidecar from sidecar.c with static MSVC runtime, finds FreeRDP/FreeRDP-Client/WinPR via CMake config mode, and links Windows system libraries. sidecar.c adds Windows-only headers and calls _setmode(_fileno(stdout), _O_BINARY) in main to prevent newline translation corrupting binary frames, plus WSAStartup() and WSACleanup() for Winsock initialization.
CI: vcpkg cache, static build, and tag-only publish guard
.github/workflows/release.yml
Adds a Windows cache step for vcpkg directories keyed on CMakeLists.txt hash, then a build step that installs freerdp:x64-windows-static via vcpkg, configures/builds via CMake, and fails if rdp-sidecar.exe is not produced. Adds an artifact upload step for all runs, and an if: startsWith(github.ref, 'refs/tags/') guard to the artifact-attach step.
Electron packaging and static-linkage verification
electron-builder.yml, scripts/verify-sidecar.js
electron-builder.yml ships native/rdp-spike/rdp-sidecar.exe into the Windows app bundle via extraResources. verify-sidecar.js gains a win32 branch that checks the exe exists in packaged resources and runs dumpbin /dependents to detect leaked non-redistributable DLLs, throwing on matches and gracefully skipping when dumpbin is unavailable.
rdpSupported capability flag and UI gating
src/renderer/src/lib/platform.ts, src/renderer/src/components/ConnectionManager/AddConnectionModal.tsx, src/renderer/src/components/Dashboard/Dashboard.tsx, src/renderer/src/components/Sidebar/Sidebar.tsx
platform.ts defines RDP_PLATFORMS = ['darwin', 'win32'] and exports rdpSupported. AddConnectionModal, Dashboard, and Sidebar replace window.api.platform === 'darwin' checks with rdpSupported to gate the RDP connection type option, context menu action, and Remote Desktop sidebar section.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 Hippity-hop, a new .exe appears,
CMake and vcpkg calming all fears,
Binary stdout, no \r\n to creep,
dumpbin on watch while the DLLs sleep,
rdpSupported hops Darwin and Win—
Two platforms now let the RDP in! 🖥️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Windows RDP sidecar' directly and clearly summarizes the main change: implementing Windows RDP sidecar support, which is the core focus across all modified files including CMakeLists.txt, release.yml, sidecar.c, and platform integration.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/windows-rdp-sidecar

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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

Actionable comments posted: 3

🤖 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 @.github/workflows/release.yml:
- Around line 59-67: The Cache vcpkg FreeRDP (Windows) step uses a floating tag
`actions/cache@v4` instead of a pinned commit SHA, and the cache key does not
include the current ref or release information. To fix this, pin the
actions/cache action to a specific commit SHA instead of the floating v4 tag,
and modify the cache key by appending the GitHub ref or release tag (such as
using ${{ github.ref }}) to ensure the cache is scoped to the specific release
and prevent unintended cache reuse across different branches and builds.

In `@native/rdp-spike/sidecar.c`:
- Around line 209-214: The `_setmode(_fileno(stdout), _O_BINARY)` call on line
213 does not check the return value for failure. If this call fails, stdout
remains in text mode, which will corrupt the binary frame stream. Capture the
return value of the `_setmode` call and check if it indicates failure (typically
returns -1), and if so, log an error message and exit the program before any
frames are written to prevent protocol corruption.

In `@scripts/verify-sidecar.js`:
- Around line 64-67: The catch block currently treats all non-prefixed errors as
if dumpbin is unavailable, which masks real dependency-scan failures. Instead of
immediately logging a success message for any error that doesn't start with
'[verify-sidecar]', add logic to distinguish between actual dumpbin
unavailability (check for specific error codes like ENOENT or error messages
containing "command not found") and other legitimate errors. Only log the skip
message for confirmed unavailability cases; throw or properly handle any other
error type to prevent broken artifacts from passing verification.
🪄 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

Run ID: adcda091-5695-4794-848f-1186c158075f

📥 Commits

Reviewing files that changed from the base of the PR and between 8ac31bb and 196661b.

📒 Files selected for processing (9)
  • .github/workflows/release.yml
  • electron-builder.yml
  • native/rdp-spike/CMakeLists.txt
  • native/rdp-spike/sidecar.c
  • scripts/verify-sidecar.js
  • src/renderer/src/components/ConnectionManager/AddConnectionModal.tsx
  • src/renderer/src/components/Dashboard/Dashboard.tsx
  • src/renderer/src/components/Sidebar/Sidebar.tsx
  • src/renderer/src/lib/platform.ts

Comment on lines +59 to +67
- name: Cache vcpkg FreeRDP (Windows)
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: |
C:/vcpkg/installed
C:/vcpkg/packages
key: vcpkg-freerdp-x64-windows-static-${{ hashFiles('native/rdp-spike/CMakeLists.txt') }}

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify floating action refs in the workflow
rg -nP 'uses:\s*[^@\s]+@v[0-9]+' .github/workflows/release.yml

# Inspect cache usage and key scoping for the Windows vcpkg cache
rg -nP 'actions/cache|key:\s*vcpkg-freerdp' .github/workflows/release.yml -C 2

Repository: Meanski/noxed

Length of output: 815


Pin actions/cache and scope cache key to release refs.

Line 61 uses actions/cache@v4 (floating tag instead of pinned commit SHA), and the cache key at line 66 is not scoped to a specific ref or release tag. This allows the same cache to be reused across different builds and branches, increasing cache-poisoning exposure in a release workflow.

Recommendations:

  • Pin the action to a specific commit: actions/cache@<commit-sha> (not a floating major version)
  • Scope the cache key to the release ref or commit to prevent unintended cache reuse
🧰 Tools
🪛 zizmor (1.25.2)

[error] 61-61: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 61-61: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 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 @.github/workflows/release.yml around lines 59 - 67, The Cache vcpkg FreeRDP
(Windows) step uses a floating tag `actions/cache@v4` instead of a pinned commit
SHA, and the cache key does not include the current ref or release information.
To fix this, pin the actions/cache action to a specific commit SHA instead of
the floating v4 tag, and modify the cache key by appending the GitHub ref or
release tag (such as using ${{ github.ref }}) to ensure the cache is scoped to
the specific release and prevent unintended cache reuse across different
branches and builds.

Source: Linters/SAST tools

Comment thread native/rdp-spike/sidecar.c
Comment thread scripts/verify-sidecar.js
- sidecar.c: check _setmode return; abort if stdout can't go binary
  rather than emit a corrupted frame stream.
- verify-sidecar.js: only skip the dumpbin dep scan when dumpbin is
  genuinely absent (ENOENT); rethrow any other error so a real
  dependency-scan failure can't pass verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 21, 2026

Copy link
Copy Markdown

Deploying noxed with  Cloudflare Pages  Cloudflare Pages

Latest commit: e2c8e29
Status: ✅  Deploy successful!
Preview URL: https://5f0f4895.noxed.pages.dev
Branch Preview URL: https://feat-windows-rdp-sidecar.noxed.pages.dev

View logs

@Meanski

Meanski commented Jun 21, 2026

Copy link
Copy Markdown
Owner Author

Addressed CodeRabbit's 3 comments in d96e547:

  • sidecar.c — _setmode return value ✅ Fixed. Now checks for -1 and exits with an stderr message rather than proceeding in text mode and corrupting the frame stream.
  • verify-sidecar.js — catch masks failures ✅ Fixed. The dumpbin dep scan is now only skipped on ENOENT (dumpbin genuinely not on PATH); any other error is rethrown so a real scan failure can't pass verification.
  • release.yml — pin actions/cache to SHA + add github.ref to the key ⏭️ Skipped, intentionally:
    • Pinning only this one action to a SHA would be inconsistent with the rest of the workflow (every action, including the existing macOS Cache static FreeRDP, uses floating @v4). SHA-pinning is a sensible repo-wide hardening pass, but out of scope for this PR.
    • Adding github.ref to the key would defeat the cache: the vcpkg x64-windows-static FreeRDP build is byte-identical regardless of branch/tag, so sharing it across refs is the goal. Per-ref scoping would force a ~30 min FreeRDP rebuild on every branch and tag. The key is already correctly scoped by the CMakeLists.txt hash.

Meanski and others added 6 commits June 21, 2026 18:07
FreeRDP's vcpkg config references transitive static deps (cjson) by bare
library name, so the link failed with LNK1181: cannot open 'cjson.lib'.
Add the vcpkg static lib dir to the linker search path.

Also key the vcpkg cache on a stable marker instead of the CMakeLists
hash so editing CMake doesn't trigger a full FreeRDP rebuild.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add actions/upload-artifact so branch/dispatch builds expose the
installers (and the standalone sidecar) on the run itself — downloadable
for testing without cutting a release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On Windows, getaddrinfo fails unless WSAStartup has been called, so
freerdp_connect reported ERRCONNECT_DNS_NAME_NOT_FOUND (0x00020005) even
for valid hosts. FreeRDP's own Windows client calls WSAStartup in its
global init; mirror that — WSAStartup before connect, WSACleanup at exit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Internal RDP hosts almost always use self-signed certificates, so the
TLS handshake failed with ERRCONNECT_TLS_CONNECT_FAILED (0x00020008).
Set IgnoreCertificate + AutoAcceptCertificate so this view-only tool
accepts the cert instead of rejecting the connection (mstsc just prompts
the user to trust it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Connect at the actual pane size (even-aligned, min 640x480) rather than a
fixed 1280x800, and let the canvas scale to fill its container via
width/height 100% + object-fit contain (previously maxWidth/maxHeight
only scaled down, so a larger window left the desktop small + centered).

Live re-negotiation on resize is still TODO (FreeRDP DisplayControl).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The desktop was view-only. Now input events from the canvas are streamed
to the sidecar over stdin and replayed via FreeRDP's input API.

- sidecar.c: a WinPR reader thread reads fixed 8-byte input messages from
  stdin into a lock-guarded ring queue and signals an event added to the
  main wait loop, which drains it and calls freerdp_input_send_mouse/
  keyboard/unicode_event on the (single-threaded) transport. stdin is set
  to binary mode on Windows so messages aren't mangled.
- rdp.ts: keep stdin open after the password; add the rdp:input channel
  that encodes events to 8-byte messages.
- preload: rdp.input() fire-and-forget send.
- renderer: capture mouse (move/buttons/wheel, coords mapped through the
  object-fit letterbox) and keyboard (event.code -> RDP set-1 scancode in
  lib/rdpKeymap.ts) on the canvas. Key mapping lives in JS so the sidecar
  stays generic.

Live resize re-negotiation (FreeRDP DisplayControl) is still TODO.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant