Skip to content

fix(linux): stabilize portal source selection HUD#626

Merged
meiiie merged 1 commit into
mainfrom
fix/linux-selection-window-jitter
May 29, 2026
Merged

fix(linux): stabilize portal source selection HUD#626
meiiie merged 1 commit into
mainfrom
fix/linux-selection-window-jitter

Conversation

@meiiie
Copy link
Copy Markdown
Collaborator

@meiiie meiiie commented May 29, 2026

Summary

  • lock HUD fallback resizing while Linux portal source selection is active
  • keep non-passthrough HUD bounds stable so source picker / recording controls do not jitter away from the cursor
  • add preload and renderer IPC wiring for the source-selection interaction state

Fixes #623.

Research notes

  • XDG Desktop Portal ScreenCast starts source selection through an async portal dialog, so the app should not resize or reposition its HUD fallback while that selection flow is active.
  • Electron mouse passthrough support differs by platform; on Linux this code already uses a non-passthrough fallback, so this patch keeps that fallback compact/stable instead of trying to simulate true passthrough.

Verification

  • npx vitest run electron/hudOverlayBounds.test.ts src/hooks/useScreenRecorder.test.ts electron/ipc/register/sourceMapping.test.ts --reporter=verbose
  • npx biome check electron/electron-env.d.ts electron/hudOverlayBounds.test.ts electron/hudOverlayBounds.ts electron/preload.ts electron/windows.ts src/hooks/useScreenRecorder.test.ts src/hooks/useScreenRecorder.ts
  • npx tsc --noEmit
  • npm test

Risk

  • Needs confirmation on a real Ubuntu/Fedora Wayland AppImage setup because Windows CI cannot exercise xdg-desktop-portal window-manager behavior.

Summary by CodeRabbit

  • New Features

    • Added source-selection mode for the HUD that forces interactive behavior and collapses/resizes the overlay appropriately.
    • HUD locks automatically during display/source selection on Linux portal flows.
  • Bug Fixes & Improvements

    • Fallback resizing now respects source-selection and recording states.
    • Recording start now tolerates IPC/set-state failures without blocking capture.
  • Tests

    • Improved test coverage for HUD resizing and source-selection behaviors.

Review Change Stack

@meiiie
Copy link
Copy Markdown
Collaborator Author

meiiie commented May 29, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

Adds a source-selection-active IPC method and state to lock the HUD during display/source selection. Main process passthrough and fallback logic now respect the lock; renderer hook activates/deactivates the lock during recording display selection. Tests and type declarations updated accordingly.

Changes

HUD Source Selection Locking

Layer / File(s) Summary
IPC API contract and preload bridge
electron/electron-env.d.ts, electron/preload.ts
Adds hudOverlaySetSourceSelectionActive(active: boolean) to electronAPI and implements the preload bridge and minor listener/formatting edits.
Main process state & passthrough handling
electron/windows.ts
Introduces hudOverlaySourceSelectionActive, updates setHudOverlayMousePassthrough to force non-passthrough when source selection is active, collapses fallback on activation, and adds an IPC listener to toggle the flag and reapply passthrough.
Fallback resize gating and tests
electron/hudOverlayBounds.ts, electron/hudOverlayBounds.test.ts
shouldResizeHudOverlayFallback accepts an interactionLocked flag and prevents resizing when true; test added to verify non-resizable fallback during source selection.
Renderer-side locking and recorder integration
src/hooks/useScreenRecorder.ts, src/hooks/useScreenRecorder.test.ts
Adds shouldLockHudDuringDisplaySelection predicate, HUD lock state and idempotent setter in startRecording that calls the preload API, wraps setRecordingState calls in try/catch, clears lock in finally, and adds tests for the predicate.

Sequence Diagram

