- electron/main.ts: treat missing sourceId on linux as portal sentinel
so the request handler never calls getSources() (which itself opens
an extra portal dialog) on fresh sessions.
- useScreenRecorder.ts: persist the synthesized portal sentinel via
selectSource() so main has the source set before getDisplayMedia.
Extract acquireLinuxPortalStream() helper to dedupe the three
duplicated getDisplayMedia constraint blocks.
- LaunchWindow.tsx: hide the screen-source selector button (and its
separator) on Linux so users cannot trigger an extra portal dialog
via the dropdown.
Previously on Linux/Wayland, starting a fullscreen recording required three
separate picker interactions: an in-app source dropdown plus two xdg-desktop-portal
dialogs. The duplicate portal dialogs were caused by:
1. resolveBrowserCaptureSource() calling desktopCapturer.getSources() in the
renderer, which itself triggers the portal on Wayland.
2. setDisplayMediaRequestHandler() in main also calling getSources(), which
triggers another portal.
3. Returning a pre-enumerated source id to Chromium, which on Wayland is stale
and forces Chromium to re-prompt via the portal during MediaStream creation.
Additionally, the editor window failed to appear after recording on some
Wayland sessions because 'ready-to-show' did not fire reliably.
Changes:
- LaunchWindow: skip the in-app source dropdown on Linux and start recording
directly; the OS portal becomes the source picker.
- useScreenRecorder: introduce a 'screen:linux-portal' sentinel source. When
set, route capture through navigator.mediaDevices.getDisplayMedia() so the
portal handles selection in a single dialog. Skip resolveBrowserCaptureSource
for the sentinel to avoid an extra getSources() call.
- electron/main: in setDisplayMediaRequestHandler, when the sentinel is set,
skip desktopCapturer.getSources() entirely and return a synthetic source so
Chromium opens the portal exactly once for the actual capture.
- electron/windows: in createEditorWindow, also call win.show() from
did-finish-load as a fallback for Linux/Wayland where ready-to-show may
not fire.
- Add Linux GPU config: use EGL (better Wayland compat), disable VAAPI
video decoder/encoder (many distros ship broken drivers causing
'vaInitialize failed' and preventing renderer from loading)
- Add ready-to-show fallback for HUD window: if did-finish-load never
fires (GPU failure), show the window after 500ms via ready-to-show
Ref #261
On Windows, the packaged app serves the renderer from http://127.0.0.1:PORT,
which causes Chromium to block file:// URLs in <video> elements — even with
webSecurity disabled. This affects all users running the packaged build.
Add a local media HTTP server (random port) that streams approved video files
with range request support for seeking. The renderer now resolves video paths
through this server instead of using file:// URLs directly.
- electron/mediaServer.ts: HTTP server with path validation against
approvedLocalReadPaths and Content-Range support
- IPC handler 'get-local-media-url' converts file paths to HTTP URLs
- resolveVideoUrl() in renderer falls back to file:// if server unavailable
- Improved <video> onError to log actual MediaError details
Fixes#244
- Extension loader with manifest validation, ID sanitization, and path traversal protection
- Permission-gated runtime API (render, cursor, audio, timeline, ui, assets, export)
- Extension host with lifecycle management (activate/deactivate)
- Marketplace client for browsing, downloading, and installing extensions
- Extension Manager UI panel with enable/disable/uninstall
- IPC bridge for extension discovery, management, and marketplace operations
- Proxy guard on window.electronAPI to block extension access to privileged IPC
- Zip-slip protection via post-extraction directory traversal validation
- Safe PowerShell invocation for Windows zip extraction (no string interpolation)
- File URL resolution with path escape prevention
Chromium (Electron 17+) requires setDevicePermissionHandler in addition
to setPermissionCheckHandler/setPermissionRequestHandler. Without it,
device-level access is silently denied on Windows 11 even though the
permission check passes, causing getUserMedia to throw NotAllowedError.
- Check systemPreferences.getMediaAccessStatus on Windows startup
- Log warnings when camera/mic access is denied so users know to check
Windows Settings > Privacy > Camera/Microphone
- Use native OS Notification on Windows instead of transparent overlay
toast window (transparent BrowserWindows are unreliable on Windows)
- Set app user model ID on Windows for proper notification grouping
- Make toast window opaque with solid background on Windows
- Only set transparent body background on macOS for update-toast window
- Refactor UpdateToastWindow to inline styles for Windows compatibility
- Add primaryAction-based button rendering in toast UI
- Add polling fallback for toast payload in UpdateToastWindow
- Add global CSS variables for brand accent color
- Add CGPreflightScreenCaptureAccess pre-check in Swift helper
- Add microphone TCC pre-flight when mic capture is requested
- Warm up TCC via desktopCapturer.getSources before spawning helper
- Request mic access from Electron before helper spawn
- Remove unreliable systemPreferences.getMediaAccessStatus pre-checks
- Add camera entitlements to plist files for webcam support
- Guard getScreen() calls behind app.isReady()
- Make tray icons lazy to avoid early screen access
- Suppress duplicate permission error alerts in renderer
- Add tray.on('click') to call focusOrCreateMainWindow() so left-click
on the systray icon opens/focuses the app on all platforms (was
previously only possible via right-click → Open).
- On Linux/Wayland, compositors often ignore focus() for security;
Electron's tray does not yet handle ProvideXdgActivationToken like
Qt apps (e.g. Telegram). Workaround: when the HUD exists but is
not focused, destroy and recreate it so the new window receives
focus (creation path works). Applied only for the HUD overlay, not
the editor.
- Reorder: show() before restore(), then moveTop(), focus() for
more reliable raise-on-restore.
Made-with: Cursor
- Use getRecordingsDir() instead of hardcoded RECORDINGS_DIR
- Fix stop order: video first, then audio save, then mux via new
mux-wgc-recording IPC
- Explicitly stop MediaStream tracks for audio resource cleanup
- Add native WASAPI audio capture (system loopback + mic) in C++ process
for sample-accurate sync; browser audio APIs crash with WGC
- Add window capture support (CreateForWindow) with window: source prefix
- Kill WGC capture process on app before-quit
- Add even-dimension guard in MFEncoder
- Fix ffmpeg asar path backslash handling on Windows
- Remove dead browser-side WGC audio code (storeWgcAudio IPC,
ScriptProcessorNode refs, stopWgcAudioCapture)