Files
ChatApp/apps/desktop/src/lib/screenSources.ts
T
byGalax 825160ee46 feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.

Highlights:
  - All 17 Tauri release commits ported (audio fixes, custom notification
    sound, Discord-style chat UX, profile banner, changelog page,
    Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
    fix).
  - Native napi-rs audio-loopback addon with WASAPI process-loopback:
      * EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
        never hear themselves echoed back through the capture.
      * INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
        picked window's audio is captured, not the whole OS mixer
        (Discord parity).
      * HWND -> PID resolution via Win32 GetWindowThreadProcessId.
  - Discord-style screen-source picker (thumbnail grid, screens vs
    apps tabs, live-refreshing thumbnails).
  - Hash routing fix for packaged builds (file:// can't resolve
    BrowserRouter paths).
  - Tauri sources removed (apps/desktop/src-tauri).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:35:01 +02:00

79 lines
2.7 KiB
TypeScript

// Frontend wrapper for the main-process screen-source enumerator.
// Pre-migration this called into a Rust command that captured JPEG
// thumbnails via xcap; Electron's desktopCapturer returns thumbnails
// inline as data URLs so there's no binary/base64 dual-path to juggle.
//
// The return shape here matches the ipc-types `ScreenSource` contract.
import { isTauriRuntime } from './globalShortcut';
export type ScreenSourceKind = 'screen' | 'window';
export interface ScreenSource {
/** Chromium desktopCapturer id; feed unchanged to getUserMedia's
* chromeMediaSourceId constraint when capturing this source. */
id: string;
name: string;
kind: ScreenSourceKind;
/** Thumbnail as a ready-to-use data URL (image/png). Null when
* desktopCapturer returned an empty buffer. */
thumbnailDataUrl: string | null;
/** App icon as a data URL for window sources; null for screens. */
iconDataUrl: string | null;
displayId: number | null;
}
export async function listScreenSources(): Promise<ScreenSource[]> {
if (!isTauriRuntime()) return [];
try {
return await window.electronAPI.getScreenSources();
} catch (err: unknown) {
console.warn('getScreenSources failed', err);
return [];
}
}
// Single-source high-res refresh. Re-queries desktopCapturer at 640x360
// so a detail view looks crisp without paying the full enumeration cost
// more than once per hover-debounce.
export async function captureScreenSourceThumbnail(
sourceId: string,
): Promise<string | null> {
if (!isTauriRuntime()) return null;
try {
return await window.electronAPI.getScreenThumbnail(sourceId);
} catch (err: unknown) {
console.warn('getScreenThumbnail failed', { sourceId, err });
return null;
}
}
// Legacy name retained for call-sites that expected a Blob. desktopCapturer
// already gives us a data URL — callers that need a Blob can fetch() the
// URL. This helper keeps the old signature so nothing breaks during the
// migration.
export async function captureScreenSourceThumbnailBytes(
sourceId: string,
): Promise<Blob | null> {
const url = await captureScreenSourceThumbnail(sourceId);
if (!url) return null;
try {
const res = await fetch(url);
return await res.blob();
} catch (err: unknown) {
console.warn('thumbnail fetch failed', { sourceId, err });
return null;
}
}
// Legacy single-shot API — now just delegates to listScreenSources.
export async function enumerateScreenSources(): Promise<ScreenSource[]> {
return listScreenSources();
}
// Picker tiles bind `src={thumbnailDataUrl(src)}`; we already receive a
// data URL so this is just a pass-through for API compatibility.
export function thumbnailDataUrl(src: ScreenSource): string | null {
return src.thumbnailDataUrl;
}