Skip to content

Instantly share code, notes, and snippets.

@sj26
Last active February 5, 2024 10:37
Show Gist options
  • Save sj26/66871cc0c7d856402015f8810771d068 to your computer and use it in GitHub Desktop.
Save sj26/66871cc0c7d856402015f8810771d068 to your computer and use it in GitHub Desktop.
Apple Terminal Default Window Settings Changer
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.sj26.TerminalAppearanceThemeChanger</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<!--
<key>LaunchEvents</key>
<dict>
<key>com.apple.notifyd.matching</key>
<dict>
<key>AppleInterfaceThemeChangedNotification</key>
<dict>
<key>Notification</key>
<string>AppleInterfaceThemeChangedNotification</string>
</dict>
</dict>
</dict>
-->
<key>ProgramArguments</key>
<array>
<string>/usr/bin/swift</string>
<string>.../main.swift</string>
</array>
</dict>
</plist>
#!/usr/bin/swift
// Starts a service which listens for interface theme changes (light mode, or dark mode)
// and changes the Terminal default settings and the settings of any open tabs to match.
import Foundation
import Cocoa
import ScriptingBridge
@objc public protocol SBObjectProtocol: NSObjectProtocol {
func get() -> Any!
}
@objc public protocol SBApplicationProtocol: SBObjectProtocol {
func activate()
var delegate: SBApplicationDelegate! { get set }
var isRunning: Bool { get }
}
// MARK: TerminalSaveOptions
@objc public enum TerminalSaveOptions : AEKeyword {
case yes = 0x79657320 /* 'yes ' */
case no = 0x6e6f2020 /* 'no ' */
case ask = 0x61736b20 /* 'ask ' */
}
// MARK: TerminalPrintingErrorHandling
@objc public enum TerminalPrintingErrorHandling : AEKeyword {
case standard = 0x6c777374 /* 'lwst' */
case detailed = 0x6c776474 /* 'lwdt' */
}
// MARK: TerminalGenericMethods
@objc public protocol TerminalGenericMethods {
@objc optional func closeSaving(_ saving: TerminalSaveOptions, savingIn: Any!) // Close a document.
@objc optional func saveIn(_ in_: Any!) // Save a document.
@objc optional func printWithProperties(_ withProperties: Any!, printDialog: Any!) // Print a document.
@objc optional func delete() // Delete an object.
@objc optional func duplicateTo(_ to: Any!, withProperties: Any!) // Copy object(s) and put the copies at a new location.
@objc optional func exists() // Verify if an object exists.
@objc optional func moveTo(_ to: Any!) // Move object(s) to a new location.
}
// MARK: TerminalApplication
@objc public protocol TerminalApplication: SBApplicationProtocol {
@objc optional func windows() -> [TerminalWindow]
@objc optional var name: String { get } // The name of the application.
@objc optional var frontmost: Bool { get } // Is this the frontmost (active) application?
@objc optional var version: String { get } // The version of the application.
@objc optional func `open`(_ x: Any!) // Open a document.
@objc optional func print(_ x: Any!, withProperties: Any!, printDialog: Any!) // Print a document.
@objc optional func quitSaving(_ saving: TerminalSaveOptions) // Quit the application.
@objc optional func doScript(_ x: Any!, in in_: Any!) -> TerminalTab // Runs a UNIX shell script or command.
@objc optional func settingsSets() -> [TerminalSettingsSet]
@objc optional var defaultSettings: TerminalSettingsSet { get } // The settings set used for new windows.
@objc optional var startupSettings: TerminalSettingsSet { get } // The settings set used for the window created on application startup.
@objc optional func setDefaultSettings(_ defaultSettings: TerminalSettingsSet!) // The settings set used for new windows.
@objc optional func setStartupSettings(_ startupSettings: TerminalSettingsSet!) // The settings set used for the window created on application startup.
}
extension SBApplication: TerminalApplication {}
// MARK: TerminalWindow
@objc public protocol TerminalWindow: SBObjectProtocol, TerminalGenericMethods {
@objc optional func tabs() -> [TerminalTab]
@objc optional var name: String { get } // The full title of the window.
@objc optional func id() // The unique identifier of the window.
@objc optional var index: Int { get } // The index of the window, ordered front to back.
@objc optional var bounds: Int { get } // The bounding rectangle of the window.
@objc optional var closeable: Bool { get } // Whether the window has a close box.
@objc optional var miniaturizable: Bool { get } // Whether the window can be minimized.
@objc optional var miniaturized: Bool { get } // Whether the window is currently minimized.
@objc optional var resizable: Bool { get } // Whether the window can be resized.
@objc optional var visible: Bool { get } // Whether the window is currently visible.
@objc optional var zoomable: Bool { get } // Whether the window can be zoomed.
@objc optional var zoomed: Bool { get } // Whether the window is currently zoomed.
@objc optional var frontmost: Bool { get } // Whether the window is currently the frontmost Terminal window.
@objc optional var selectedTab: TerminalTab { get }
@objc optional var position: Int { get } // The position of the window, relative to the upper left corner of the screen.
@objc optional var origin: Int { get } // The position of the window, relative to the lower left corner of the screen.
@objc optional var size: Int { get } // The width and height of the window
@objc optional var frame: Int { get } // The bounding rectangle, relative to the lower left corner of the screen.
@objc optional func setIndex(_ index: Int) // The index of the window, ordered front to back.
@objc optional func setBounds(_ bounds: Int) // The bounding rectangle of the window.
@objc optional func setMiniaturized(_ miniaturized: Int) // Whether the window is currently minimized.
@objc optional func setVisible(_ visible: Int) // Whether the window is currently visible.
@objc optional func setZoomed(_ zoomed: Int) // Whether the window is currently zoomed.
@objc optional func setFrontmost(_ frontmost: Int) // Whether the window is currently the frontmost Terminal window.
@objc optional func setSelectedTab(_ selectedTab: TerminalTab!)
@objc optional func setPosition(_ position: Int) // The position of the window, relative to the upper left corner of the screen.
@objc optional func setOrigin(_ origin: Int) // The position of the window, relative to the lower left corner of the screen.
@objc optional func setSize(_ size: Int) // The width and height of the window
@objc optional func setFrame(_ frame: Int) // The bounding rectangle, relative to the lower left corner of the screen.
}
extension SBObject: TerminalWindow {}
// MARK: TerminalSettingsSet
@objc public protocol TerminalSettingsSet: SBObjectProtocol, TerminalGenericMethods {
@objc optional func id() // The unique identifier of the settings set.
@objc optional var name: String { get } // The name of the settings set.
@objc optional var numberOfRows: Int { get } // The number of rows displayed in the tab.
@objc optional var numberOfColumns: Int { get } // The number of columns displayed in the tab.
@objc optional var cursorColor: Int { get } // The cursor color for the tab.
@objc optional var backgroundColor: Int { get } // The background color for the tab.
@objc optional var normalTextColor: Int { get } // The normal text color for the tab.
@objc optional var boldTextColor: Int { get } // The bold text color for the tab.
@objc optional var fontName: Int { get } // The name of the font used to display the tab’s contents.
@objc optional var fontSize: Int { get } // The size of the font used to display the tab’s contents.
@objc optional var fontAntialiasing: Int { get } // Whether the font used to display the tab’s contents is antialiased.
@objc optional var titleDisplaysDeviceName: Int { get } // Whether the title contains the device name.
@objc optional var titleDisplaysShellPath: Int { get } // Whether the title contains the shell path.
@objc optional var titleDisplaysWindowSize: Int { get } // Whether the title contains the tab’s size, in rows and columns.
@objc optional var titleDisplaysSettingsName: Int { get } // Whether the title contains the settings name.
@objc optional var titleDisplaysCustomTitle: Int { get } // Whether the title contains a custom title.
@objc optional var customTitle: Int { get } // The tab’s custom title.
@objc optional func setName(_ name: Int) // The name of the settings set.
@objc optional func setNumberOfRows(_ numberOfRows: Int) // The number of rows displayed in the tab.
@objc optional func setNumberOfColumns(_ numberOfColumns: Int) // The number of columns displayed in the tab.
@objc optional func setCursorColor(_ cursorColor: Int) // The cursor color for the tab.
@objc optional func setBackgroundColor(_ backgroundColor: Int) // The background color for the tab.
@objc optional func setNormalTextColor(_ normalTextColor: Int) // The normal text color for the tab.
@objc optional func setBoldTextColor(_ boldTextColor: Int) // The bold text color for the tab.
@objc optional func setFontName(_ fontName: Int) // The name of the font used to display the tab’s contents.
@objc optional func setFontSize(_ fontSize: Int) // The size of the font used to display the tab’s contents.
@objc optional func setFontAntialiasing(_ fontAntialiasing: Int) // Whether the font used to display the tab’s contents is antialiased.
@objc optional func setTitleDisplaysDeviceName(_ titleDisplaysDeviceName: Int) // Whether the title contains the device name.
@objc optional func setTitleDisplaysShellPath(_ titleDisplaysShellPath: Int) // Whether the title contains the shell path.
@objc optional func setTitleDisplaysWindowSize(_ titleDisplaysWindowSize: Int) // Whether the title contains the tab’s size, in rows and columns.
@objc optional func setTitleDisplaysSettingsName(_ titleDisplaysSettingsName: Int) // Whether the title contains the settings name.
@objc optional func setTitleDisplaysCustomTitle(_ titleDisplaysCustomTitle: Int) // Whether the title contains a custom title.
@objc optional func setCustomTitle(_ customTitle: Int) // The tab’s custom title.
}
extension SBObject: TerminalSettingsSet {}
// MARK: TerminalTab
@objc public protocol TerminalTab: SBObjectProtocol, TerminalGenericMethods {
@objc optional var numberOfRows: Int { get } // The number of rows displayed in the tab.
@objc optional var numberOfColumns: Int { get } // The number of columns displayed in the tab.
@objc optional var contents: Int { get } // The currently visible contents of the tab.
@objc optional var history: Int { get } // The contents of the entire scrolling buffer of the tab.
@objc optional var busy: Int { get } // Whether the tab is busy running a process.
@objc optional var selected: Int { get } // Whether the tab is selected.
@objc optional var titleDisplaysCustomTitle: Int { get } // Whether the title contains a custom title.
@objc optional var customTitle: Int { get } // The tab’s custom title.
@objc optional var tty: Int { get } // The tab’s TTY device.
@objc optional var currentSettings: TerminalSettingsSet { get } // The set of settings which control the tab’s behavior and appearance.
@objc optional var cursorColor: Int { get } // The cursor color for the tab.
@objc optional var backgroundColor: Int { get } // The background color for the tab.
@objc optional var normalTextColor: Int { get } // The normal text color for the tab.
@objc optional var boldTextColor: Int { get } // The bold text color for the tab.
@objc optional var titleDisplaysDeviceName: Int { get } // Whether the title contains the device name.
@objc optional var titleDisplaysShellPath: Int { get } // Whether the title contains the shell path.
@objc optional var titleDisplaysWindowSize: Int { get } // Whether the title contains the tab’s size, in rows and columns.
@objc optional var titleDisplaysFileName: Int { get } // Whether the title contains the file name.
@objc optional var fontName: Int { get } // The name of the font used to display the tab’s contents.
@objc optional var fontSize: Int { get } // The size of the font used to display the tab’s contents.
@objc optional var fontAntialiasing: Int { get } // Whether the font used to display the tab’s contents is antialiased.
@objc optional func setNumberOfRows(_ numberOfRows: Int) // The number of rows displayed in the tab.
@objc optional func setNumberOfColumns(_ numberOfColumns: Int) // The number of columns displayed in the tab.
@objc optional func setSelected(_ selected: Int) // Whether the tab is selected.
@objc optional func setTitleDisplaysCustomTitle(_ titleDisplaysCustomTitle: Int) // Whether the title contains a custom title.
@objc optional func setCustomTitle(_ customTitle: Int) // The tab’s custom title.
@objc optional func setCurrentSettings(_ currentSettings: TerminalSettingsSet!) // The set of settings which control the tab’s behavior and appearance.
@objc optional func setCursorColor(_ cursorColor: Int) // The cursor color for the tab.
@objc optional func setBackgroundColor(_ backgroundColor: Int) // The background color for the tab.
@objc optional func setNormalTextColor(_ normalTextColor: Int) // The normal text color for the tab.
@objc optional func setBoldTextColor(_ boldTextColor: Int) // The bold text color for the tab.
@objc optional func setTitleDisplaysDeviceName(_ titleDisplaysDeviceName: Int) // Whether the title contains the device name.
@objc optional func setTitleDisplaysShellPath(_ titleDisplaysShellPath: Int) // Whether the title contains the shell path.
@objc optional func setTitleDisplaysWindowSize(_ titleDisplaysWindowSize: Int) // Whether the title contains the tab’s size, in rows and columns.
@objc optional func setTitleDisplaysFileName(_ titleDisplaysFileName: Int) // Whether the title contains the file name.
@objc optional func setFontName(_ fontName: Int) // The name of the font used to display the tab’s contents.
@objc optional func setFontSize(_ fontSize: Int) // The size of the font used to display the tab’s contents.
@objc optional func setFontAntialiasing(_ fontAntialiasing: Int) // Whether the font used to display the tab’s contents is antialiased.
}
extension SBObject: TerminalTab {}
// Update the terminal default settings, and if it's running also
// change the settings of any open tab to the new default
func updateTerminalDefaultSettings() {
let terminalDefaults = UserDefaults(suiteName: "com.apple.Terminal")!
let isDark = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark"
let theme = isDark ? "Tango Dark" : "Tango Light"
terminalDefaults.set(theme, forKey: "Default Window Settings")
print("Default Window Settings are now", terminalDefaults.string(forKey: "Default Window Settings")!)
let terminal = SBApplication(bundleIdentifier: "com.apple.Terminal")! as TerminalApplication
if terminal.isRunning {
let settings = terminal.settingsSets!().first(where: { (settings) -> Bool in
settings.name == theme
})
terminal.setDefaultSettings!(settings)
for window in terminal.windows!() {
for tab in window.tabs!() {
tab.setCurrentSettings!(settings)
}
}
}
}
// Listen for when the interface theme changes and trigger an update
DistributedNotificationCenter.default.addObserver(forName: Notification.Name("AppleInterfaceThemeChangedNotification"), object: nil, queue: nil) { (notification) in
print(notification)
updateTerminalDefaultSettings()
}
// Do an initial update when the run loop starts
RunLoop.current.perform {
updateTerminalDefaultSettings()
}
// Enter cocoa run loop and start listening for notifyd events
NSApplication.shared.run()
@deecewan
Copy link

deecewan commented Feb 5, 2024

i don't think it's possible to listen to DistributedNotificationCenter events with the launch plist 😭

@sj26
Copy link
Author

sj26 commented Feb 5, 2024

ohai DBS 😅

Nah I haven't found a way to do it based on events via launchd. I just run this utility in the background all the time. But it's pretty tiny, and works a treat.

@deecewan
Copy link

deecewan commented Feb 5, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment