import { useEffect, type Dispatch, type SetStateAction } from 'react'; import type { CustomKeyBindings, HotkeyScheme, SessionLogFormat, TerminalSettings, UILanguage } from '../../domain/models'; import { parseCustomKeyBindingsStorageRecord } from '../../domain/customKeyBindings'; import { resolveSupportedLocale } from '../../infrastructure/config/i18n'; import { STORAGE_KEY_ACCENT_MODE, STORAGE_KEY_AUTO_LAUNCH_ENABLED, STORAGE_KEY_AUTO_UPDATE_ENABLED, STORAGE_KEY_COLOR, STORAGE_KEY_CUSTOM_CSS, STORAGE_KEY_CUSTOM_KEY_BINDINGS, STORAGE_KEY_EDITOR_WORD_WRAP, STORAGE_KEY_GLOBAL_HOTKEY_ENABLED, STORAGE_KEY_HOTKEY_RECORDING, STORAGE_KEY_HOTKEY_SCHEME, STORAGE_KEY_DISABLE_TERMINAL_FONT_ZOOM, STORAGE_KEY_RESTORE_PREVIOUS_SESSION, STORAGE_KEY_RESTORE_TERMINAL_CWD, STORAGE_KEY_STARTUP_LANDING, STORAGE_KEY_SESSION_LOGS_DIR, STORAGE_KEY_SESSION_LOGS_ENABLED, STORAGE_KEY_SESSION_LOGS_FORMAT, STORAGE_KEY_SESSION_LOGS_TIMESTAMPS_ENABLED, STORAGE_KEY_SSH_DEBUG_LOGS_ENABLED, STORAGE_KEY_SSH_DEEP_LINK_ENABLED, STORAGE_KEY_JMS_DEEP_LINK_ENABLED, STORAGE_KEY_EXPLORER_CONTEXT_MENU_ENABLED, STORAGE_KEY_SFTP_AUTO_OPEN_SIDEBAR, STORAGE_KEY_SFTP_FOLLOW_TERMINAL_CWD, STORAGE_KEY_SFTP_DEFAULT_VIEW_MODE, STORAGE_KEY_SFTP_TRANSFER_CONCURRENCY, STORAGE_KEY_SSH_TRANSPORT_IDLE_TTL_MS, STORAGE_KEY_TERM_FOLLOW_APP_THEME, STORAGE_KEY_TERM_FONT_FAMILY, STORAGE_KEY_TERM_FONT_SIZE, STORAGE_KEY_TERM_SETTINGS, STORAGE_KEY_TERM_THEME, STORAGE_KEY_TERM_THEME_DARK, STORAGE_KEY_TERM_THEME_LIGHT, STORAGE_KEY_THEME, STORAGE_KEY_UI_FONT_FAMILY, STORAGE_KEY_UI_LANGUAGE, STORAGE_KEY_UI_THEME_DARK, STORAGE_KEY_UI_THEME_LIGHT, STORAGE_KEY_WORKSPACE_FOCUS_STYLE, STORAGE_KEY_SHOW_HOST_TREE_SIDEBAR, STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN, STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN_TAB, STORAGE_KEY_WINDOW_OPACITY, STORAGE_KEY_APP_ICON_VARIANT, STORAGE_KEY_HTTP_NETWORK_PROXY, } from '../../infrastructure/config/storageKeys'; import { resolveAppIconVariant, type AppIconVariant } from '../../domain/appIconVariant'; import { areHttpNetworkProxySettingsEqual, normalizeHttpNetworkProxySettings, type HttpNetworkProxySettings, } from '../../domain/httpNetworkProxy'; import { netcattyBridge } from '../../infrastructure/services/netcattyBridge'; import { isValidUiFontId, migrateIncomingTerminalFontId, } from './settingsStateDefaults'; import { isTerminalSidePanelAutoOpenTab, type TerminalSidePanelAutoOpenTab } from '../../domain/terminalSidePanelAutoOpen'; import { isStartupLanding, type StartupLanding } from '../../domain/startupLanding'; import type { AppearanceSyncEvent } from './appearanceSync'; interface UseSettingsIpcSyncParams { enabled?: boolean; syncAppearanceFromStorage: (incoming?: AppearanceSyncEvent) => void; syncCustomCssFromStorage: () => void; setUiLanguage: Dispatch>; setUiFontFamilyId: Dispatch>; setTerminalThemeId: Dispatch>; setTerminalThemeDarkId: Dispatch>; setTerminalThemeLightId: Dispatch>; setFollowAppTerminalThemeState: Dispatch>; setTerminalFontFamilyId: Dispatch>; setTerminalFontSize: (raw: unknown) => void; mergeIncomingTerminalSettings: (incoming: Partial) => void; setEditorWordWrapState: Dispatch>; setSessionLogsEnabled: Dispatch>; setSessionLogsDir: Dispatch>; setSessionLogsFormat: Dispatch>; setSessionLogsTimestampsEnabled: Dispatch>; setSshDebugLogsEnabled: Dispatch>; setSshDeepLinkEnabledState: (enabled: boolean) => void; setJmsDeepLinkEnabledState: (enabled: boolean) => void; setExplorerContextMenuEnabledState: (enabled: boolean) => void; setHotkeyScheme: Dispatch>; applyIncomingCustomKeyBindings: (incoming: { bindings: CustomKeyBindings; version: number; origin: string }) => void; setIsHotkeyRecordingState: Dispatch>; setGlobalHotkeyEnabled: Dispatch>; setWindowOpacity: (raw: unknown) => void; setAppIconVariant: Dispatch>; setAutoUpdateEnabled: Dispatch>; setAutoLaunchEnabled: Dispatch>; setHttpNetworkProxy: Dispatch>; setSftpAutoOpenSidebar: Dispatch>; setSftpFollowTerminalCwd: Dispatch>; setSftpDefaultViewMode: Dispatch>; setWorkspaceFocusStyleState: Dispatch>; setShowHostTreeSidebarState: Dispatch>; setTerminalSidePanelAutoOpenState: Dispatch>; setTerminalSidePanelAutoOpenTabState: Dispatch>; setDisableTerminalFontZoomState: Dispatch>; setRestorePreviousSessionState: Dispatch>; setRestoreTerminalCwdState: Dispatch>; setStartupLandingState: Dispatch>; setSftpTransferConcurrencyState: Dispatch>; setSshTransportIdleTtlMsState: Dispatch>; } export function useSettingsIpcSync({ enabled = true, syncAppearanceFromStorage, syncCustomCssFromStorage, setUiLanguage, setUiFontFamilyId, setTerminalThemeId, setTerminalThemeDarkId, setTerminalThemeLightId, setFollowAppTerminalThemeState, setTerminalFontFamilyId, setTerminalFontSize, mergeIncomingTerminalSettings, setEditorWordWrapState, setSessionLogsEnabled, setSessionLogsDir, setSessionLogsFormat, setSessionLogsTimestampsEnabled, setSshDebugLogsEnabled, setSshDeepLinkEnabledState, setJmsDeepLinkEnabledState, setExplorerContextMenuEnabledState, setHotkeyScheme, applyIncomingCustomKeyBindings, setIsHotkeyRecordingState, setGlobalHotkeyEnabled, setWindowOpacity, setAppIconVariant, setAutoUpdateEnabled, setAutoLaunchEnabled, setHttpNetworkProxy, setSftpAutoOpenSidebar, setSftpFollowTerminalCwd, setSftpDefaultViewMode, setWorkspaceFocusStyleState, setShowHostTreeSidebarState, setTerminalSidePanelAutoOpenState, setTerminalSidePanelAutoOpenTabState, setDisableTerminalFontZoomState, setRestorePreviousSessionState, setRestoreTerminalCwdState, setStartupLandingState, setSftpTransferConcurrencyState, setSshTransportIdleTtlMsState, }: UseSettingsIpcSyncParams) { // Listen for settings changes from other windows via IPC useEffect(() => { if (!enabled) return; const bridge = netcattyBridge.get(); if (!bridge?.onSettingsChanged) return; const unsubscribe = bridge.onSettingsChanged((payload) => { const { key, value } = payload; if ( key === STORAGE_KEY_THEME || key === STORAGE_KEY_UI_THEME_LIGHT || key === STORAGE_KEY_UI_THEME_DARK || key === STORAGE_KEY_ACCENT_MODE || key === STORAGE_KEY_COLOR ) { syncAppearanceFromStorage({ key, value }); return; } if (key === STORAGE_KEY_UI_LANGUAGE && typeof value === 'string') { const next = resolveSupportedLocale(value); setUiLanguage((prev) => (prev === next ? prev : next)); document.documentElement.lang = next; } if (key === STORAGE_KEY_CUSTOM_CSS && typeof value === 'string') { syncCustomCssFromStorage(); } if (key === STORAGE_KEY_UI_FONT_FAMILY && typeof value === 'string') { if (isValidUiFontId(value)) { setUiFontFamilyId(value); } } if (key === STORAGE_KEY_TERM_THEME && typeof value === 'string') { setTerminalThemeId(value); } if (key === STORAGE_KEY_TERM_THEME_DARK && typeof value === 'string') { setTerminalThemeDarkId(value); } if (key === STORAGE_KEY_TERM_THEME_LIGHT && typeof value === 'string') { setTerminalThemeLightId(value); } if (key === STORAGE_KEY_TERM_FOLLOW_APP_THEME) { const next = value === true || value === 'true'; setFollowAppTerminalThemeState((prev) => (prev === next ? prev : next)); } if (key === STORAGE_KEY_TERM_FONT_FAMILY && typeof value === 'string') { const migrated = migrateIncomingTerminalFontId(value); if (migrated) setTerminalFontFamilyId(migrated); } if (key === STORAGE_KEY_TERM_FONT_SIZE) { setTerminalFontSize(value); } if (key === STORAGE_KEY_TERM_SETTINGS) { if (typeof value === 'string') { try { const parsed = JSON.parse(value) as Partial; mergeIncomingTerminalSettings(parsed); } catch { // ignore parse errors } } else if (value && typeof value === 'object') { mergeIncomingTerminalSettings(value as Partial); } } if (key === STORAGE_KEY_EDITOR_WORD_WRAP && typeof value === 'boolean') { setEditorWordWrapState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SESSION_LOGS_ENABLED && typeof value === 'boolean') { setSessionLogsEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SESSION_LOGS_DIR && typeof value === 'string') { setSessionLogsDir((prev) => (prev === value ? prev : value)); } if ( key === STORAGE_KEY_SESSION_LOGS_FORMAT && (value === 'txt' || value === 'raw' || value === 'html') ) { setSessionLogsFormat((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SESSION_LOGS_TIMESTAMPS_ENABLED && typeof value === 'boolean') { setSessionLogsTimestampsEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SSH_DEBUG_LOGS_ENABLED && typeof value === 'boolean') { setSshDebugLogsEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SSH_DEEP_LINK_ENABLED && typeof value === 'boolean') { setSshDeepLinkEnabledState(value); } if (key === STORAGE_KEY_JMS_DEEP_LINK_ENABLED && typeof value === 'boolean') { setJmsDeepLinkEnabledState(value); } if (key === STORAGE_KEY_EXPLORER_CONTEXT_MENU_ENABLED && typeof value === 'boolean') { setExplorerContextMenuEnabledState(value); } if (key === STORAGE_KEY_HOTKEY_SCHEME && (value === 'disabled' || value === 'mac' || value === 'pc')) { setHotkeyScheme(value); } if (key === STORAGE_KEY_CUSTOM_KEY_BINDINGS) { const parsed = parseCustomKeyBindingsStorageRecord(value); if (parsed) { applyIncomingCustomKeyBindings(parsed); } } if (key === STORAGE_KEY_HOTKEY_RECORDING && typeof value === 'boolean') { setIsHotkeyRecordingState(value); } if (key === STORAGE_KEY_GLOBAL_HOTKEY_ENABLED && typeof value === 'boolean') { setGlobalHotkeyEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_WINDOW_OPACITY) { setWindowOpacity(value); } if (key === STORAGE_KEY_APP_ICON_VARIANT) { const nextVariant = resolveAppIconVariant(value); setAppIconVariant((prev) => (prev === nextVariant ? prev : nextVariant)); } if (key === STORAGE_KEY_AUTO_UPDATE_ENABLED && typeof value === 'boolean') { setAutoUpdateEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_AUTO_LAUNCH_ENABLED && typeof value === 'boolean') { // Peer windows (main, tray panel, terminal popups) all mount // useSettingsState via AppLockGate even though the toggle is only // ever rendered in the Settings window — keep their in-memory copy // in sync so it's not stale if anything reads it later. setAutoLaunchEnabled((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_HTTP_NETWORK_PROXY) { const next = normalizeHttpNetworkProxySettings(value); setHttpNetworkProxy((prev) => ( areHttpNetworkProxySettingsEqual(prev, next) ? prev : next )); } if (key === STORAGE_KEY_SFTP_AUTO_OPEN_SIDEBAR && typeof value === 'boolean') { setSftpAutoOpenSidebar((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SFTP_FOLLOW_TERMINAL_CWD && typeof value === 'boolean') { setSftpFollowTerminalCwd((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SFTP_DEFAULT_VIEW_MODE && typeof value === 'string') { if (value === 'list' || value === 'tree') { setSftpDefaultViewMode((prev) => (prev === value ? prev : value)); } } if (key === STORAGE_KEY_WORKSPACE_FOCUS_STYLE && (value === 'dim' || value === 'border')) { setWorkspaceFocusStyleState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SHOW_HOST_TREE_SIDEBAR && typeof value === 'boolean') { setShowHostTreeSidebarState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN && typeof value === 'boolean') { setTerminalSidePanelAutoOpenState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN_TAB && isTerminalSidePanelAutoOpenTab(value)) { setTerminalSidePanelAutoOpenTabState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_DISABLE_TERMINAL_FONT_ZOOM && typeof value === 'boolean') { setDisableTerminalFontZoomState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_RESTORE_PREVIOUS_SESSION && typeof value === 'boolean') { setRestorePreviousSessionState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_RESTORE_TERMINAL_CWD && typeof value === 'boolean') { setRestoreTerminalCwdState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_STARTUP_LANDING && isStartupLanding(value)) { setStartupLandingState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SFTP_TRANSFER_CONCURRENCY && typeof value === 'number') { setSftpTransferConcurrencyState((prev) => (prev === value ? prev : value)); } if (key === STORAGE_KEY_SSH_TRANSPORT_IDLE_TTL_MS && typeof value === 'number') { setSshTransportIdleTtlMsState((prev) => (prev === value ? prev : value)); } }); return () => { try { unsubscribe?.(); } catch { // ignore } }; }, [ enabled, applyIncomingCustomKeyBindings, mergeIncomingTerminalSettings, setAutoUpdateEnabled, setAutoLaunchEnabled, setHttpNetworkProxy, setEditorWordWrapState, setFollowAppTerminalThemeState, setGlobalHotkeyEnabled, setWindowOpacity, setAppIconVariant, setHotkeyScheme, setIsHotkeyRecordingState, setSessionLogsDir, setSessionLogsEnabled, setSessionLogsFormat, setSessionLogsTimestampsEnabled, setSshDeepLinkEnabledState, setJmsDeepLinkEnabledState, setExplorerContextMenuEnabledState, setSshDebugLogsEnabled, setSftpAutoOpenSidebar, setSftpFollowTerminalCwd, setSftpDefaultViewMode, setShowHostTreeSidebarState, setTerminalSidePanelAutoOpenState, setTerminalSidePanelAutoOpenTabState, setDisableTerminalFontZoomState, setRestorePreviousSessionState, setRestoreTerminalCwdState, setStartupLandingState, setSftpTransferConcurrencyState, setSshTransportIdleTtlMsState, setTerminalFontFamilyId, setTerminalFontSize, setTerminalThemeDarkId, setTerminalThemeId, setTerminalThemeLightId, setUiFontFamilyId, setUiLanguage, setWorkspaceFocusStyleState, syncAppearanceFromStorage, syncCustomCssFromStorage, ]); }