import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Ban, RotateCcw } from "lucide-react"; import type { HotkeyScheme, KeyBinding } from "../../../domain/models"; import { keyEventToString } from "../../../domain/models"; import { useI18n } from "../../../application/i18n/I18nProvider"; import { cn } from "../../../lib/utils"; import { Button } from "../../ui/button"; import { SectionHeader, Select, SettingsAnchor, SettingsTabContent, SettingRow, Toggle } from "../settings-ui"; import { isAppLockOverlayActive } from '../../../infrastructure/appLockOverlayDom'; export default function SettingsShortcutsTab(props: { hotkeyScheme: HotkeyScheme; setHotkeyScheme: (scheme: HotkeyScheme) => void; shellOnlyTabNumberShortcuts: boolean; setShellOnlyTabNumberShortcuts: (enabled: boolean) => void; showTabNumberBadges: boolean; setShowTabNumberBadges: (enabled: boolean) => void; disableTerminalFontZoom: boolean; setDisableTerminalFontZoom: (enabled: boolean) => void; keyBindings: KeyBinding[]; updateKeyBinding?: (bindingId: string, scheme: "mac" | "pc", newKey: string) => void; resetKeyBinding?: (bindingId: string, scheme?: "mac" | "pc") => void; resetAllKeyBindings: () => void; setIsHotkeyRecording?: (isRecording: boolean) => void; }) { const { hotkeyScheme, setHotkeyScheme, shellOnlyTabNumberShortcuts, setShellOnlyTabNumberShortcuts, showTabNumberBadges, setShowTabNumberBadges, disableTerminalFontZoom, setDisableTerminalFontZoom, keyBindings, updateKeyBinding, resetKeyBinding, resetAllKeyBindings, setIsHotkeyRecording, } = props; const { t } = useI18n(); const [recordingBindingId, setRecordingBindingId] = useState(null); const [recordingScheme, setRecordingScheme] = useState<"mac" | "pc" | null>(null); const cancelRecording = useCallback(() => { setRecordingBindingId(null); setRecordingScheme(null); }, []); const getSpecialSuffix = useCallback( (bindingId: string): string | null => { const binding = keyBindings.find((b) => b.id === bindingId); if (!binding) return null; const currentKey = hotkeyScheme === "mac" ? binding.mac : binding.pc; if (currentKey.includes("[1...9]")) return "[1...9]"; if (currentKey.includes("arrows")) return "arrows"; return null; }, [keyBindings, hotkeyScheme], ); useEffect(() => { if (!recordingBindingId || !recordingScheme) return; const specialSuffix = getSpecialSuffix(recordingBindingId); const handleKeyDown = (e: KeyboardEvent) => { // Skip while app lock overlay is up so password keys never bind (Codex P2). if (isAppLockOverlayActive()) return; e.preventDefault(); e.stopPropagation(); if (e.key === "Escape") { cancelRecording(); return; } if (specialSuffix) { if (["Meta", "Control", "Alt", "Shift"].includes(e.key)) return; const parts: string[] = []; if (recordingScheme === "mac") { if (e.metaKey) parts.push("⌘"); if (e.ctrlKey) parts.push("⌃"); if (e.altKey) parts.push("⌥"); if (e.shiftKey) parts.push("Shift"); } else { if (e.ctrlKey) parts.push("Ctrl"); if (e.altKey) parts.push("Alt"); if (e.shiftKey) parts.push("Shift"); if (e.metaKey) parts.push("Win"); } const modifierString = parts.length > 0 ? `${parts.join(" + ")} + ` : ""; const fullKeyString = modifierString + specialSuffix; updateKeyBinding?.(recordingBindingId, recordingScheme, fullKeyString); cancelRecording(); return; } if (["Meta", "Control", "Alt", "Shift"].includes(e.key)) return; const keyString = keyEventToString(e, recordingScheme === "mac"); updateKeyBinding?.(recordingBindingId, recordingScheme, keyString); cancelRecording(); }; const handleClick = () => { cancelRecording(); }; const timer = setTimeout(() => { window.addEventListener("click", handleClick, true); }, 100); window.addEventListener("keydown", handleKeyDown, true); return () => { clearTimeout(timer); window.removeEventListener("keydown", handleKeyDown, true); window.removeEventListener("click", handleClick, true); }; }, [recordingBindingId, recordingScheme, updateKeyBinding, cancelRecording, getSpecialSuffix]); useEffect(() => { const isRecording = Boolean(recordingBindingId && recordingScheme); setIsHotkeyRecording?.(isRecording); return () => { setIsHotkeyRecording?.(false); }; }, [recordingBindingId, recordingScheme, setIsHotkeyRecording]); const categories = useMemo(() => ["tabs", "terminal", "navigation", "app", "sftp"] as const, []); return (