diff --git a/bin/swap_mode.sh b/bin/swap_mode.sh index 75b339b..b5d313f 100755 --- a/bin/swap_mode.sh +++ b/bin/swap_mode.sh @@ -10,7 +10,6 @@ CLAUDE_CONF="${HOME}/.claude.json" [ -f "$GEMINI_CONF" ] || { echo "Missing $GEMINI_CONF"; exit 1; } [ -f "$CLAUDE_CONF" ] || { echo "Missing $CLAUDE_CONF"; exit 1; } -which sed set_light_mode() { sed -E -i 's/^([[:space:]]*theme[[:space:]]*=[[:space:]]*)zenwritten-dark/\1zenwritten-light/' "$GHOSTTY_CONF" sed -E -i 's/(vim.g.light_mode[[:space:]]*=[[:space:]]*).*/\1true/' "$NVIM_CONF" @@ -61,11 +60,7 @@ for dir in "${XDG_RUNTIME_DIR:-}" "${TMPDIR:-/tmp}" "/tmp" "$HOME/.local/state/n done -# Reload Ghostty via AppleScript menu click -osascript -e 'tell application "System Events" - tell process "Ghostty" - click menu item "Reload Configuration" of menu "Ghostty" of menu bar item "Ghostty" of menu bar 1 - end tell -end tell' 2>/dev/null || true +# Reload Ghostty config via signal (no Accessibility permissions required) +kill -SIGUSR2 $(ps aux | grep "[G]hostty.app" | awk '{print $2}') echo "Switched to ${MODE} mode" diff --git a/bin/theme_listener.swift b/bin/theme_listener.swift new file mode 100644 index 0000000..1c5c36e --- /dev/null +++ b/bin/theme_listener.swift @@ -0,0 +1,52 @@ +import Cocoa +import Foundation + +func currentMode() -> String { + let style = UserDefaults(suiteName: ".GlobalPreferences")?.string(forKey: "AppleInterfaceStyle") + return style == "Dark" ? "dark" : "light" +} + +func runSwapScript(mode: String) { + let binDir = URL(fileURLWithPath: CommandLine.arguments[0]) + .resolvingSymlinksInPath() + .deletingLastPathComponent() + .path + let script = "\(binDir)/swap_mode.sh" + + let process = Process() + process.executableURL = URL(fileURLWithPath: "/bin/bash") + process.arguments = [script, mode] + var env = ProcessInfo.processInfo.environment + let brewPaths = "/opt/homebrew/opt/gnu-sed/libexec/gnubin/:/opt/homebrew/bin:/usr/local/bin" + env["PATH"] = "\(brewPaths):\(env["PATH"] ?? "/usr/bin:/bin:/usr/sbin:/sbin")" + process.environment = env + DispatchQueue.global().async { + do { + try process.run() + process.waitUntilExit() + } catch { + fputs("theme_listener: failed to run swap_mode.sh: \(error)\n", stderr) + } + } +} + +// Sync to current state on startup +let initial = currentMode() +print("theme_listener: startup mode = \(initial)") +runSwapScript(mode: initial) + +// Listen for system theme changes +DistributedNotificationCenter.default().addObserver( + forName: Notification.Name("AppleInterfaceThemeChangedNotification"), + object: nil, + queue: .main +) { _ in + // Brief delay so UserDefaults reflects the new value + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + let mode = currentMode() + print("theme_listener: changed to \(mode)") + runSwapScript(mode: mode) + } +} + +RunLoop.main.run() diff --git a/config/ghostty/config b/config/ghostty/config index 4b261f0..5ecbff1 100644 --- a/config/ghostty/config +++ b/config/ghostty/config @@ -1,4 +1,5 @@ # --- Basics +auto-update-channel = tip font-family = "IosevkaTerm Nerd Font Propo" font-style = "Regular" font-size = 13