Skip to content

Releases: clintcan/macrdp

v0.9.11 — input fixes

Choose a tag to compare

@github-actions github-actions released this 01 Oct 10:05

A bug-fix release for mouse and keyboard input, both contributed by @antonmos. The default runtime path changes only in that clicks, drags and scrolls now carry the modifiers you're holding.

  • Held modifiers reach mouse events (#183). Clicks, drags and scrolls previously carried no modifiers at all, so a Shift-click selection, a Cmd-click or a Shift-drag didn't work as expected. They now carry the keys actually held, whether or not --map-ctrl-to-cmd is on.
  • Ctrl+click → Cmd+click under --map-ctrl-to-cmd (#183). A Windows-style Ctrl+click on a link opens it in a new tab instead of a secondary click. Left button only; it's decided once at button-down and kept for the drag and release, and right/middle clicks and scrolling keep the real modifiers (so Ctrl+scroll is still macOS zoom).
  • No stuck buttons or modifiers after a reconnect (#183). A connection that dropped mid-click left the button held, so every move on the next connection was a phantom drag until the next click. Each newly served connection now releases held buttons and modifiers once — not on a live resize or blank-recovery reactivation. Held modifiers are also cleared after 10 s with no input (MACRDP_MODS_RESYNC_IDLE_MS, 0 disables), so a key-up lost when the client lost focus can't turn every click into a secondary click. Caveat: a drag interrupted by a disconnect completes as a drop at the last cursor position — see docs/known-quirks.md.
  • Ctrl+, → Cmd+, under --map-ctrl-to-cmd (#184), so Ctrl+, opens an app's Preferences.

Pre-tag gates: cargo fmt --check clean on stable and nightly, cargo clippy --all-targets -- -D warnings clean, 262 tests passing.

Full history: docs/release-history.md

Artifacts

  • macrdp-v0.9.11-aarch64-apple-darwin-app.zip / .tar.gz — the standard CI build (ad-hoc signed, server only).
  • macrdp-0.9.11-usb.dmg — entitled build with both apps (server + menu-bar controller), notarized and stapled. The only build in which --enable-usb-redirection works, and the one the microphone driver was verified on.

All three are covered by SHA256SUMS.

Full Changelog: v0.9.10...v0.9.11

v0.9.10 — microphone redirection

Choose a tag to compare

@github-actions github-actions released this 30 Sep 03:12

A feature release: the client's microphone presents as a real macOS input device. Opt-in and EXPERIMENTAL; nothing changes unless you turn it on.

  • New: microphone redirection (#191). --enable-microphone-redirection (config ENABLE_MICROPHONE_REDIRECTION=1, or the menu-bar controller's Settings → Redirection → Microphone) feeds the client's mic into "macrdp Microphone" — a Core Audio device that shows up in System Settings → Sound → Input, Zoom, Teams, FaceTime and QuickTime. The client opts in too (mstsc: Local Resources → Remote audio → Settings → Record from this computer).
    • Install the driver once: it ships inside macrdp.app; install it from the controller (Install macrdp Microphone… — one admin prompt, and Core Audio restarts, briefly interrupting sound) or with macrdp.app/Contents/Resources/install-audio-plugin.sh. No entitlement needed.
    • Format: 16-bit PCM at 44.1 kHz only (no resampler yet). mstsc offers it; a client that can't gets no mic, and macrdp logs why.
    • Privacy caveat: the audio reaches the driver through a shared-memory segment that other local accounts on the Mac can read while a session is streaming (they can't write to it). On a Mac shared with other people, treat an active redirected mic as audible to them. Details in docs/macos-gotchas.md.
    • Upstream: the protocol layer is a vendored copy; upstream IronRDP's new ironrdp-rdpeai replaces it at the next dependency bump.
  • Fix: --lock-on-disconnect no longer locks underneath a client that's reconnecting (#190). A client that came back 12–22 s after leaving could be locked mid-handshake, stranding a remote user without --auto-unlock. A pending lock now holds for a recent authenticated reconnect (or briefly for a just-accepted connection), capped at 30 s so an unauthenticated peer can only delay the lock, never prevent it.

Verified: live on Windows 11 mstsc with the entitled build — the client's voice came through (−10.6 dB peak); on the final build the log confirms the 44.1 kHz format, a minute of streaming, and the shared segment wiped and released on disconnect. Controller install/remove verified. Not live-tested on FreeRDP's /microphone.

Pre-tag gates: cargo fmt --check clean on stable and nightly, cargo clippy --all-targets -- -D warnings clean, 259 tests passing.

Full history: docs/release-history.md

Artifacts

  • macrdp-v0.9.10-aarch64-apple-darwin-app.zip / .tar.gz — the standard CI build (ad-hoc signed, server only).
  • macrdp-0.9.10-usb.dmg — entitled build with both apps (server + menu-bar controller), notarized and stapled. The only build in which --enable-usb-redirection works, and the one the microphone was verified on.

All three are covered by SHA256SUMS.

Full Changelog: v0.9.9...v0.9.10

v0.9.9 — rich-text clipboard, both directions

Choose a tag to compare

@github-actions github-actions released this 29 Sep 04:58

A feature release: formatted text now survives the clipboard in both directions. On by default; --no-rich-clipboard (config RICH_CLIPBOARD=0) restores text + images only. File copy/paste is unchanged.

  • New: rich text (#189). The Windows HTML Format and Rich Text Format clipboard formats map to the Mac's public.html and public.rtf, so bold, colours, lists and links cross between Word, Outlook or a browser and Mail, Notes, TextEdit or Pages. A Windows copy is fetched as every representation macrdp can use — plain text, one rich format (HTML preferred, RTF otherwise), one image — and published as a single pasteboard item, so the app you paste into picks the richest type it understands: a Chrome "Copy Image" (bitmap plus HTML) still pastes as an image. A copy offering files always takes the file path. Images embedded inside rich text generally don't survive (Word's HTML points at local temp files).
  • Fix: Windows-copied text and images no longer echo back. They weren't marked as macrdp's own pasteboard writes, so the poller advertised them straight back to Windows, which made the client take over its own clipboard with only what was fetched. Marked now — in a separate marker from the file-paste one, so disconnect cleanup never wipes copied text.
  • Live-verified on Windows 11 (build 26100) mstsc over ZeroTier, 8/8: Word → TextEdit, Firefox/Chrome → Notes (links clickable), TextEdit → Word, Chrome Copy Image → Preview, files both ways, a Word copy pasted back into Word, plain text into Notepad/Terminal, and RICH_CLIPBOARD=0. The live test found and fixed three bugs:
    • Firefox and Word announce each copy twice, 5–180 ms apart. Restarting the fetch at once let the first announce's reply be attributed to the second fetch's request — text in the HTML slot and vice versa — so the copy arrived as plain text. The new copy's first request now waits for the superseded reply and discards it (bounded by a 5 s grace).
    • A transient format error on a re-announce: a failed representation is now retried once.
    • A trailing ^@ pasting Word text into Terminal. Word can send several trailing NULs and only one was stripped — pre-existing since the first text-clipboard commit. Text now ends at its first NUL.
  • Tip: if a paste into Word keeps bold but loses colour, that's Word's Merge Formatting paste mode, not macrdp — choose Keep Source Formatting.

Pre-tag gates: cargo fmt --check clean on stable and nightly, cargo clippy --all-targets -- -D warnings clean, 239 tests passing.

Full history: docs/release-history.md

Artifacts

  • macrdp-v0.9.9-aarch64-apple-darwin-app.zip / .tar.gz — the standard CI build.
  • macrdp-0.9.9-usb.dmg — entitled build with both apps (server + menu-bar controller), notarized and stapled. The only build in which --enable-usb-redirection works; the standard build no-ops it.

All three are covered by SHA256SUMS.

Full Changelog: v0.9.8...v0.9.9

v0.9.8 — lock while away (--lock-on-disconnect + --auto-unlock)

Choose a tag to compare

@github-actions github-actions released this 27 Sep 04:00

Lock while away: --lock-on-disconnect + --auto-unlock

A feature release for headless, remote-only Macs, plus a real --shield-primary bug fix. Everything new is opt-in; the default runtime path is unchanged.

#181 — lock when you leave, unlock when you come back (experimental, @antonmos)

--lock-on-disconnect (config LOCK_ON_DISCONNECT=1) locks the Mac about 25 seconds after the last RDP client leaves, so a remote-only Mac isn't left sitting unlocked between sessions. It needs one of the headless modes (--detach-primary, --capture-primary or --shield-primary). The delay is a cancellable buffer, so a blank-recovery reconnect coming back within it doesn't get locked under it.

It locks by starting the screen saver — the old CGSession -suspend trick no longer exists on macOS 26 — which is only a real, password-required lock when System Settings → Lock Screen → "Require password after screen saver begins" is set to Immediately. macrdp warns at startup when it isn't.

--auto-unlock (config AUTO_UNLOCK=1) types your account password into the lock screen when a client connects while the Mac is locked — the same password macrdp validated at startup and the client proved when it authenticated. It is off by default for a reason: it acts on the physical Mac. Once it fires, anyone at the machine has a live desktop, and it will undo a lock it didn't set.

Its safety properties, since a wrong password here costs a macOS attempt:

  • a shared budget of 2 password submissions per lock, below macOS's three free attempts, reset only when the unlock is confirmed;
  • it skips rather than guesses when Caps Lock is on, the active keyboard layout can't type a character, or the lock state can't be read;
  • every character is resolved to a keystroke before anything is typed.

Live-verified on a Mac mini (M1, macOS 26.5). Earlier passes, run over ZeroTier from another island with nobody at the machine, found two bugs — a detection window too short for the mini's unlock, and a false "gave up" that left the next lock impossible to undo remotely. Both were fixed before merge; the final pass ran 4/4 lock/unlock cycles cleanly.

#188 — GUI toggles

The menu-bar controller's Display tab gains a Lock while away section with both options. They're disabled and cleared unless a Detach/Blank mode is picked, and you get a warning if the lock is on without auto-unlock. The controller ships in macrdp-0.9.8-usb.dmg below; it isn't in the .app.zip or the tarball, so otherwise build it with gui/make-tray-app.sh.

#187 — --shield-primary works on a lid-closed MacBook (@antonmos)

A closed lid removes the built-in panel from the display list entirely, so the shield failed with "no physical display to shield". Because that failure only produced a log warning, it silently disabled three things at once — the shield, --restore-windows-on-disconnect and the connect-time window gather — while the RDP session itself kept working. It now degrades cleanly to an empty shield.

Known issue

With --lock-on-disconnect, a reconnect started roughly 12–22 seconds after you leave can be locked mid-handshake: a connection only counts as "back" once it has fully connected. With --auto-unlock on, it unlocks again about 3 seconds later. With the lock alone, a remote user lands on a lock screen they can't get past — which is why the GUI warns about that combination. A fix is tracked as a follow-up.

Repository hardening

.github/CODEOWNERS with required code-owner review on main, CI on fork pull requests gated on approval for all outside collaborators, and SHA pinning of GitHub Actions enforced. No runtime change.

Verified before tagging

Stable and nightly cargo fmt --check, cargo clippy --all-targets -- -D warnings, and the full test suite (212), all clean.

Artifacts

  • macrdp-v0.9.8-aarch64-apple-darwin-app.zip — the macrdp.app bundle (Apple Silicon). Ad-hoc signed, so right-click → Open on first launch (or build a locally-notarized bundle with packaging/make-app.sh). Includes the smart-card IFD handler + helpers.
  • macrdp-v0.9.8-aarch64-apple-darwin.tar.gz — the bare macrdp binary.
  • macrdp-0.9.8-usb.dmg — signed + notarized, containing both macrdp.app (with the USB host-controller entitlement, needed for --enable-usb-redirection, which no-ops in the ad-hoc .app.zip) and the macrdpController.app menu-bar controller. Drag both to /Applications; being notarized, they open without the right-click → Open step. Apple Silicon.
  • SHA256SUMS — covers all three files; shasum -a 256 -c SHA256SUMS to verify.

Full narrative notes for every release: docs/release-history.md.

Full Changelog: v0.9.7...v0.9.8

v0.9.7 — dependency & security patch (rustls, cryptoki, h2)

Choose a tag to compare

@github-actions github-actions released this 18 Sep 19:56

Dependency & security patch: rustls, cryptoki, h2

Three security advisories carried out of the dependency tree, plus one opt-in UI change. No change to the default runtime path beyond the dependency updates themselves.

To be straight about severity: none of these is known to be exploitable against macrdp for an authentication bypass — the rustls handshake transcript stays authenticated, so a network-position attacker can't alter or complete a handshake. But the headline one sits in macrdp's own TLS stack, which every client connection terminates on, so upgrading is the right call.

rustls 0.23.40 → 0.23.45 — RUSTSEC-2026-0285 (macrdp's own TLS stack)

Rustls accepted TLS 1.3 handshake messages sent at the wrong encryption level — for example a plaintext EncryptedExtensions packed into the same record as the ServerHello — where RFC 8446 §5.1 requires terminating the connection with an unexpected_message alert. The transcript is still authenticated, so the practical effect is that a peer could send in plaintext what should have been encrypted without rustls rejecting the connection. Functionally the same bug as Go's CVE-2025-61730.

This also moved the crypto provider beneath rustls: aws-lc-rs 1.16.3 → 1.18.1, aws-lc-sys 0.40.0 → 0.45.0, rustls-webpki 0.103.13 → 0.103.15.

cryptoki 0.12.0 → 0.12.1 — RUSTSEC-2026-0286

cryptoki treated the CKA_ALLOWED_MECHANISMS ulValueLen byte count as a CK_MECHANISM_TYPE element count, so a valid non-empty attribute returned through the safe Session::get_attributes API could construct an out-of-bounds slice — a crash, or disclosure of adjacent heap words. It reaches macrdp transitively, through sspi → ironrdp-connector.

h2 0.4.14 → 0.4.16 — RUSTSEC-2026-0258

chacha20 0.10.1 → 0.10.2 — a yank, not a vulnerability

0.10.0 and 0.10.1 were yanked for a correctness bug: an SSE4.1 intrinsic used in the SSE2 backend of the RNG and legacy 64-bit-counter variants (RustCrypto/stream-ciphers#580). The failure mode is an illegal instruction on an x86 CPU that has SSE2 but not SSE4.1, so macrdp's exposure was nil — Apple Silicon never compiles those backends, and every Intel Mac has SSE4.1. Cleared anyway, because a yanked crate in the lockfile is a supply-chain signal worth not carrying.

#185 — the app-switcher HUD now looks like the native Cmd+Tab (@antonmos)

The --app-switcher-hud overlay — the switcher macrdp draws on screen so the RDP client can see it — moves from its first-cut "spike" look to the system style: a light translucent slab (.popover material forced vibrant-light, continuous 28 px corner, hairline border), bigger icons that scale to fit the display (144 px for a few apps, shrinking to keep the row within ~80 % of the display width, floored at 32 px), and a subtler ring on the selected icon. Opt-in as before — nothing changes unless you pass --app-switcher-hud.

Verified before tagging

Stable and nightly cargo fmt --check, cargo clippy --all-targets -- -D warnings, and the full test suite, all clean. Because this release swaps the TLS stack underneath, the release binary was also driven through a live CredSSP/NLA handshake with a real sdl-freerdp client (scripts/test-audit-log.sh): correct password accepted, wrong password cleanly rejected, 12/12 audit assertions. cargo-deny is clean on both invocations CI runs, and the daily scheduled scan — red 2026-09-15 → 09-17 on the rustls/cryptoki pair — is green again.

Artifacts

  • macrdp-v0.9.7-aarch64-apple-darwin-app.zip — the macrdp.app bundle (Apple Silicon). Ad-hoc signed, so right-click → Open on first launch (or build a locally-notarized bundle with packaging/make-app.sh). Includes the smart-card IFD handler + helpers.
  • macrdp-v0.9.7-aarch64-apple-darwin.tar.gz — the bare macrdp binary.
  • SHA256SUMS — shasum -a 256 -c SHA256SUMS to verify.

Full narrative notes for every release: docs/release-history.md.

Full Changelog: v0.9.6...v0.9.7

v0.9.6 — bug-fix: scroll-down regression + unauthenticated accept-loop DoS

Choose a tag to compare

@github-actions github-actions released this 16 Aug 02:42

Bug-fix patch: a scroll-down regression + an unauthenticated accept-loop DoS

Two fixes over v0.9.5, both from @antonmos. If you're on v0.9.5, or you use second-client preemption on v0.9.3+, upgrade — one of these is a scroll regression, the other an unauthenticated remote DoS.

#179 — scroll-down is no longer inflated ~255× (regression introduced in v0.9.5)

v0.9.5's pin bump moved ironrdp-pdu past its own wheel-rotation decode fix (it now decodes the 9-bit WheelRotationMask as proper two's complement), but didn't delete the vendored compensation (divergence 17) that only existed to correct the old buggy decode — so it began double-correcting. Every downward tick (−1) became −255, a max-speed scroll, while upward scrolling stayed correct. That asymmetry — down runs away, up is fine — is the fingerprint (macrdp #113, in the opposite direction). Worst on the macOS Windows App's fine ±1..±3 trackpad deltas; mstsc was ~13 % too fast on the way down.

Divergence 17 is retired (the delta passes through unchanged), and a new end-to-end round-trip test (encode → decode → MouseEvent, both directions) pins the contract so a future pin bump that changes the decode fails in CI, not in a user's hands.

#180 — an unauthenticated remote client can no longer wedge the accept loop (security; latent since v0.9.3)

The second-client-preemption path awaited a candidate's negotiation unbounded — and that negotiation blocks on socket reads from a peer that has not yet authenticated. A single TCP connection to the port that completed the handshake and then sent nothing parked the accept loop: accepts were already gated off for the rest of the session, and once the live session ended the loop hung on the handoff await with no select! left — no accepts, no event drain, not even a Quit. One silent connection wedged the whole listener until the process restarted, and — like the v0.9.4 find_size DoS — the health watchdog doesn't catch it (the runtime stays healthy; only the accept loop is stuck).

Fixed with two separate bounds — a 10 s cap on the negotiation and a 750 ms cap on the post-session-end handoff wait (the window where the loop services nothing) — plus a regression test that reproduces the hang. The same PR also fixes two more preemption bugs: the server-global eviction event could be drained by, and disconnect, the winner of a takeover (stale events are now discarded before serving a winner), and the anti-storm re-preempt lockout was uncapped and permanently barred a dropped client from reclaiming its own stale session (now capped at 30 s). All three were found by the automated reviewer on upstream Devolutions/IronRDP#1476 and confirmed present in the vendored copy.

Artifacts

  • macrdp-v0.9.6-aarch64-apple-darwin-app.zip — the macrdp.app bundle (Apple Silicon). Ad-hoc signed, so right-click → Open on first launch (or build a locally-notarized bundle with packaging/make-app.sh). Includes the smart-card IFD handler + helpers.
  • macrdp-v0.9.6-aarch64-apple-darwin.tar.gz — the bare macrdp binary.
  • SHA256SUMS — shasum -a 256 -c SHA256SUMS to verify.

Full narrative notes for every release: docs/release-history.md.

Full Changelog: v0.9.5...v0.9.6

v0.9.5 — maintenance: IronRDP pin bump + divergence shrink

Choose a tag to compare

@github-actions github-actions released this 09 Aug 14:26

Maintenance: IronRDP pin bump + vendored-divergence shrink

A dependency-maintenance release. No user-facing behavior change — the default runtime path is functionally unchanged. It brings macrdp current with upstream IronRDP and retires vendored divergence rather than adding it.

What changed

The IronRDP dependency pin was bumped 879ffed8 → a5d1c682 (133 upstream commits); two vendored forks and one divergence were retired.

  • ironrdp-rdpeusb fork retired. Its lenient USB-3 caps decode landed upstream, so the crate is now the plain a5d1c682 git dependency. The USB URBDRC server (src/rdpeusb.rs) was ported to a5d1c682's split control/device PDUs — and the mstsc Xbox-controller USB redirect is live-verified on the bumped build, proving the hard-won mstsc-strict interop fixes survived the port.
  • ironrdp-async vendor dropped. This was the v0.9.4 hotfix guard for the pre-auth framing DoS; a5d1c682 already carries upstream Devolutions/IronRDP#1515's find_size hardening, so that framing DoS is now fixed upstream in the pin, not by a local vendor.
  • QOI divergence harvested (upstream always-Rgb: Devolutions/IronRDP#1335 + Devolutions/IronRDP#1341).
  • Absorbed into src/: honor-client-desktop-size, rdpsnd format selection (Devolutions/IronRDP#1359 — the crate now owns it, so macrdp's local choose_audio_format divergence is gone), and the egfx frame-ack signature (Devolutions/IronRDP#1345).

Net: −2 vendored forks, −1 divergence. vendor/ now holds 5 forks (acceptor, dvc, rdpdr, rdpeudp, server).

Verification

  • End-to-end on FreeRDP (CredSSP/audit, rdpsnd AAC, EGFX H.264) and mstsc (USB redirection).
  • The four in-tree abuse-fuzz harnesses, including the find_size decoder fuzz that motivated v0.9.4 and a new UDP-multitransport fuzz.
  • Soaked 27.4 h on a Mac mini before landing — 0 panics, 0 restarts, RSS bounded — alongside the full adversarial abuse suite. Details: docs/pin-bump-soak-results.md.

Landed via #178.

Artifacts

  • macrdp-v0.9.5-aarch64-apple-darwin-app.zip — the macrdp.app bundle (Apple Silicon). Ad-hoc signed, so on first launch right-click → Open to get past Gatekeeper (or build a locally-notarized bundle with packaging/make-app.sh + a Developer ID). Includes the smart-card IFD handler and the HUD/shield helpers.
  • macrdp-v0.9.5-aarch64-apple-darwin.tar.gz — the bare macrdp binary.
  • SHA256SUMS — checksums for both.

Verify a download: shasum -a 256 -c SHA256SUMS.

Full narrative notes for every release: docs/release-history.md.

Full Changelog: v0.9.4...v0.9.5

v0.9.4

Choose a tag to compare

@github-actions github-actions released this 05 Aug 12:01

Security fix — unauthenticated, pre-TLS remote DoS

A single malformed 2-byte fast-path frame (04 00 / 00 00 — first byte & 0b11 == 0, length 0) could spin the RDP framing reader (ironrdp-async's Framed::read_by_hint) at 100% CPU and wedge the whole server — the acceptor's connection loop stops accepting = full outage. It is reachable before TLS / CredSSP, so the connection-rate/lockout auth-guard never sees it, and the health-check watchdog misses it (single-worker spin — the trivial runtime probe still passes on the other workers).

Fix: macrdp now vendors ironrdp-async and guards read_by_hint to reject a zero-length unmatched PDU (Some((matched: false, length: 0)) → an InvalidData error → a clean per-connection rejection) instead of looping forever. This is macrdp's own upstream fix, Devolutions/IronRDP#1556, complementing upstream #1515's find_size hardening.

Verified: the exact 2-byte trigger fired ×10 leaves CPU at 0.0% (no spin, no pegged thread), the accept loop live, and each connection cleanly rejected. Found by the in-tree soak_abuse3 decoder-fuzz harness.

⚠️ All users on v0.9.3 or earlier should update to v0.9.4.

No other runtime change — the fix is a vendored guard in the framing crate, not a src/ change. See docs/release-history.md for full detail.

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 29 Jul 12:09

What's Changed

  • fix(input): implement RelMove and clamp mapped mouse coords into the display by @antonmos in #176
  • fix(server): a second client hangs instead of taking over the live session by @antonmos in #174
  • fix(h264): post-attempt heal-confirmation deadline for blank recovery by @antonmos in #175
  • fix(clipboard): don't race the client's own FormatList ack with our advertise by @antonmos in #173

Full Changelog: v0.9.2...v0.9.3

v0.9.2 — blank-recovery clean-presentation latch

Choose a tag to compare

@github-actions github-actions released this 26 Jul 10:16

v0.9.2 — blank-recovery clean-presentation latch

A point release fixing a v0.9.1 regression in the mstsc / Windows-App reconnect-blank auto-recovery. The default runtime path is unchanged — only the blank detector's disarm logic changed.

The fix

v0.9.1 (#172) replaced the older one-way disarm latch (first nonzero decode+render report → detector off for the connection) with a revocable disarm, to catch a client that reported nonzero-EDR while still black after a reactivation. That regressed a client with the mirror behaviour — one that presents the desktop fine but reports timeDiffEDR == 0 mid-session, whose short nonzero-EDR runs (3–14) never reach the "established" bar (40). The aggressive path force-dropped its working ~50-second sessions, and the auto-reconnect cookie reconnected each time → a connect/disconnect loop on a session that was rendering and interactive.

Root cause: QoE decode+render-time is bidirectionally unreliable (one client class reports nonzero-while-black, another zero-while-presenting), so no EDR threshold separates them. The signal that does: when the nonzero run occurs. The fix is a durable presented_clean latch — a sustained nonzero-EDR run seen before any recovery attempt has fired proves the client painted the desktop at connect (it is not the reconnect-blank, which is black from frame one), so the detector latches off for the connection (the pre-#172 behaviour, restored). The "before any recovery attempt" guard preserves #172: the blank client's nonzero-while-black flicker only appears after its reactivation, so the latch is withheld for it and the escalation still recovers it.

Live-verified: a 6-minute session held (previously dropped every ~50 s), and a genuine reconnect-blank still self-healed. Unit-tested. Trade-off: a genuine mid-session blackout on a cleanly-presented connection is no longer auto-recovered (an unconfirmed case) — Ctrl+Alt+Shift+R is the manual A/V-resync lever.

Known issue (deferred, not a regression)

A blank-recovery reactivation un-blanks the --capture-primary physical panel — macOS resets the panels' gamma on the reconfiguration, and the recovery's same-size Resize skips the re-assert the live-resize path runs. Present since blank-recovery reactivation shipped (v0.8.27), only noticed now that sessions stop false-dropping. A gamma-timing reblank fix was flaky and was reverted; the robust fix is a shield-window helper — --shield-primary already avoids the whole class. See docs/known-quirks.md.

Which artifact do I want?

All builds are arm64-only.

  • macrdp-0.9.2.dmg — the full build, recommended. Signed (Developer ID), notarized + stapled (passes Gatekeeper offline — no right-click-Open dance), and entitled, so generic USB redirection and camera redirection work in it. Drag-to-install. SHA-256: 8a095b6468b93155f22f9443ffdf7de76a65da60c0834d81636b600a941de7e0.
  • …-app.zip / …-.tar.gz — the CI builds of macrdp.app. Not entitled, so USB + camera redirection are inert (controller unavailable / no camera to feed); everything else — --shield-primary, virtual display, H.264, audio, clipboard, drive + smart-card redirection — works. Covered by SHA256SUMS (shasum -c SHA256SUMS).

Builds on v0.9.1 (the lockable-headless release). See docs/release-history.md for the full narrative and per-feature caveats.