Skip to content

Commit 8d4239f

Browse files
committed
feat: Cleanup bits
1 parent 778d80f commit 8d4239f

File tree

2 files changed

+129
-61
lines changed

2 files changed

+129
-61
lines changed

apps/desktop/src-tauri/src/general_settings.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,9 @@ impl MainWindowRecordingStartBehaviour {
4242

4343
const DEFAULT_EXCLUDED_WINDOW_TITLES: &[&str] = &[
4444
"Cap",
45-
"Cap Setup",
4645
"Cap Settings",
47-
"Cap Editor",
48-
"Cap Mode Selection",
49-
"Cap Camera",
50-
"Cap Recordings Overlay",
5146
"Cap In Progress Recording",
52-
"Cap Window Capture Occluder",
53-
"Cap Capture Area",
47+
"Cap Camera",
5448
];
5549

5650
pub fn default_excluded_windows() -> Vec<WindowExclusion> {

apps/desktop/src/routes/(window-chrome)/settings/general.tsx

Lines changed: 128 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,33 @@ import "@total-typescript/ts-reset/filter-boolean";
99
import { CheckMenuItem, Menu, MenuItem } from "@tauri-apps/api/menu";
1010
import { confirm } from "@tauri-apps/plugin-dialog";
1111
import { cx } from "cva";
12-
import { createEffect, createMemo, createResource, For, Show } from "solid-js";
12+
import {
13+
createEffect,
14+
createMemo,
15+
createResource,
16+
createSignal,
17+
For,
18+
Show,
19+
} from "solid-js";
1320
import { createStore, reconcile } from "solid-js/store";
1421
import themePreviewAuto from "~/assets/theme-previews/auto.jpg";
1522
import themePreviewDark from "~/assets/theme-previews/dark.jpg";
1623
import themePreviewLight from "~/assets/theme-previews/light.jpg";
1724
import { Input } from "~/routes/editor/ui";
1825
import { authStore, generalSettingsStore } from "~/store";
19-
import IconLucidePlus from "~icons/lucide/plus";
20-
import IconLucideX from "~icons/lucide/x";
2126
import {
2227
type AppTheme,
23-
type CaptureWindowWithThumbnail,
28+
type CaptureWindow,
2429
commands,
30+
events,
2531
type GeneralSettingsStore,
26-
type WindowExclusion,
2732
type MainWindowRecordingStartBehaviour,
2833
type PostDeletionBehaviour,
2934
type PostStudioRecordingBehaviour,
35+
type WindowExclusion,
3036
} from "~/utils/tauri";
37+
import IconLucidePlus from "~icons/lucide/plus";
38+
import IconLucideX from "~icons/lucide/x";
3139
import { Setting, ToggleSetting } from "./Setting";
3240

3341
const getExclusionPrimaryLabel = (entry: WindowExclusion) =>
@@ -45,7 +53,7 @@ const getExclusionSecondaryLabel = (entry: WindowExclusion) => {
4553
return entry.bundleIdentifier ?? null;
4654
};
4755

48-
const getWindowOptionLabel = (window: CaptureWindowWithThumbnail) => {
56+
const getWindowOptionLabel = (window: CaptureWindow) => {
4957
const parts = [window.owner_name];
5058
if (window.name && window.name !== window.owner_name) {
5159
parts.push(window.name);
@@ -59,9 +67,15 @@ type LegacyWindowExclusion = {
5967
window_title?: string | null;
6068
};
6169

62-
const coerceWindowExclusion = (entry: WindowExclusion | LegacyWindowExclusion): WindowExclusion => {
70+
const coerceWindowExclusion = (
71+
entry: WindowExclusion | LegacyWindowExclusion,
72+
): WindowExclusion => {
6373
if (entry && typeof entry === "object") {
64-
if ("bundleIdentifier" in entry || "ownerName" in entry || "windowTitle" in entry) {
74+
if (
75+
"bundleIdentifier" in entry ||
76+
"ownerName" in entry ||
77+
"windowTitle" in entry
78+
) {
6579
const current = entry as WindowExclusion;
6680
return {
6781
bundleIdentifier: current.bundleIdentifier ?? null,
@@ -97,7 +111,7 @@ const normalizeWindowExclusions = (
97111
return {
98112
bundleIdentifier,
99113
ownerName,
100-
windowTitle: hasBundleIdentifier ? null : coerced.windowTitle ?? null,
114+
windowTitle: hasBundleIdentifier ? null : (coerced.windowTitle ?? null),
101115
} satisfies WindowExclusion;
102116
});
103117

@@ -124,9 +138,9 @@ const deriveInitialSettings = (
124138
excluded_windows?: LegacyWindowExclusion[];
125139
};
126140

127-
const rawExcludedWindows = (
128-
store.excludedWindows ?? excluded_windows ?? []
129-
) as (WindowExclusion | LegacyWindowExclusion)[];
141+
const rawExcludedWindows = (store.excludedWindows ??
142+
excluded_windows ??
143+
[]) as (WindowExclusion | LegacyWindowExclusion)[];
130144

131145
return {
132146
...defaults,
@@ -137,7 +151,7 @@ const deriveInitialSettings = (
137151

138152
export default function GeneralSettings() {
139153
const [store] = createResource(generalSettingsStore.get, {
140-
initialValue: null,
154+
initialValue: undefined,
141155
});
142156

143157
return (
@@ -231,9 +245,16 @@ function Inner(props: {
231245
setSettings(reconcile(deriveInitialSettings(props.initialStore)));
232246
});
233247

234-
const [windows] = createResource(commands.listWindowsWithThumbnails, {
235-
initialValue: [] as CaptureWindowWithThumbnail[],
236-
});
248+
const [windows, { refetch: refetchWindows }] = createResource(
249+
async () => {
250+
// Fetch windows with a small delay to avoid blocking initial render
251+
await new Promise((resolve) => setTimeout(resolve, 100));
252+
return commands.listCaptureWindows();
253+
},
254+
{
255+
initialValue: [] as CaptureWindow[],
256+
},
257+
);
237258

238259
const handleChange = async <K extends keyof typeof settings>(
239260
key: K,
@@ -251,7 +272,10 @@ function Inner(props: {
251272
() => props.isStoreLoading || windows.loading,
252273
);
253274

254-
const matchesExclusion = (exclusion: WindowExclusion, window: CaptureWindowWithThumbnail) => {
275+
const matchesExclusion = (
276+
exclusion: WindowExclusion,
277+
window: CaptureWindow,
278+
) => {
255279
const bundleMatch = exclusion.bundleIdentifier
256280
? window.bundle_identifier === exclusion.bundleIdentifier
257281
: false;
@@ -276,33 +300,48 @@ function Inner(props: {
276300
return false;
277301
};
278302

279-
const isManagedWindowsApp = (window: CaptureWindowWithThumbnail) => {
303+
const isManagedWindowsApp = (window: CaptureWindow) => {
280304
const bundle = window.bundle_identifier?.toLowerCase() ?? "";
281305
if (bundle.includes("so.cap.desktop")) {
282306
return true;
283307
}
284308
return window.owner_name.toLowerCase().includes("cap");
285309
};
286310

311+
const isWindowAvailable = (window: CaptureWindow) => {
312+
if (excludedWindows().some((entry) => matchesExclusion(entry, window))) {
313+
return false;
314+
}
315+
if (ostype === "windows") {
316+
return isManagedWindowsApp(window);
317+
}
318+
return true;
319+
};
320+
287321
const availableWindows = createMemo(() => {
288322
const data = windows() ?? [];
289-
return data.filter((window) => {
290-
if (excludedWindows().some((entry) => matchesExclusion(entry, window))) {
291-
return false;
292-
}
293-
if (ostype === "windows") {
294-
return isManagedWindowsApp(window);
295-
}
296-
return true;
297-
});
323+
return data.filter(isWindowAvailable);
298324
});
299325

326+
const refreshAvailableWindows = async (): Promise<CaptureWindow[]> => {
327+
try {
328+
const refreshed = (await refetchWindows()) ?? windows() ?? [];
329+
return refreshed.filter(isWindowAvailable);
330+
} catch (error) {
331+
console.error("Failed to refresh available windows", error);
332+
return availableWindows();
333+
}
334+
};
335+
300336
const applyExcludedWindows = async (next: WindowExclusion[]) => {
301337
const normalized = normalizeWindowExclusions(next);
302338
setSettings("excludedWindows", normalized);
303339
try {
304340
await generalSettingsStore.set({ excludedWindows: normalized });
305341
await commands.refreshWindowContentProtection();
342+
if (ostype === "macos") {
343+
await events.requestScreenCapturePrewarm.emit({ force: true });
344+
}
306345
} catch (error) {
307346
console.error("Failed to update excluded windows", error);
308347
}
@@ -314,7 +353,7 @@ function Inner(props: {
314353
await applyExcludedWindows(current);
315354
};
316355

317-
const handleAddWindow = async (window: CaptureWindowWithThumbnail) => {
356+
const handleAddWindow = async (window: CaptureWindow) => {
318357
const windowTitle = window.bundle_identifier ? null : window.name;
319358

320359
const next = [
@@ -682,13 +721,14 @@ function Inner(props: {
682721
</For>
683722
</div>
684723
</div>
685-
</Show>
724+
</Show>
686725
)}
687726
</For>
688727

689728
<ExcludedWindowsCard
690729
excludedWindows={excludedWindows()}
691730
availableWindows={availableWindows()}
731+
onRequestAvailableWindows={refreshAvailableWindows}
692732
onRemove={handleRemoveExclusion}
693733
onAdd={handleAddWindow}
694734
onReset={handleResetExclusions}
@@ -757,33 +797,68 @@ function ServerURLSetting(props: {
757797

758798
function ExcludedWindowsCard(props: {
759799
excludedWindows: WindowExclusion[];
760-
availableWindows: CaptureWindowWithThumbnail[];
800+
availableWindows: CaptureWindow[];
801+
onRequestAvailableWindows: () => Promise<CaptureWindow[]>;
761802
onRemove: (index: number) => Promise<void>;
762-
onAdd: (window: CaptureWindowWithThumbnail) => Promise<void>;
803+
onAdd: (window: CaptureWindow) => Promise<void>;
763804
onReset: () => Promise<void>;
764805
isLoading: boolean;
765806
isWindows: boolean;
766807
}) {
767808
const hasExclusions = () => props.excludedWindows.length > 0;
768-
const canAdd = () => props.availableWindows.length > 0 && !props.isLoading;
809+
const canAdd = () => !props.isLoading;
810+
811+
const handleAddClick = async (event: MouseEvent) => {
812+
event.preventDefault();
813+
event.stopPropagation();
769814

770-
const handleAddClick = async () => {
771815
if (!canAdd()) return;
772816

773-
const items = await Promise.all(
774-
props.availableWindows.map((window) =>
775-
MenuItem.new({
776-
text: getWindowOptionLabel(window),
777-
action: () => {
778-
void props.onAdd(window);
779-
},
780-
}),
781-
),
782-
);
817+
// Use available windows if we have them, otherwise fetch
818+
let windows = props.availableWindows;
783819

784-
const menu = await Menu.new({ items });
785-
await menu.popup();
786-
await menu.close();
820+
// Only refresh if we don't have any windows cached
821+
if (!windows.length) {
822+
try {
823+
windows = await props.onRequestAvailableWindows();
824+
} catch (error) {
825+
console.error("Failed to fetch windows:", error);
826+
return;
827+
}
828+
}
829+
830+
if (!windows.length) {
831+
console.log("No available windows to exclude");
832+
return;
833+
}
834+
835+
try {
836+
const items = await Promise.all(
837+
windows.map((window) =>
838+
MenuItem.new({
839+
text: getWindowOptionLabel(window),
840+
action: () => {
841+
void props.onAdd(window);
842+
},
843+
}),
844+
),
845+
);
846+
847+
const menu = await Menu.new({ items });
848+
849+
// Save scroll position before popup
850+
const scrollPos = window.scrollY;
851+
852+
await menu.popup();
853+
await menu.close();
854+
855+
// Restore scroll position after menu closes
856+
requestAnimationFrame(() => {
857+
window.scrollTo(0, scrollPos);
858+
});
859+
} catch (error) {
860+
console.error("Error showing window menu:", error);
861+
}
787862
};
788863

789864
return (
@@ -796,38 +871,37 @@ function ExcludedWindowsCard(props: {
796871
</p>
797872
<Show when={props.isWindows}>
798873
<p class="text-xs text-gray-9">
799-
Only Cap windows can be excluded on Windows.
874+
<span class="font-medium text-gray-11">Note:</span> Only Cap
875+
related windows can be excluded on Windows due to technical
876+
limitations.
800877
</p>
801878
</Show>
802879
</div>
803880
<div class="flex gap-2">
804881
<Button
805-
variant="ghost"
882+
variant="gray"
806883
size="sm"
807884
disabled={props.isLoading}
808885
onClick={() => {
809886
if (props.isLoading) return;
810887
void props.onReset();
811888
}}
812889
>
813-
Default
890+
Set to Default
814891
</Button>
815892
<Button
816893
variant="dark"
817894
size="sm"
818895
disabled={!canAdd()}
819-
onClick={() => void handleAddClick()}
896+
onClick={(e) => void handleAddClick(e)}
820897
class="flex items-center gap-2"
821898
>
822899
<IconLucidePlus class="size-4" />
823900
Add
824901
</Button>
825902
</div>
826903
</div>
827-
<Show
828-
when={!props.isLoading}
829-
fallback={<ExcludedWindowsSkeleton />}
830-
>
904+
<Show when={!props.isLoading} fallback={<ExcludedWindowsSkeleton />}>
831905
<Show
832906
when={hasExclusions()}
833907
fallback={

0 commit comments

Comments
 (0)