import { isModifierPairShortcut, type DesktopCaptureConfigApplied, type DesktopCaptureConfigPreview, type DesktopSnapShotState, } from "@t3tools/contracts"; import { parseKeybindingShortcut } from "@t3tools/shared/keybindings"; import { FileDiff } from "@pierre/diffs/react"; import { parseDiffFromFile } from "@pierre/diffs"; import { useMemo, useState } from "react"; import { getDesktopSnapShotBridge } from "../../lib/desktopSnapShot"; import { resolveDiffThemeName } from "../../lib/diffRendering"; import { useTheme } from "../../hooks/useTheme"; import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; import { Button } from "../ui/button"; import { toastManager } from "../ui/toast"; import { shortcutToKeybindingInput } from "./KeybindingsSettings.logic"; import { useSnapShotShortcutRecorder } from "./useSnapShotShortcutRecorder"; const DEFAULT_SHORTCUT = parseKeybindingShortcut("Ctrl+Shift+2")!; /** Wizard-owned config review; config contents never leave the desktop bridge. */ export function CaptureShortcutConfig({ state, disabled = false, onBusyChange, onSaved, onComplete, }: { state: DesktopSnapShotState; disabled?: boolean; onBusyChange?: (busy: boolean) => void; onSaved?: () => Promise; onComplete?: () => Promise; }) { const bridge = getDesktopSnapShotBridge(); const { resolvedTheme } = useTheme(); const { copyToClipboard, isCopied } = useCopyToClipboard(); const [preview, setPreview] = useState(null); const [result, setResult] = useState(null); const [error, setError] = useState<{ message: string; detail?: string } | null>(null); const [working, setWorking] = useState<"reading" | "writing" | null>(null); const [keys, setKeys] = useState(null); const [customFile, setCustomFile] = useState(false); const busy = disabled || working !== null; const supported = Boolean(bridge?.previewSnapShotConfig && bridge.applySnapShotConfig); const changed = preview !== null && preview.before !== preview.after; const niri = state.linuxBackend === "niri"; const desktop = niri ? "Niri" : "Hyprland"; const shortcutKeys = (keys ?? preview?.shortcut)?.trim(); const recorder = useSnapShotShortcutRecorder({ shortcut: shortcutKeys ? (parseKeybindingShortcut(shortcutKeys.replace(/super/gi, "meta")) ?? DEFAULT_SHORTCUT) : DEFAULT_SHORTCUT, disabled: busy, allowModifierPairs: false, onStart: () => setError(null), onError: (message) => setError({ message }), onRecord: (shortcut) => { if (isModifierPairShortcut(shortcut)) return; setKeys( shortcutToKeybindingInput({ ...shortcut, ctrlKey: shortcut.ctrlKey || shortcut.modKey, modKey: false, }), ); setPreview(null); setError(null); }, }); const actionBusy = busy || recorder.recording; const diff = useMemo( () => preview && changed ? parseDiffFromFile( { name: preview.path, contents: preview.before }, { name: preview.path, contents: preview.after }, ) : null, [preview, changed], ); const begin = (phase: "reading" | "writing") => { setWorking(phase); onBusyChange?.(true); setError(null); }; const end = () => { setWorking(null); onBusyChange?.(false); }; const read = async (chooseFile = customFile, operation: "install" | "remove" = "install") => { if (actionBusy || !bridge?.previewSnapShotConfig) return; begin("reading"); setPreview(null); setResult(null); setCustomFile(chooseFile); try { setPreview( await bridge.previewSnapShotConfig({ operation, chooseFile, ...(keys?.trim() ? { shortcut: keys.trim() } : {}), }), ); } catch (cause) { setError({ message: "Couldn't prepare the changes. Check Advanced for help.", ...(cause instanceof Error ? { detail: cause.message } : {}), }); } finally { end(); } }; const apply = async () => { if (actionBusy || !preview || !bridge?.applySnapShotConfig) return; begin("writing"); try { const applied = await bridge.applySnapShotConfig(preview.id); setResult(applied); await onSaved?.(); if (!applied.warning && preview.operation === "install" && onComplete) { toastManager.add({ type: "success", title: "Shortcut saved", description: `Use ${preview.shortcut} from another app.`, }); await onComplete(); } } catch (cause) { setError({ message: "Couldn't save your shortcut. Review the changes and try again.", ...(cause instanceof Error ? { detail: cause.message } : {}), }); setPreview(null); } finally { end(); } }; return (
{!result ? (
Shortcut {recorder.input}
) : null} {recorder.recording ? (

Press your shortcut. Esc cancels.

) : null} {result ? (

{result.warning ? "Saved, but the shortcut needs attention. Check Advanced for help." : preview?.operation === "remove" ? "Shortcut removed." : `Use ${preview?.shortcut} from another app to capture a window.`}

) : preview ? ( <>

{changed ? preview.operation === "remove" ? "Review the change below to remove your shortcut." : "Review the change below, then save your shortcut." : preview.operation === "remove" ? "There's no capture shortcut to remove." : "This shortcut is already set up."}

{diff ? (
) : null} {changed ? (

Only these changes will be saved. We'll keep a backup.

) : null}
{changed || preview.operation === "install" ? ( ) : null}
) : ( <>

Allow T3 Code to read your desktop settings. You'll review any changes here before saving.

{!supported ? (

Update T3 Code to finish setting up your shortcut.

) : null} )} {error ? (

{error.message}

) : null} {state.shortcutActionRegistered === false && state.shortcutMessage ? (

{state.shortcutPending ? "Connecting to your desktop…" : "Restart T3 Code to finish connecting your shortcut."}

) : null}
Advanced
{error?.detail || result?.warning ? (

Troubleshooting

{error?.detail ?? result?.warning}

) : null}

Settings file

{preview?.path ?? state.shortcutConfigPath ?? (niri ? "~/.config/niri/config.kdl" : "~/.config/hypr/hyprland.conf")}

{niri ?

T3 Code also reads any files included by this file.

: null} {preview && preview.resolvedPath !== preview.path ? (

Linked to {preview.resolvedPath}. The link will be kept.

) : null}
{result ? ( ) : null}

Use your desktop's shortcut settings file.{" "} {niri ? "A custom --config or NIRI_CONFIG can change its location." : "On Omarchy, use your own bindings file, not its defaults."}

{result?.backupPath ?

Backup: {result.backupPath}

: null}

Manual setup

{niri ? "Paste this inside binds { … } in your Niri config, then save." : "Add this binding to your Hyprland config, then save."}{" "} Change the keys if needed.

            {state.shortcutBinding}
          

Turn capture off in T3 Code to stop it. Remove the shortcut from {desktop} to free up the keys.

{state.shortcutActionRegistered === false ? (

{state.shortcutMessage}

) : null} {onComplete ? ( ) : null}
); }