Files
backspace/packages/web/src/platform/electron.d.ts
T
devsyncwrld d525bbb8c5 feat(desktop): capture system audio with process isolation
Electron's audio: 'loopback' captures the whole output mix, this app's own
playback included — so everyone else's voices went back out inside the share
and each listener heard themselves. Not acoustic echo but a digital copy of
the output, which is why headphones never helped, and why shareAudio already
defaulted to off in the desktop app.

Electron offers no way to exclude our own audio: the docs allow only
'loopback' or 'loopbackWithMute', and the handler discards the renderer's
constraints (restrictOwnAudio never arrives). electron-native-screenshare does
it at the OS level — WASAPI process loopback on Windows — capturing only the
shared window when its pid resolves, and otherwise everything except us.

The module hands raw PCM to the main process, so it crosses IPC and is
scheduled onto a running cursor in Web Audio to become a MediaStreamTrack,
published as ScreenShareAudio. Loading is optional and failure degrades to a
silent share rather than blocking the app or the screen share.

The browser path is untouched: Chrome honours restrictOwnAudio and has no echo.

Verified by typecheck (web and Electron main) and the web suite. The audio path
itself cannot be exercised here — no Windows, no Electron, no audio device.
2026-09-01 00:56:34 -03:00

101 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Type augmentation for the Electron IPC bridge exposed by preload.ts */
// Recovery mode types (Task 11)
type RecoveryReasonCode = 'load-failed' | 'render-gone' | 'unresponsive' | 'renderer-stalled';
type UpdateState = 'idle' | 'checking' | 'downloading' | 'downloaded' | 'error';
interface RecoveryState {
mode: 'normal' | 'recovery';
reason: { code: RecoveryReasonCode; detail: string } | null;
updateState: UpdateState;
updateVersion: string | null;
lastUpdateError: { message: string; code: string | null; at: number } | null;
lastCheckResult: 'up-to-date' | 'failed' | null;
}
type RecoveryAction =
| 'reload'
| 'check-update'
| 'install-update'
| 'change-instance'
| 'open-releases'
| 'quit';
interface ElectronScreenSource {
id: string; // "screen:0:0" or "window:12345:0"
name: string; // "Entire Screen" or "Firefox"
thumbnailDataUrl: string; // PNG data URL at 320×180
appIconDataUrl: string | null; // App icon (windows only)
isScreen: boolean; // true = display, false = window
}
interface BackspaceElectronAPI {
// Platform info
platform: NodeJS.Platform;
// Window controls
minimize: () => void;
maximize: () => void;
close: () => void;
// Notifications & badge
showNotification: (title: string, body: string) => void;
setBadgeCount: (count: number) => void;
// Auto-update (Task 2.1)
onUpdateAvailable: (callback: (info: { version: string }) => void) => void;
onUpdateDownloaded: (callback: (info: { version: string }) => void) => void;
onUpdateError: (callback: (error: { message: string; releaseUrl: string }) => void) => void;
installUpdate: () => void;
checkForUpdates: () => void;
getVersion: () => Promise<string>;
// Window focus (Task 2.2)
onWindowFocusChange: (callback: (focused: boolean) => void) => void;
// Deep linking (Task 2.3)
onDeepLink: (callback: (url: string) => void) => void;
// Instance-origin-aware URL routing
setConnectedOrigins: (origins: string[]) => void;
onOpenInternalRoute: (callback: (path: string) => void) => (() => void);
// Screen share picker coordination
onScreenShareSources: (callback: (sources: ElectronScreenSource[]) => void) => void;
selectScreenSource: (sourceId: string | null, shareAudio?: boolean) => void;
onNativeAudioData?: (
callback: (data: ArrayBuffer, meta: { sampleRate: number; channels: number; bitsPerSample: number; isFloat: boolean }) => void,
) => () => void;
onNativeAudioUnavailable?: (callback: () => void) => () => void;
stopNativeAudio?: () => void;
// Instance URL management
getInstanceUrl: () => Promise<string | null>;
setInstanceUrl: (url: string) => Promise<void>;
clearInstanceUrl: () => Promise<void>;
// Auto-launch settings
getAutoLaunchSettings: () => Promise<{ openAtLogin: boolean; startMinimized: boolean }>;
setAutoLaunchSettings: (settings: { openAtLogin?: boolean; startMinimized?: boolean }) =>
Promise<{ openAtLogin: boolean; startMinimized: boolean }>;
// Activity detection (game/app process scanning)
onActivityDetected: (callback: (activity: unknown) => void) => (() => void);
getCurrentActivity: () => Promise<unknown>;
// Keybind support
syncKeybinds: (keybinds: Array<{ actionId: string; keys: number[]; mouseButton?: number }>) => void;
onKeybindAction: (callback: (action: { actionId: string; pressed: boolean }) => void) => (() => void);
onAccessibilityStatus: (callback: (status: { trusted: boolean }) => void) => (() => void);
onKeybindHookError: (callback: (error: { message: string }) => void) => (() => void);
checkAccessibility: () => Promise<boolean>;
// Recovery mode bridge (Task 11)
rendererReady: () => void;
getRecoveryState: () => Promise<RecoveryState>;
onRecoveryStateChanged: (cb: (state: RecoveryState) => void) => () => void;
recoveryAction: (action: RecoveryAction) => void;
}
interface Window {
backspace?: BackspaceElectronAPI;
}