// Frontend wrapper for the Rust `enumerate_screen_sources` command. Falls // back to an empty list outside the Tauri runtime so a browser-only dev // build (pnpm vite:dev in Chrome without Tauri) degrades gracefully to // "nothing to show" rather than throwing. import { isTauriRuntime } from './globalShortcut'; export type ScreenSourceKind = 'screen' | 'window'; export interface ScreenSource { /** Chromium-format source id ("screen::0" / "window::0"). */ id: string; name: string; kind: ScreenSourceKind; /** Base64-encoded JPEG without a data-URL prefix. Null when capture failed. * Kept under `thumbnailPng` key for rollout stability — the server-side * format switched from PNG to JPEG for payload size, but the field name * preserves the wire contract during the transition. */ thumbnailPng: string | null; width: number; height: number; } // Fast, metadata-only list. The picker uses this first so names show up // immediately; thumbnails stream in via captureScreenSourceThumbnail below. export async function listScreenSources(): Promise { if (!isTauriRuntime()) return []; try { const { invoke } = await import('@tauri-apps/api/core'); const raw = await invoke('list_screen_sources'); return raw ?? []; } catch (err: unknown) { console.warn('list_screen_sources failed', err); return []; } } // Single-source thumbnail capture (legacy base64 variant). Callers should // prefer `captureScreenSourceThumbnailBytes` below — it ships raw JPEG // bytes over IPC so the main thread avoids both the base64 decode AND // the JSON parse overhead of a long string result. Kept for fallback. export async function captureScreenSourceThumbnail( sourceId: string, ): Promise { if (!isTauriRuntime()) return null; try { const { invoke } = await import('@tauri-apps/api/core'); const result = await invoke('capture_screen_source_thumbnail', { sourceId, }); return result ?? null; } catch (err: unknown) { console.warn('capture_screen_source_thumbnail failed', { sourceId, err }); return null; } } // Binary-IPC variant. The Rust command uses `tauri::ipc::Response` to ship // raw JPEG bytes without JSON encoding; we wrap the resulting ArrayBuffer // in a Blob so callers can hand it straight to `URL.createObjectURL` — // never touches base64 on either side. Returns null when the Rust side // produced zero bytes (capture failed, source vanished). export async function captureScreenSourceThumbnailBytes( sourceId: string, ): Promise { if (!isTauriRuntime()) return null; try { const { invoke } = await import('@tauri-apps/api/core'); const result = await invoke( 'capture_screen_source_thumbnail_bytes', { sourceId }, ); if (!result || result.byteLength === 0) return null; return new Blob([result], { type: 'image/jpeg' }); } catch (err: unknown) { console.warn('capture_screen_source_thumbnail_bytes failed', { sourceId, err }); return null; } } // Legacy single-shot variant. Captures everything serially on the Rust side // before returning. Prefer listScreenSources + captureScreenSourceThumbnail // for user-facing flows — they feel 5–10× more responsive in practice. export async function enumerateScreenSources(): Promise { if (!isTauriRuntime()) return []; try { const { invoke } = await import('@tauri-apps/api/core'); const raw = await invoke('enumerate_screen_sources'); return raw ?? []; } catch (err: unknown) { console.warn('enumerate_screen_sources failed', err); return []; } } // Data-URL helper — picker tiles bind `src={thumbnailDataUrl(src)}` so the // thumbnail bytes never leave the component's render pass. Rust encodes // JPEG now (smaller payload, faster decode); the mime type here must // match or the element silently fails to paint. export function thumbnailDataUrl(src: ScreenSource): string | null { if (!src.thumbnailPng) return null; return 'data:image/jpeg;base64,' + src.thumbnailPng; }