feat(call): Discord-style screen-source picker with thumbnails

Rust side:
- New src-tauri/src/screen_sources.rs with an enumerate_screen_sources
  command. Uses the xcap crate for cross-platform screen + window
  enumeration and capture; PNG thumbnails are letterbox-scaled to fit
  320x180 and returned as base64.
- Source ids emit Chromium's internal desktopCapturer format
  ("screen:<id>:0", "window:<hwnd>:0") so JS can try passing them
  straight into chromeMediaSourceId.
- Registered in both invoke_handler branches in lib.rs.

Frontend:
- New lib/screenSources.ts — Tauri command wrapper + thumbnailDataUrl
  helper for the picker UI.
- New components/ScreenSourcePicker.tsx — Discord-style grid: sources
  grouped under "Bildschirme" / "Fenster", large thumbnail cards with
  selection state, quality preset + system-audio toggle in the footer.
  "Teilen" button is enabled either way; without a selection it says
  "Ohne Auswahl weiter" and falls through to the OS picker.
- Replaces the old form-style ScreenShareDialog entirely (removed).

CallContext wiring:
- startScreenShare now accepts an optional sourceId. When set, it
  captures that exact source via getUserMedia's legacy
  chromeMediaSourceId constraint and publishes the resulting tracks
  manually (video as ScreenShare, audio as ScreenShareAudio). Falls
  back to setScreenShareEnabled if WebView2 rejects the constraint,
  so users always get a working share even if the direct path fails.
- Track 'ended' listeners unpublish the pub when the OS revokes
  capture (close of shared window, OS "stop sharing" banner).

InCallPanel:
- Left-click on the share button now opens the picker instead of
  starting with last-saved settings; right-click opens it too. The
  picker itself is the 1-click UX.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-04-22 20:55:52 +02:00
parent 331b1298f8
commit b44a785d20
9 changed files with 1472 additions and 284 deletions
+38
View File
@@ -0,0 +1,38 @@
// 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:<id>:0" / "window:<hwnd>:0"). */
id: string;
name: string;
kind: ScreenSourceKind;
/** Base64-encoded PNG without a data-URL prefix. Null when capture failed. */
thumbnailPng: string | null;
width: number;
height: number;
}
export async function enumerateScreenSources(): Promise<ScreenSource[]> {
if (!isTauriRuntime()) return [];
try {
const { invoke } = await import('@tauri-apps/api/core');
const raw = await invoke<ScreenSource[]>('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.
export function thumbnailDataUrl(src: ScreenSource): string | null {
if (!src.thumbnailPng) return null;
return 'data:image/png;base64,' + src.thumbnailPng;
}