diff --git a/src/App.tsx b/src/App.tsx
index 8c590dca..b2ca2b0a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { CountdownOverlay } from "./components/countdown/CountdownOverlay";
import { LaunchWindow } from "./components/launch/LaunchWindow";
-import { SourceSelector } from "./components/launch/SourceSelector";
+import { StandaloneSourceSelector } from "./components/launch/StandaloneSourceSelector";
import { UpdateToastWindow } from "./components/launch/UpdateToastWindow";
import { Toaster } from "./components/ui/sonner";
import { ShortcutsConfigDialog } from "./components/video-editor/ShortcutsConfigDialog";
@@ -63,7 +63,7 @@ export default function App() {
>
);
case "source-selector":
- return ;
+ return ;
case "countdown":
return ;
case "update-toast":
diff --git a/src/components/launch/StandaloneSourceSelector.tsx b/src/components/launch/StandaloneSourceSelector.tsx
new file mode 100644
index 00000000..5cf00278
--- /dev/null
+++ b/src/components/launch/StandaloneSourceSelector.tsx
@@ -0,0 +1,91 @@
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { SourceSelector } from "./SourceSelector";
+import type { DesktopSource } from "./popovers/launchPopoverTypes";
+
+export function StandaloneSourceSelector() {
+ const [sources, setSources] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [selectedSourceName, setSelectedSourceName] = useState("Screen");
+ const [open, setOpen] = useState(true);
+
+ const fetchSources = useCallback(async () => {
+ if (!window.electronAPI) return;
+ setLoading(true);
+ try {
+ const rawSources = await window.electronAPI.getSources({
+ types: ["screen", "window"],
+ thumbnailSize: { width: 160, height: 90 },
+ fetchWindowIcons: true,
+ });
+ setSources(
+ rawSources.map((s: any) => {
+ const isWindow = s.id.startsWith("window:");
+ const type = s.sourceType ?? (isWindow ? "window" : "screen");
+ let displayName = s.name;
+ let appName = s.appName;
+ if (isWindow && !appName && s.name.includes(" — ")) {
+ const parts = s.name.split(" — ");
+ appName = parts[0]?.trim();
+ displayName = parts.slice(1).join(" — ").trim() || s.name;
+ } else if (isWindow && s.windowTitle) {
+ displayName = s.windowTitle;
+ }
+ return {
+ id: s.id,
+ name: displayName,
+ thumbnail: s.thumbnail,
+ display_id: s.display_id,
+ appIcon: s.appIcon,
+ sourceType: type,
+ appName,
+ windowTitle: s.windowTitle ?? displayName,
+ };
+ }),
+ );
+ } catch (error) {
+ console.error("Failed to fetch sources:", error);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ void fetchSources();
+ }, [fetchSources]);
+
+ const handleSourceSelect = useCallback(async (source: DesktopSource) => {
+ if (!window.electronAPI) return;
+ try {
+ const result = await window.electronAPI.selectSource(source);
+ if (result) {
+ setSelectedSourceName(source.name);
+ }
+ } catch (error) {
+ console.error("Failed to select source:", error);
+ }
+ }, []);
+
+ const screenSources = useMemo(
+ () => sources.filter((s) => s.sourceType === "screen" || s.id.startsWith("screen:")),
+ [sources],
+ );
+ const windowSources = useMemo(
+ () => sources.filter((s) => s.sourceType === "window" || s.id.startsWith("window:")),
+ [sources],
+ );
+
+ return (
+
+
+
+ );
+}