Skip to content

fix(main): permission handler was denying whole-app loopback capture - #100

Merged
OmikronApex merged 1 commit into
mainfrom
fix/loopback-capture-permission
Jul 11, 2026
Merged

OmikronApex merged 1 commit into
mainfrom
fix/loopback-capture-permission

Conversation

@OmikronApex

@OmikronApex OmikronApex commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Problem

Tester report (2026-07-11, Focusrite USB ASIO): song-preview audio silent / on the wrong device with ASIO output. Log showed [renderer-bus] loopback capture unavailable — NotAllowedError with no display-media handler activity.

Root cause: getDisplayMedia (the renderer-bus whole-app loopback capture, #98) arrives at setPermissionRequestHandler as a media request with empty mediaTypes. The audio-only rule (empty ⇒ deny) rejected it before setDisplayMediaRequestHandler ever ran. Loopback went sticky-unavailable; song_preview's private <audio> element has no surface-tap fallback → previews played to the default WASAPI endpoint.

Fix

  • Allow media permission requests unless they explicitly include 'video' (camera stays blocked — getDisplayMedia's video is the app's own frame; real camera requests always carry mediaTypes:['video']).
  • Mirror the policy in setPermissionCheckHandler.
  • [asio-diag] logging on every permission deny path and in the display-media handler (received / granted / DENIED with reason) so any future denial names its stage in tester logs.

Verification

Packaged portable build (fix16), ASIO4ALL:

[asio-diag] display-media request received (loopback capture)
[asio-diag] display-media granted: frame audio + screen video
[renderer-bus] engaged: app loopback → engine bus
[asio-diag] engine: ... "busEnabled":true,"busFlowing":true

Tester confirms preview audio now on ASIO + streamer WASAPI output.

Companion core PR: raw-audio capture constraints (tin-can quality fix) in feedBack.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved screen-sharing requests by granting access only when a valid screen source and application window are available.
    • Added clearer handling when screen capture sources cannot be found or loaded.
    • Updated media permissions to allow non-video requests while continuing to restrict video access and other blocked requests.
    • Improved diagnostic logging for display capture and permission decisions.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f4c21d75-8c75-4798-81ac-1630b8599815

📥 Commits

Reviewing files that changed from the base of the PR and between ef0c988 and bcbb0e3.

📒 Files selected for processing (1)
  • src/main/main.ts

📝 Walkthrough

Walkthrough

Electron display capture now grants only available screen sources with main-frame audio, denies invalid requests, and logs outcomes. Renderer media permissions deny video requests while allowing non-video requests, with centralized diagnostic logging and existing origin restrictions retained.

Changes

Desktop capture and runtime diagnostics

Layer / File(s) Summary
Display capture request handling
src/main/main.ts
Display-media requests enumerate screen sources, grant the first source when the main window exists, and deny unavailable or failed requests with diagnostics.
Renderer media permission policy
src/main/main.ts
Async and synchronous permission handlers deny video media, allow other media types, and log deny-list or non-renderer-origin decisions.

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

Sequence Diagram(s)

sequenceDiagram
  participant Renderer
  participant PermissionHandler
  participant DisplayMediaHandler
  participant desktopCapturer
  participant MainWindow
  Renderer->>PermissionHandler: request media permission
  PermissionHandler-->>Renderer: allow non-video or deny video
  Renderer->>DisplayMediaHandler: request display media
  DisplayMediaHandler->>desktopCapturer: enumerate screen sources
  desktopCapturer-->>DisplayMediaHandler: return sources
  DisplayMediaHandler->>MainWindow: provide screen video and main-frame audio
  DisplayMediaHandler-->>Renderer: deny when window or sources are unavailable
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing the permission handler so whole-app loopback capture is no longer denied.
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 fix/loopback-capture-permission

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

getDisplayMedia (renderer-bus whole-app loopback capture) rides the
'media' permission request with EMPTY mediaTypes. The audio-only rule
denied it, so the display-media handler never ran and exclusive/ASIO
output lost all page audio (song previews, element-song fallback).

Allow media requests unless they explicitly ask for 'video' (camera
stays blocked; getDisplayMedia video is the app's own frame), mirror
the policy in the permission-check handler, and add [asio-diag] logs
to every deny path plus the display-media handler so future denials
name their stage in tester logs.

Verified packaged: display-media granted → renderer-bus engaged,
engine busEnabled/busFlowing=true on ASIO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@OmikronApex
OmikronApex force-pushed the fix/loopback-capture-permission branch from ef0c988 to bcbb0e3 Compare July 11, 2026 23:44

@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)
src/audio/AudioEngine.h (1)

624-634: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comment claims dest is cleared first, but implementation never clears it.

The comment says drains "into dest (stereo, bus gain applied, dest cleared first)", but pullRendererBus's implementation (AudioEngine.cpp) never calls dest.clear(). On the success path this is harmless since the write loop overwrites every sample in [0, numSamples). On the gated/early-return paths (disabled, priming, underflow, undersized scratch), dest is left with whatever stale data it held from the previous block — safe today only because every call site additionally gates on rendererFrames > 0 before reading rendererBusPullScratch. If a future caller reads the buffer without that gate, this comment would incorrectly imply it's safe to do so.

📝 Suggested comment fix
     // Shared consumer step for the duplex and split output paths: drain one
     // block from the renderer-bus ring into `dest` (stereo, bus gain applied,
-    // dest cleared first). Returns numSamples on success, 0 when gated
+    // dest fully overwritten on success, left untouched on the gated paths
+    // below — callers must check the return value before reading `dest`).
+    // Returns numSamples on success, 0 when gated
     // (disabled, priming, underflow, scratch undersized). Single consumer —
🤖 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 `@src/audio/AudioEngine.h` around lines 624 - 634, Update the documentation for
pullRendererBus and rendererBusPullScratch to remove the claim that dest is
cleared first, or revise it to accurately describe the current behavior on gated
and early-return paths. Keep the implementation unchanged unless needed to make
the documented contract accurate.
🤖 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 `@src/audio/AudioEngine.h`:
- Around line 624-634: Update the documentation for pullRendererBus and
rendererBusPullScratch to remove the claim that dest is cleared first, or revise
it to accurately describe the current behavior on gated and early-return paths.
Keep the implementation unchanged unless needed to make the documented contract
accurate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5030cae-ea7f-4b19-87f5-94c663578a60

📥 Commits

Reviewing files that changed from the base of the PR and between c71aa7c and ef0c988.

📒 Files selected for processing (5)
  • src/audio/AudioEngine.cpp
  • src/audio/AudioEngine.h
  • src/main/audio-bridge.ts
  • src/main/main.ts
  • src/main/preload.ts

@OmikronApex
OmikronApex merged commit ce242a6 into main Jul 11, 2026
4 checks passed
@OmikronApex
OmikronApex deleted the fix/loopback-capture-permission branch July 13, 2026 21:34
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