sequenceDiagram
  participant Renderer as useScreenRecorder
  participant Preload as electronAPI (preload)
  participant Main as MainProcess (windows.ts)

  Renderer->>Preload: hudOverlaySetSourceSelectionActive(true)
  Preload->>Main: ipcMain 'hud-overlay-set-source-selection-active' true
  Main->>Main: set hudOverlaySourceSelectionActive = true\ncollapse fallback, setIgnoreMouseEvents(false)
  Renderer->>Preload: hudOverlaySetSourceSelectionActive(false)
  Preload->>Main: ipcMain 'hud-overlay-set-source-selection-active' false
  Main->>Main: restore passthrough via setHudOverlayMousePassthrough(lastIntent)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Checked

Poem

🐰 I nudged the HUD to sit quite still,
While portals open on Linux hill.
Source selection locks the overlay tight,
So captures start calm and just right.
A little hop, and all is well tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title clearly and concisely summarizes the main change: stabilizing the portal source selection HUD on Linux to fix jitter issues.
Description check ✅ Passed The description covers key aspects (summary, motivation, verification steps) but is missing several template sections including Type of Change checkboxes and explicit Testing Guide.
Linked Issues check ✅ Passed The PR directly addresses issue #623 by implementing the core requirement: stabilizing the HUD during Linux portal source selection to prevent jitter.
Out of Scope Changes check ✅ Passed All changes are focused on fixing the HUD jitter issue for Linux portal source selection; no unrelated modifications detected.

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

✨ 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/linux-selection-window-jitter

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
electron/preload.ts (1)

904-904: 💤 Low value

Verify the null coalescing addition is intentional.

The change from result.value to (result.value ?? null) makes null-handling more explicit. If result.value can be undefined, this ensures null is consistently returned instead. Confirm this subtle behavior clarification was intended.

🤖 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 `@electron/preload.ts` at line 904, The change wraps result.value with nullish
coalescing in the return expression (return result?.success ? (result.value ??
null) : null), which alters behavior by converting undefined to null; decide
whether you intended to normalize undefined to null—if yes, keep (result.value
?? null) and update the surrounding type annotations and any callers to expect
null instead of undefined; if not, revert to returning result.value directly
(return result?.success ? result.value : null) so existing callers that rely on
undefined aren’t affected. Ensure whichever choice you make is reflected in the
function's return type and add or update tests calling this return path.
src/hooks/useScreenRecorder.ts (1)

1651-1651: ⚡ Quick win

Inconsistent error handling for native recording path.

Line 2017 wraps setRecordingState(true) in try/catch for the browser recording path, but this native recording path at line 1651 does not. If the IPC call exists but fails, it could throw and break recording start. For consistency and resilience, consider wrapping this call similarly:

+try {
+	await window.electronAPI?.setRecordingState(true);
+} catch (stateError) {
+	console.warn("Failed to notify main process that recording started:", stateError);
+}
-window.electronAPI?.setRecordingState(true);

Note: Other setRecordingState calls (lines 1174, 1293, 1389, 2030, 2158, 2179) also lack try/catch, but those are stop/cleanup calls where failure is less critical than start.

