From 25fa58143ec6d8e6eb200b459838f16376423fdf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2026 14:57:31 -0800 Subject: [PATCH] macos: add macos-applescript config --- macos/AGENTS.md | 3 ++ .../AppleScript/AppDelegate+AppleScript.swift | 37 +++++++++++++++++-- .../AppleScript/ScriptCloseCommand.swift | 6 +++ .../AppleScript/ScriptFocusCommand.swift | 6 +++ .../AppleScript/ScriptInputTextCommand.swift | 2 + .../AppleScript/ScriptKeyEventCommand.swift | 2 + .../ScriptMouseButtonCommand.swift | 2 + .../AppleScript/ScriptMousePosCommand.swift | 2 + .../ScriptMouseScrollCommand.swift | 2 + .../AppleScript/ScriptSplitCommand.swift | 2 + .../Features/AppleScript/ScriptTab.swift | 17 +++++++-- .../Features/AppleScript/ScriptTerminal.swift | 11 ++++-- .../Features/AppleScript/ScriptWindow.swift | 31 +++++++++++----- macos/Sources/Ghostty/Ghostty.Config.swift | 8 ++++ src/config/Config.zig | 10 +++++ 15 files changed, 122 insertions(+), 19 deletions(-) diff --git a/macos/AGENTS.md b/macos/AGENTS.md index 22da2a4c2..1a0c84c32 100644 --- a/macos/AGENTS.md +++ b/macos/AGENTS.md @@ -13,6 +13,9 @@ ## AppleScript - The AppleScript scripting definition is in `macos/Ghostty.sdef`. +- Guard AppleScript entry points and object accessors with the + `macos-applescript` configuration (use `NSApp.isAppleScriptEnabled` + and `NSApp.validateScript(command:)` where applicable). - In `macos/Ghostty.sdef`, keep top-level definitions in this order: 1. Classes 2. Records diff --git a/macos/Sources/Features/AppleScript/AppDelegate+AppleScript.swift b/macos/Sources/Features/AppleScript/AppDelegate+AppleScript.swift index 1071f9f67..647aaba6a 100644 --- a/macos/Sources/Features/AppleScript/AppDelegate+AppleScript.swift +++ b/macos/Sources/Features/AppleScript/AppDelegate+AppleScript.swift @@ -29,6 +29,8 @@ extension NSApplication { /// such as `windows`, `window 1`, or `every window whose ...`. @objc(scriptWindows) var scriptWindows: [ScriptWindow] { + guard isAppleScriptEnabled else { return [] } + // AppKit exposes one NSWindow per tab. AppleScript users expect one // top-level window object containing multiple tabs, so we dedupe tab // siblings into a single ScriptWindow. @@ -64,7 +66,8 @@ extension NSApplication { /// Returning `nil` makes the object specifier fail naturally. @objc(valueInScriptWindowsWithUniqueID:) func valueInScriptWindows(uniqueID: String) -> ScriptWindow? { - scriptWindows.first(where: { $0.stableID == uniqueID }) + guard isAppleScriptEnabled else { return nil } + return scriptWindows.first(where: { $0.stableID == uniqueID }) } } @@ -77,7 +80,8 @@ extension NSApplication { /// Required selector name: `terminals`. @objc(terminals) var terminals: [ScriptTerminal] { - allSurfaceViews.map(ScriptTerminal.init) + guard isAppleScriptEnabled else { return [] } + return allSurfaceViews.map(ScriptTerminal.init) } /// Enables AppleScript unique-ID lookup for terminal references. @@ -89,7 +93,8 @@ extension NSApplication { /// `terminal id "..."` even as windows/tabs change. @objc(valueInTerminalsWithUniqueID:) func valueInTerminals(uniqueID: String) -> ScriptTerminal? { - allSurfaceViews + guard isAppleScriptEnabled else { return nil } + return allSurfaceViews .first(where: { $0.id.uuidString == uniqueID }) .map(ScriptTerminal.init) } @@ -111,6 +116,8 @@ extension NSApplication { /// We return a Bool to match the command's declared result type. @objc(handlePerformActionScriptCommand:) func handlePerformActionScriptCommand(_ command: NSScriptCommand) -> Any? { + guard validateScript(command: command) else { return nil } + guard let action = command.directParameter as? String else { command.scriptErrorNumber = errAEParamMissed command.scriptErrorString = "Missing action string." @@ -129,6 +136,8 @@ extension NSApplication { /// Handler for creating a reusable AppleScript surface configuration object. @objc(handleNewSurfaceConfigurationScriptCommand:) func handleNewSurfaceConfigurationScriptCommand(_ command: NSScriptCommand) -> Any? { + guard validateScript(command: command) else { return nil } + do { let configuration = try Ghostty.SurfaceConfiguration( scriptRecord: command.evaluatedArguments?["configuration"] as? NSDictionary @@ -151,6 +160,8 @@ extension NSApplication { /// Returns the newly created scripting window object. @objc(handleNewWindowScriptCommand:) func handleNewWindowScriptCommand(_ command: NSScriptCommand) -> Any? { + guard validateScript(command: command) else { return nil } + guard let appDelegate = delegate as? AppDelegate else { command.scriptErrorNumber = errAEEventFailed command.scriptErrorString = "Ghostty app delegate is unavailable." @@ -195,6 +206,8 @@ extension NSApplication { /// Returns the newly created scripting tab object. @objc(handleNewTabScriptCommand:) func handleNewTabScriptCommand(_ command: NSScriptCommand) -> Any? { + guard validateScript(command: command) else { return nil } + guard let appDelegate = delegate as? AppDelegate else { command.scriptErrorNumber = errAEEventFailed command.scriptErrorString = "Ghostty app delegate is unavailable." @@ -262,6 +275,24 @@ extension NSApplication { @MainActor extension NSApplication { + /// Whether Ghostty should currently accept AppleScript interactions. + var isAppleScriptEnabled: Bool { + guard let appDelegate = delegate as? AppDelegate else { return true } + return appDelegate.ghostty.config.macosAppleScript + } + + /// Applies a consistent error when scripting is disabled by configuration. + @discardableResult + func validateScript(command: NSScriptCommand) -> Bool { + guard isAppleScriptEnabled else { + command.scriptErrorNumber = errAEEventFailed + command.scriptErrorString = "AppleScript is disabled by the macos-applescript configuration." + return false + } + + return true + } + /// Discovers all currently alive terminal surfaces across normal and quick /// terminal windows. This powers both terminal enumeration and ID lookup. fileprivate var allSurfaceViews: [Ghostty.SurfaceView] { diff --git a/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift b/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift index 3f240cf6e..e62d9bb60 100644 --- a/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptCloseCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptCloseCommand) final class ScriptCloseCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let terminal = evaluatedArguments?["terminal"] as? ScriptTerminal else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing terminal target." @@ -37,6 +39,8 @@ final class ScriptCloseCommand: NSScriptCommand { @objc(GhosttyScriptCloseTabCommand) final class ScriptCloseTabCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let tab = evaluatedArguments?["tab"] as? ScriptTab else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing tab target." @@ -70,6 +74,8 @@ final class ScriptCloseTabCommand: NSScriptCommand { @objc(GhosttyScriptCloseWindowCommand) final class ScriptCloseWindowCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let window = evaluatedArguments?["window"] as? ScriptWindow else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing window target." diff --git a/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift b/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift index edda43a45..61b4ab8c6 100644 --- a/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptFocusCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptFocusCommand) final class ScriptFocusCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let terminal = evaluatedArguments?["terminal"] as? ScriptTerminal else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing terminal target." @@ -37,6 +39,8 @@ final class ScriptFocusCommand: NSScriptCommand { @objc(GhosttyScriptActivateWindowCommand) final class ScriptActivateWindowCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let window = evaluatedArguments?["window"] as? ScriptWindow else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing window target." @@ -60,6 +64,8 @@ final class ScriptActivateWindowCommand: NSScriptCommand { @objc(GhosttyScriptSelectTabCommand) final class ScriptSelectTabCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let tab = evaluatedArguments?["tab"] as? ScriptTab else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing tab target." diff --git a/macos/Sources/Features/AppleScript/ScriptInputTextCommand.swift b/macos/Sources/Features/AppleScript/ScriptInputTextCommand.swift index 729c20e6c..9662de343 100644 --- a/macos/Sources/Features/AppleScript/ScriptInputTextCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptInputTextCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptInputTextCommand) final class ScriptInputTextCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let text = directParameter as? String else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing text to input." diff --git a/macos/Sources/Features/AppleScript/ScriptKeyEventCommand.swift b/macos/Sources/Features/AppleScript/ScriptKeyEventCommand.swift index 8ac5afb9c..0091098c5 100644 --- a/macos/Sources/Features/AppleScript/ScriptKeyEventCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptKeyEventCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptKeyEventCommand) final class ScriptKeyEventCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let keyName = directParameter as? String else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing key name." diff --git a/macos/Sources/Features/AppleScript/ScriptMouseButtonCommand.swift b/macos/Sources/Features/AppleScript/ScriptMouseButtonCommand.swift index 2070cb1ee..15fe0fbce 100644 --- a/macos/Sources/Features/AppleScript/ScriptMouseButtonCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptMouseButtonCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptMouseButtonCommand) final class ScriptMouseButtonCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let buttonCode = directParameter as? UInt32, let button = ScriptMouseButtonValue(code: buttonCode) else { scriptErrorNumber = errAEParamMissed diff --git a/macos/Sources/Features/AppleScript/ScriptMousePosCommand.swift b/macos/Sources/Features/AppleScript/ScriptMousePosCommand.swift index 8ee56ec91..a044c3b2d 100644 --- a/macos/Sources/Features/AppleScript/ScriptMousePosCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptMousePosCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptMousePosCommand) final class ScriptMousePosCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let x = evaluatedArguments?["x"] as? Double else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing x position." diff --git a/macos/Sources/Features/AppleScript/ScriptMouseScrollCommand.swift b/macos/Sources/Features/AppleScript/ScriptMouseScrollCommand.swift index 6ce24a317..083937eaf 100644 --- a/macos/Sources/Features/AppleScript/ScriptMouseScrollCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptMouseScrollCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptMouseScrollCommand) final class ScriptMouseScrollCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let x = evaluatedArguments?["x"] as? Double else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing x scroll delta." diff --git a/macos/Sources/Features/AppleScript/ScriptSplitCommand.swift b/macos/Sources/Features/AppleScript/ScriptSplitCommand.swift index e74eed78f..30f34fc8e 100644 --- a/macos/Sources/Features/AppleScript/ScriptSplitCommand.swift +++ b/macos/Sources/Features/AppleScript/ScriptSplitCommand.swift @@ -9,6 +9,8 @@ import AppKit @objc(GhosttyScriptSplitCommand) final class ScriptSplitCommand: NSScriptCommand { override func performDefaultImplementation() -> Any? { + guard NSApp.validateScript(command: self) else { return nil } + guard let terminal = evaluatedArguments?["terminal"] as? ScriptTerminal else { scriptErrorNumber = errAEParamMissed scriptErrorString = "Missing terminal target." diff --git a/macos/Sources/Features/AppleScript/ScriptTab.swift b/macos/Sources/Features/AppleScript/ScriptTab.swift index bda694ebc..467fc3d06 100644 --- a/macos/Sources/Features/AppleScript/ScriptTab.swift +++ b/macos/Sources/Features/AppleScript/ScriptTab.swift @@ -34,7 +34,8 @@ final class ScriptTab: NSObject { /// Exposed as the AppleScript `id` property. @objc(id) var idValue: String { - stableID + guard NSApp.isAppleScriptEnabled else { return "" } + return stableID } /// Exposed as the AppleScript `title` property. @@ -42,7 +43,8 @@ final class ScriptTab: NSObject { /// Returns the title of the tab's window. @objc(title) var title: String { - controller?.window?.title ?? "" + guard NSApp.isAppleScriptEnabled else { return "" } + return controller?.window?.title ?? "" } /// Exposed as the AppleScript `index` property. @@ -50,6 +52,7 @@ final class ScriptTab: NSObject { /// Cocoa scripting expects this to be 1-based for user-facing collections. @objc(index) var index: Int { + guard NSApp.isAppleScriptEnabled else { return 0 } guard let controller else { return 0 } return window?.tabIndex(for: controller) ?? 0 } @@ -59,18 +62,21 @@ final class ScriptTab: NSObject { /// Powers script conditions such as `if selected of tab 1 then ...`. @objc(selected) var selected: Bool { + guard NSApp.isAppleScriptEnabled else { return false } guard let controller else { return false } return window?.tabIsSelected(controller) ?? false } /// Best-effort native window containing this tab. var parentWindow: NSWindow? { - controller?.window + guard NSApp.isAppleScriptEnabled else { return nil } + return controller?.window } /// Live controller backing this tab wrapper. var parentController: BaseTerminalController? { - controller + guard NSApp.isAppleScriptEnabled else { return nil } + return controller } /// Exposed as the AppleScript `terminals` element on a tab. @@ -78,6 +84,7 @@ final class ScriptTab: NSObject { /// Returns all terminal surfaces (split panes) within this tab. @objc(terminals) var terminals: [ScriptTerminal] { + guard NSApp.isAppleScriptEnabled else { return [] } guard let controller else { return [] } return (controller.surfaceTree.root?.leaves() ?? []) .map(ScriptTerminal.init) @@ -86,6 +93,7 @@ final class ScriptTab: NSObject { /// Enables unique-ID lookup for `terminals` references on a tab. @objc(valueInTerminalsWithUniqueID:) func valueInTerminals(uniqueID: String) -> ScriptTerminal? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let controller else { return nil } return (controller.surfaceTree.root?.leaves() ?? []) .first(where: { $0.id.uuidString == uniqueID }) @@ -94,6 +102,7 @@ final class ScriptTab: NSObject { /// Provides Cocoa scripting with a canonical "path" back to this object. override var objectSpecifier: NSScriptObjectSpecifier? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let window else { return nil } guard let windowClassDescription = window.classDescription as? NSScriptClassDescription else { return nil diff --git a/macos/Sources/Features/AppleScript/ScriptTerminal.swift b/macos/Sources/Features/AppleScript/ScriptTerminal.swift index 1e47aa7b6..e4500c6b9 100644 --- a/macos/Sources/Features/AppleScript/ScriptTerminal.swift +++ b/macos/Sources/Features/AppleScript/ScriptTerminal.swift @@ -32,13 +32,15 @@ final class ScriptTerminal: NSObject { /// by `NSUniqueIDSpecifier` to re-identify a terminal object in scripts. @objc(id) var stableID: String { - surfaceView?.id.uuidString ?? "" + guard NSApp.isAppleScriptEnabled else { return "" } + return surfaceView?.id.uuidString ?? "" } /// Exposed as the AppleScript `title` property. @objc(title) var title: String { - surfaceView?.title ?? "" + guard NSApp.isAppleScriptEnabled else { return "" } + return surfaceView?.title ?? "" } /// Exposed as the AppleScript `working directory` property. @@ -47,11 +49,13 @@ final class ScriptTerminal: NSObject { /// camel-cased selector name `workingDirectory`. @objc(workingDirectory) var workingDirectory: String { - surfaceView?.pwd ?? "" + guard NSApp.isAppleScriptEnabled else { return "" } + return surfaceView?.pwd ?? "" } /// Used by command handling (`perform action ... on `). func perform(action: String) -> Bool { + guard NSApp.isAppleScriptEnabled else { return false } guard let surfaceModel = surfaceView?.surfaceModel else { return false } return surfaceModel.perform(action: action) } @@ -62,6 +66,7 @@ final class ScriptTerminal: NSObject { /// referenced in follow-up script statements because AppleScript cannot /// express where the object came from (`application.terminals[id]`). override var objectSpecifier: NSScriptObjectSpecifier? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let appClassDescription = NSApplication.shared.classDescription as? NSScriptClassDescription else { return nil } diff --git a/macos/Sources/Features/AppleScript/ScriptWindow.swift b/macos/Sources/Features/AppleScript/ScriptWindow.swift index 97822e973..01a603843 100644 --- a/macos/Sources/Features/AppleScript/ScriptWindow.swift +++ b/macos/Sources/Features/AppleScript/ScriptWindow.swift @@ -39,7 +39,8 @@ final class ScriptWindow: NSObject { /// This is what scripts read with `id of window ...`. @objc(id) var idValue: String { - stableID + guard NSApp.isAppleScriptEnabled else { return "" } + return stableID } /// Exposed as the AppleScript `title` property. @@ -47,7 +48,8 @@ final class ScriptWindow: NSObject { /// Returns the title of the window (from the selected/primary controller's NSWindow). @objc(title) var title: String { - selectedController?.window?.title ?? "" + guard NSApp.isAppleScriptEnabled else { return "" } + return selectedController?.window?.title ?? "" } /// Exposed as the AppleScript `tabs` element. @@ -57,7 +59,8 @@ final class ScriptWindow: NSObject { /// so tab additions/removals are reflected immediately. @objc(tabs) var tabs: [ScriptTab] { - controllers.map { ScriptTab(window: self, controller: $0) } + guard NSApp.isAppleScriptEnabled else { return [] } + return controllers.map { ScriptTab(window: self, controller: $0) } } /// Exposed as the AppleScript `selected tab` property. @@ -65,6 +68,7 @@ final class ScriptWindow: NSObject { /// This powers expressions like `selected tab of window 1`. @objc(selectedTab) var selectedTab: ScriptTab? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let selectedController else { return nil } return ScriptTab(window: self, controller: selectedController) } @@ -77,6 +81,7 @@ final class ScriptWindow: NSObject { /// Cocoa uses this when a script resolves `tab id "..." of window ...`. @objc(valueInTabsWithUniqueID:) func valueInTabs(uniqueID: String) -> ScriptTab? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let controller = controller(tabID: uniqueID) else { return nil } return ScriptTab(window: self, controller: controller) } @@ -86,7 +91,8 @@ final class ScriptWindow: NSObject { /// Returns all terminal surfaces across every tab in this window. @objc(terminals) var terminals: [ScriptTerminal] { - controllers + guard NSApp.isAppleScriptEnabled else { return [] } + return controllers .flatMap { $0.surfaceTree.root?.leaves() ?? [] } .map(ScriptTerminal.init) } @@ -94,7 +100,8 @@ final class ScriptWindow: NSObject { /// Enables unique-ID lookup for `terminals` references on a window. @objc(valueInTerminalsWithUniqueID:) func valueInTerminals(uniqueID: String) -> ScriptTerminal? { - controllers + guard NSApp.isAppleScriptEnabled else { return nil } + return controllers .flatMap { $0.surfaceTree.root?.leaves() ?? [] } .first(where: { $0.id.uuidString == uniqueID }) .map(ScriptTerminal.init) @@ -103,22 +110,26 @@ final class ScriptWindow: NSObject { /// AppleScript tab indexes are 1-based, so we add one to Swift's 0-based /// array index. func tabIndex(for controller: BaseTerminalController) -> Int? { - controllers.firstIndex(where: { $0 === controller }).map { $0 + 1 } + guard NSApp.isAppleScriptEnabled else { return nil } + return controllers.firstIndex(where: { $0 === controller }).map { $0 + 1 } } /// Reports whether a given controller maps to this window's selected tab. func tabIsSelected(_ controller: BaseTerminalController) -> Bool { - selectedController === controller + guard NSApp.isAppleScriptEnabled else { return false } + return selectedController === controller } /// Best-effort native window to use as a tab parent for AppleScript commands. var preferredParentWindow: NSWindow? { - selectedController?.window ?? controllers.first?.window + guard NSApp.isAppleScriptEnabled else { return nil } + return selectedController?.window ?? controllers.first?.window } /// Best-effort controller to use for window-scoped AppleScript commands. var preferredController: BaseTerminalController? { - selectedController ?? controllers.first + guard NSApp.isAppleScriptEnabled else { return nil } + return selectedController ?? controllers.first } /// Resolves a previously generated tab ID back to a live controller. @@ -131,6 +142,7 @@ final class ScriptWindow: NSObject { /// We recalculate on every access so AppleScript immediately sees tab-group /// changes (new tabs, closed tabs, tab moves) without rebuilding all objects. private var controllers: [BaseTerminalController] { + guard NSApp.isAppleScriptEnabled else { return [] } guard let primaryController else { return [] } guard let window = primaryController.window else { return [primaryController] } @@ -168,6 +180,7 @@ final class ScriptWindow: NSObject { /// references for later script statements. This specifier encodes: /// `application -> scriptWindows[id]`. override var objectSpecifier: NSScriptObjectSpecifier? { + guard NSApp.isAppleScriptEnabled else { return nil } guard let appClassDescription = NSApplication.shared.classDescription as? NSScriptClassDescription else { return nil } diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 87ae0511f..239f458e3 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -678,6 +678,14 @@ extension Ghostty { return v } + var macosAppleScript: Bool { + guard let config = self.config else { return true } + var v = false + let key = "macos-applescript" + _ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) + return v + } + var maximize: Bool { guard let config = self.config else { return true } var v = false diff --git a/src/config/Config.zig b/src/config/Config.zig index ca93c85d6..591c0b049 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -3347,6 +3347,16 @@ keybind: Keybinds = .{}, /// you may want to disable it. @"macos-secure-input-indication": bool = true, +/// If true, Ghostty exposes and handles the built-in AppleScript dictionary +/// on macOS. +/// +/// If false, all AppleScript interactions are disabled. This includes +/// AppleScript commands and AppleScript object lookup for windows, tabs, +/// and terminals. +/// +/// The default is true. +@"macos-applescript": bool = true, + /// Customize the macOS app icon. /// /// This only affects the icon that appears in the dock, application