diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift index 3015a93c8cb9..ff75c9b7c6bc 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Environment/SettingsHostActions.swift @@ -68,6 +68,9 @@ public protocol SettingsHostActions: AnyObject { /// Live-reloads Ghostty after the adaptive-default-theme preference commits. func terminalAdaptiveDefaultThemeDidChange() + /// Opens the interactive terminal theme picker in a focused cmux terminal pane. + func openTerminalThemePicker() + /// Launches the host's browser-import flow (Safari / Chrome / /// Firefox source picker + profile selection + cookie prompt). func openBrowserImportFlow() @@ -364,6 +367,9 @@ public extension SettingsHostActions { /// Default no-op for package-only settings hosts without Ghostty. func terminalAdaptiveDefaultThemeDidChange() {} + /// Default no-op for package-only settings hosts without a terminal theme picker. + func openTerminalThemePicker() {} + /// Default no-op for hosts with no app-owned reset side effects. func resetAllSettingsSideEffects() {} diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift index 1e534e9c9d2d..18fae3f909f2 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift @@ -257,6 +257,20 @@ public struct TerminalSection: View { } } SettingsCardDivider() + SettingsCardRow( + configurationReview: .settingsOnly, + String(localized: "settings.app.theme", defaultValue: "Theme") + ) { + Button( + String(localized: "settings.browser.import.choose", defaultValue: "Choose…") + ) { + hostActions.openTerminalThemePicker() + } + .buttonStyle(.bordered) + .controlSize(.small) + .accessibilityIdentifier("SettingsTerminalThemePickerButton") + } + SettingsCardDivider() SettingsCardRow( configurationReview: .json("terminal.adaptiveDefaultTheme"), String( diff --git a/Sources/HostSettingsActions.swift b/Sources/HostSettingsActions.swift index 0165d7289abf..b67ffd1e901d 100644 --- a/Sources/HostSettingsActions.swift +++ b/Sources/HostSettingsActions.swift @@ -8,7 +8,7 @@ import Foundation import OSLog import SwiftUI -private let hostSettingsLogger = Logger(subsystem: "com.cmuxterm.app", category: "Settings") +nonisolated private let hostSettingsLogger = Logger(subsystem: "com.cmuxterm.app", category: "Settings") /// App-side implementation of the package's `SettingsHostActions` /// protocol. Routes UI-triggered actions to the existing host @@ -110,6 +110,44 @@ final class HostSettingsActions: SettingsHostActions { TerminalAdaptiveDefaultThemeSettings.notifyDidChange() } + func openTerminalThemePicker() { + let cliURL = Bundle.main.bundleURL + .appendingPathComponent("Contents/Resources/bin/cmux", isDirectory: false) + guard FileManager.default.isExecutableFile(atPath: cliURL.path) else { + hostSettingsLogger.error("Theme picker unavailable: bundled cmux CLI missing") + return + } + + guard let appDelegate = AppDelegate.shared, + let manager = appDelegate.activeTabManagerForCommands(), + let workspace = manager.selectedWorkspace else { + NSSound.beep() + return + } + + // The native Settings entry point keeps CLI diagnostics private. The + // interactive picker still owns stdout/the TTY, while raw helper and + // launch errors on stderr are suppressed on this user-facing path. + let initialCommand = "\(LocalSurfaceProvider.shellQuote(cliURL.path)) themes 2>/dev/null; exit" + do { + let picker = try SurfacePaneFactory.makeTerminalPane( + initialCommand: initialCommand, + workingDirectory: nil, + at: .workspace(id: workspace.id, placement: .tab), + focus: true + ) + if let windowID = appDelegate.windowId(for: manager) { + _ = appDelegate.focusMainWindow(windowId: windowID) + } + SurfacePaneFactory.focus( + panelID: picker.panelID, + in: picker.workspaceID + ) + } catch { + hostSettingsLogger.error("Failed to open terminal theme picker") + } + } + func notifyShortcutSettingsDidChange() { // reload() already posts didChangeNotification when the file's // contents changed; posting again here double-notified every