diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 3c3fa1a0..ed21b275 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -166,10 +166,33 @@ class GhosttyApp { private(set) var config: ghostty_config_t? private(set) var defaultBackgroundColor: NSColor = .windowBackgroundColor private(set) var defaultBackgroundOpacity: Double = 1.0 + private static func resolveBackgroundLogURL( + environment: [String: String] = ProcessInfo.processInfo.environment + ) -> URL { + if let explicitPath = environment["CMUX_DEBUG_BG_LOG"], + !explicitPath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return URL(fileURLWithPath: explicitPath) + } + + if let debugLogPath = environment["CMUX_DEBUG_LOG"], + !debugLogPath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + let baseURL = URL(fileURLWithPath: debugLogPath) + let extensionSeparatorIndex = baseURL.lastPathComponent.lastIndex(of: ".") + let stem = extensionSeparatorIndex.map { String(baseURL.lastPathComponent[..<$0]) } ?? baseURL.lastPathComponent + let bgName = "\(stem)-bg.log" + return baseURL.deletingLastPathComponent().appendingPathComponent(bgName) + } + + return URL(fileURLWithPath: "/tmp/cmux-bg.log") + } + let backgroundLogEnabled = { if ProcessInfo.processInfo.environment["CMUX_DEBUG_BG"] == "1" { return true } + if ProcessInfo.processInfo.environment["CMUX_DEBUG_LOG"] != nil { + return true + } if ProcessInfo.processInfo.environment["GHOSTTYTABS_DEBUG_BG"] == "1" { return true } @@ -178,7 +201,7 @@ class GhosttyApp { } return UserDefaults.standard.bool(forKey: "GhosttyTabsDebugBG") }() - private let backgroundLogURL = URL(fileURLWithPath: "/tmp/cmux-bg.log") + private let backgroundLogURL = GhosttyApp.resolveBackgroundLogURL() private var appObservers: [NSObjectProtocol] = [] // Scroll lag tracking diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 6c8d20cc..99d17dcf 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -352,9 +352,20 @@ final class Workspace: Identifiable, ObservableObject { } func applyGhosttyChrome(backgroundColor: NSColor) { + let currentChromeColors = bonsplitController.configuration.appearance.chromeColors let nextChromeColors = Self.resolvedChromeColors(from: backgroundColor) - if bonsplitController.configuration.appearance.chromeColors.backgroundHex == nextChromeColors.backgroundHex && - bonsplitController.configuration.appearance.chromeColors.borderHex == nextChromeColors.borderHex { + let isNoOp = currentChromeColors.backgroundHex == nextChromeColors.backgroundHex && + currentChromeColors.borderHex == nextChromeColors.borderHex + + if GhosttyApp.shared.backgroundLogEnabled { + let currentBackgroundHex = currentChromeColors.backgroundHex ?? "nil" + let nextBackgroundHex = nextChromeColors.backgroundHex ?? "nil" + GhosttyApp.shared.logBackground( + "theme apply workspace=\(id.uuidString) currentBg=\(currentBackgroundHex) nextBg=\(nextBackgroundHex) currentBorder=\(currentChromeColors.borderHex ?? "nil") nextBorder=\(nextChromeColors.borderHex ?? "nil") noop=\(isNoOp)" + ) + } + + if isNoOp { return } bonsplitController.configuration.appearance.chromeColors = nextChromeColors diff --git a/Sources/WorkspaceContentView.swift b/Sources/WorkspaceContentView.swift index ad1d48d2..ebe6a414 100644 --- a/Sources/WorkspaceContentView.swift +++ b/Sources/WorkspaceContentView.swift @@ -9,7 +9,7 @@ struct WorkspaceContentView: View { let isWorkspaceVisible: Bool let isWorkspaceInputActive: Bool let workspacePortalPriority: Int - @State private var config = WorkspaceContentView.resolveGhosttyAppearanceConfig() + @State private var config = WorkspaceContentView.resolveGhosttyAppearanceConfig(reason: "stateInit") @Environment(\.colorScheme) private var colorScheme @EnvironmentObject var notificationStore: TerminalNotificationStore @@ -87,7 +87,7 @@ struct WorkspaceContentView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { syncBonsplitNotificationBadges() - refreshGhosttyAppearanceConfig() + refreshGhosttyAppearanceConfig(reason: "onAppear") } .onChange(of: notificationStore.notifications) { _, _ in syncBonsplitNotificationBadges() @@ -96,17 +96,20 @@ struct WorkspaceContentView: View { syncBonsplitNotificationBadges() } .onReceive(NotificationCenter.default.publisher(for: .ghosttyConfigDidReload)) { _ in - refreshGhosttyAppearanceConfig() + refreshGhosttyAppearanceConfig(reason: "ghosttyConfigDidReload") } - .onChange(of: colorScheme) { _, _ in + .onChange(of: colorScheme) { oldValue, newValue in // Keep split overlay color/opacity in sync with light/dark theme transitions. - refreshGhosttyAppearanceConfig() + refreshGhosttyAppearanceConfig(reason: "colorSchemeChanged:\(oldValue)->\(newValue)") } .onReceive(NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)) { notification in if let backgroundColor = notification.userInfo?[GhosttyNotificationKey.backgroundColor] as? NSColor { - refreshGhosttyAppearanceConfig(backgroundOverride: backgroundColor) + refreshGhosttyAppearanceConfig( + reason: "ghosttyDefaultBackgroundDidChange:withPayload", + backgroundOverride: backgroundColor + ) } else { - refreshGhosttyAppearanceConfig() + refreshGhosttyAppearanceConfig(reason: "ghosttyDefaultBackgroundDidChange:withoutPayload") } } } @@ -142,20 +145,51 @@ struct WorkspaceContentView: View { } static func resolveGhosttyAppearanceConfig( + reason: String = "unspecified", backgroundOverride: NSColor? = nil, loadConfig: () -> GhosttyConfig = GhosttyConfig.load, defaultBackground: () -> NSColor = { GhosttyApp.shared.defaultBackgroundColor } ) -> GhosttyConfig { var next = loadConfig() - next.backgroundColor = backgroundOverride ?? defaultBackground() + let loadedBackgroundHex = next.backgroundColor.hexString() + let defaultBackgroundHex: String + let resolvedBackground: NSColor + + if let backgroundOverride { + resolvedBackground = backgroundOverride + defaultBackgroundHex = "skipped" + } else { + let fallback = defaultBackground() + resolvedBackground = fallback + defaultBackgroundHex = fallback.hexString() + } + + next.backgroundColor = resolvedBackground + if GhosttyApp.shared.backgroundLogEnabled { + GhosttyApp.shared.logBackground( + "theme resolve reason=\(reason) loadedBg=\(loadedBackgroundHex) overrideBg=\(backgroundOverride?.hexString() ?? "nil") defaultBg=\(defaultBackgroundHex) finalBg=\(next.backgroundColor.hexString()) theme=\(next.theme ?? "nil")" + ) + } return next } - private func refreshGhosttyAppearanceConfig(backgroundOverride: NSColor? = nil) { - let next = Self.resolveGhosttyAppearanceConfig(backgroundOverride: backgroundOverride) + private func refreshGhosttyAppearanceConfig(reason: String, backgroundOverride: NSColor? = nil) { + let previousBackgroundHex = config.backgroundColor.hexString() + let next = Self.resolveGhosttyAppearanceConfig( + reason: reason, + backgroundOverride: backgroundOverride + ) + logTheme( + "theme refresh workspace=\(workspace.id.uuidString) reason=\(reason) previousBg=\(previousBackgroundHex) nextBg=\(next.backgroundColor.hexString()) overrideBg=\(backgroundOverride?.hexString() ?? "nil")" + ) config = next workspace.applyGhosttyChrome(from: next) } + + private func logTheme(_ message: String) { + guard GhosttyApp.shared.backgroundLogEnabled else { return } + GhosttyApp.shared.logBackground(message) + } } extension WorkspaceContentView {