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:
@@ -18,7 +18,7 @@ import { FocusIcon, GridIcon, LockIcon, MaximizeIcon, SpinnerIcon, UsersIcon } f
|
||||
import { ParticipantsPopover, type ParticipantRow } from './ParticipantsPopover';
|
||||
import { ParticipantVolumeMenu } from './ParticipantVolumeMenu';
|
||||
import { ScreenShareContextMenu } from './ScreenShareContextMenu';
|
||||
import { ScreenShareDialog } from './ScreenShareDialog';
|
||||
import { ScreenSourcePicker } from './ScreenSourcePicker';
|
||||
import { ScreenShareViewer } from './ScreenShareViewer';
|
||||
import { SoundboardPanel } from './SoundboardPanel';
|
||||
|
||||
@@ -84,7 +84,7 @@ export function InCallPanel({ conversation }: Props) {
|
||||
const { session } = useAuth();
|
||||
const myId = session?.user.id ?? null;
|
||||
const activeSpeakers = useActiveSpeakers(room);
|
||||
const [shareDialogOpen, setShareDialogOpen] = useState(false);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [soundboardOpen, setSoundboardOpen] = useState(false);
|
||||
const [participantsOpen, setParticipantsOpen] = useState(false);
|
||||
const [volumeMenu, setVolumeMenu] = useState<
|
||||
@@ -212,19 +212,19 @@ export function InCallPanel({ conversation }: Props) {
|
||||
video={isCameraEnabled}
|
||||
deafened={isDeafened}
|
||||
onToggleMute={toggleMute}
|
||||
// 1-click share uses last-saved preset + displaySurface. Right-click
|
||||
// opens the quality picker for users who want to change settings
|
||||
// before starting — matches Discord's "Go Live" vs quick-share split.
|
||||
// Click opens the Discord-style source picker (thumbnails + quality +
|
||||
// audio). Clicking again while a share is live stops it. Right-click
|
||||
// also opens the picker in case the user wants to swap sources.
|
||||
onToggleShare={() => {
|
||||
if (isScreenSharing) {
|
||||
void stopScreenShare();
|
||||
} else {
|
||||
void startScreenShare();
|
||||
setPickerOpen(true);
|
||||
}
|
||||
}}
|
||||
onShareContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
if (!isScreenSharing) setShareDialogOpen(true);
|
||||
if (!isScreenSharing) setPickerOpen(true);
|
||||
}}
|
||||
onToggleVideo={() => void toggleCamera()}
|
||||
onToggleDeafen={toggleDeafen}
|
||||
@@ -412,9 +412,9 @@ export function InCallPanel({ conversation }: Props) {
|
||||
|
||||
<PttHint />
|
||||
|
||||
<ScreenShareDialog
|
||||
open={shareDialogOpen}
|
||||
onClose={() => setShareDialogOpen(false)}
|
||||
<ScreenSourcePicker
|
||||
open={pickerOpen}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
onStart={async (opts) => {
|
||||
await startScreenShare(opts);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user