Skip to content

Instantly share code, notes, and snippets.

@sj26
Last active February 5, 2024 10:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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()
@sj26
Copy link
Author

sj26 commented Jan 29, 2020

Ideally this would only launch and run briefly when AppleInterfaceThemeChangedNotification is published, which is what LaunchEvents in the plist is working toward, but I haven't figured out xpc enough to make that work yet.

@soooch
Copy link

soooch commented Apr 20, 2020

Ideally this would only launch and run briefly when AppleInterfaceThemeChangedNotification is published, which is what LaunchEvents in the plist is working toward, but I haven't figured out xpc enough to make that work yet.

Hi, was just wondering if you had figured out if this is possible? Or if there is perhaps a way to trigger with launchd?

@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