🤖 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/hooks/useScreenRecorder.ts` at line 1651, Wrap the native recording
path's IPC call window.electronAPI?.setRecordingState(true) in a try/catch like
the browser-start path to avoid throwing if the IPC exists but fails; catch the
error, log it using the existing logger (or console.error) with context (e.g.,
"setRecordingState(true) failed during native start") and proceed without
letting the exception abort the recording start flow so the start sequence
remains resilient.
🤖 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 `@electron/preload.ts`:
- Line 904: The change wraps result.value with nullish coalescing in the return
expression (return result?.success ? (result.value ?? null) : null), which
alters behavior by converting undefined to null; decide whether you intended to
normalize undefined to null—if yes, keep (result.value ?? null) and update the
surrounding type annotations and any callers to expect null instead of
undefined; if not, revert to returning result.value directly (return
result?.success ? result.value : null) so existing callers that rely on
undefined aren’t affected. Ensure whichever choice you make is reflected in the
function's return type and add or update tests calling this return path.

In `@src/hooks/useScreenRecorder.ts`:
- Line 1651: Wrap the native recording path's IPC call
window.electronAPI?.setRecordingState(true) in a try/catch like the
browser-start path to avoid throwing if the IPC exists but fails; catch the
error, log it using the existing logger (or console.error) with context (e.g.,
"setRecordingState(true) failed during native start") and proceed without
letting the exception abort the recording start flow so the start sequence
remains resilient.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 582829c1-008e-4e51-8585-b18ba36019fa

📥 Commits

Reviewing files that changed from the base of the PR and between b46ce42 and 82a94db.

📒 Files selected for processing (7)
  • electron/electron-env.d.ts
  • electron/hudOverlayBounds.test.ts
  • electron/hudOverlayBounds.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/hooks/useScreenRecorder.test.ts
  • src/hooks/useScreenRecorder.ts

@meiiie meiiie force-pushed the fix/linux-selection-window-jitter branch from 82a94db to c0627d0 Compare May 29, 2026 16:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
electron/windows.ts (1)

314-333: ⚡ Quick win

Drop the redundant interactionLocked argument in shouldResizeHudOverlayFallback call

electron/windows.ts passes hudOverlaySourceSelectionActive as interactionLocked, but the hudOverlaySourceSelectionActive early return runs before this call—so interactionLocked is always false on this runtime path (the interactionLocked=true behavior is only covered by electron/hudOverlayBounds.test.ts). Simplify by omitting the 3rd argument and relying on the default.

♻️ Proposed simplification
 	const mousePassthroughSupported = isHudOverlayMousePassthroughSupported();
 	if (!mousePassthroughSupported) {
-		if (
-			shouldResizeHudOverlayFallback(
-				mousePassthroughSupported,
-				hudOverlayRecordingActive,
-				hudOverlaySourceSelectionActive,
-			)
-		) {
+		// Source selection is handled by the early return above, so the lock
+		// flag is always false here; resizing is gated only by recording state.
+		if (shouldResizeHudOverlayFallback(mousePassthroughSupported, hudOverlayRecordingActive)) {
 			setHudOverlayFallbackExpanded(!ignore);
 		}
 		hudOverlayWindow.setIgnoreMouseEvents(false);
 		return;
 	}
🤖 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 `@electron/windows.ts` around lines 314 - 333, The call to
shouldResizeHudOverlayFallback in electron/windows.ts is passing
hudOverlaySourceSelectionActive as the third argument even though an early
return above makes that value irrelevant; remove the third argument and call
shouldResizeHudOverlayFallback(mousePassthroughSupported,
hudOverlayRecordingActive) so the function uses its default for
interactionLocked; update the invocation near
hudOverlayWindow.setIgnoreMouseEvents(false) and confirm the function signature
of shouldResizeHudOverlayFallback supports the default parameter.
🤖 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 `@electron/windows.ts`:
- Around line 314-333: The call to shouldResizeHudOverlayFallback in
electron/windows.ts is passing hudOverlaySourceSelectionActive as the third
argument even though an early return above makes that value irrelevant; remove
the third argument and call
shouldResizeHudOverlayFallback(mousePassthroughSupported,
hudOverlayRecordingActive) so the function uses its default for
interactionLocked; update the invocation near
hudOverlayWindow.setIgnoreMouseEvents(false) and confirm the function signature
of shouldResizeHudOverlayFallback supports the default parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dacc3f35-d882-4536-85e0-5e5f05bbcfce

📥 Commits

Reviewing files that changed from the base of the PR and between 82a94db and c0627d0.

📒 Files selected for processing (7)
  • electron/electron-env.d.ts
  • electron/hudOverlayBounds.test.ts
  • electron/hudOverlayBounds.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/hooks/useScreenRecorder.test.ts
  • src/hooks/useScreenRecorder.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • electron/electron-env.d.ts
  • electron/hudOverlayBounds.ts
  • electron/preload.ts
  • src/hooks/useScreenRecorder.test.ts
  • electron/hudOverlayBounds.test.ts
  • src/hooks/useScreenRecorder.ts

@meiiie meiiie merged commit be8c97d into main May 29, 2026
3 checks passed
@meiiie meiiie deleted the fix/linux-selection-window-jitter branch May 29, 2026 16:30
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.

Recording window jitters when starting recording

1 participant