From c26da4ea06b30e8a74dea7b7b41c5040d14d3a0e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 09:19:48 -0700 Subject: [PATCH 01/12] pkg/macos: expose carbon API --- pkg/macos/carbon.zig | 5 +++++ pkg/macos/carbon/c.zig | 3 +++ pkg/macos/main.zig | 1 + 3 files changed, 9 insertions(+) create mode 100644 pkg/macos/carbon.zig create mode 100644 pkg/macos/carbon/c.zig diff --git a/pkg/macos/carbon.zig b/pkg/macos/carbon.zig new file mode 100644 index 000000000..8eafaffe6 --- /dev/null +++ b/pkg/macos/carbon.zig @@ -0,0 +1,5 @@ +pub const c = @import("carbon/c.zig").c; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/pkg/macos/carbon/c.zig b/pkg/macos/carbon/c.zig new file mode 100644 index 000000000..248af3c90 --- /dev/null +++ b/pkg/macos/carbon/c.zig @@ -0,0 +1,3 @@ +pub const c = @cImport({ + @cInclude("Carbon/Carbon.h"); +}); diff --git a/pkg/macos/main.zig b/pkg/macos/main.zig index 20274e9c0..ef244fc78 100644 --- a/pkg/macos/main.zig +++ b/pkg/macos/main.zig @@ -1,3 +1,4 @@ +pub const carbon = @import("carbon.zig"); pub const foundation = @import("foundation.zig"); pub const animation = @import("animation.zig"); pub const dispatch = @import("dispatch.zig"); From 0c38f40f0a4e24b00c993a7b2942cccdaec0962c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 10:11:10 -0700 Subject: [PATCH 02/12] macos: secure input manager, global option in app --- macos/Ghostty.xcodeproj/project.pbxproj | 12 ++ macos/Sources/App/macOS/AppDelegate.swift | 9 ++ macos/Sources/App/macOS/MainMenu.xib | 13 +- .../Features/Secure Input/SecureInput.swift | 135 ++++++++++++++++++ 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 macos/Sources/Features/Secure Input/SecureInput.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index b758411cf..88c90ba33 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; }; A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */; }; A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; }; + A57D79272C9C879B001D522E /* SecureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = A57D79262C9C8798001D522E /* SecureInput.swift */; }; A586167C2B7703CC009BDB1D /* fish in Resources */ = {isa = PBXBuildFile; fileRef = A586167B2B7703CC009BDB1D /* fish */; }; A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; }; A59630972AEE163600D64628 /* HostingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59630962AEE163600D64628 /* HostingWindow.swift */; }; @@ -105,6 +106,7 @@ A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Shell.swift; sourceTree = ""; }; A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = ""; }; A571AB1C2A206FC600248498 /* Ghostty-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Ghostty-Info.plist"; sourceTree = ""; }; + A57D79262C9C8798001D522E /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = ""; }; A586167B2B7703CC009BDB1D /* fish */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fish; path = "../zig-out/share/fish"; sourceTree = ""; }; A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; A59630962AEE163600D64628 /* HostingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingWindow.swift; sourceTree = ""; }; @@ -191,6 +193,7 @@ A56D58872ACDE6BE00508D2C /* Services */, A59630982AEE1C4400D64628 /* Terminal */, A5E112912AF73E4D00C6E0C2 /* ClipboardConfirmation */, + A57D79252C9C8782001D522E /* Secure Input */, A534263E2A7DCC5800EBB7A2 /* Settings */, A51BFC1C2B2FB5AB00E92F16 /* About */, A51BFC292B30F69F00E92F16 /* Update */, @@ -295,6 +298,14 @@ path = Services; sourceTree = ""; }; + A57D79252C9C8782001D522E /* Secure Input */ = { + isa = PBXGroup; + children = ( + A57D79262C9C8798001D522E /* SecureInput.swift */, + ); + path = "Secure Input"; + sourceTree = ""; + }; A59630982AEE1C4400D64628 /* Terminal */ = { isa = PBXGroup; children = ( @@ -516,6 +527,7 @@ A5FEB3002ABB69450068369E /* main.swift in Sources */, A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */, A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */, + A57D79272C9C879B001D522E /* SecureInput.swift in Sources */, A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */, A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */, A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */, diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 01031c9a5..60750517c 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -22,6 +22,7 @@ class AppDelegate: NSObject, @IBOutlet private var menuCheckForUpdates: NSMenuItem? @IBOutlet private var menuOpenConfig: NSMenuItem? @IBOutlet private var menuReloadConfig: NSMenuItem? + @IBOutlet private var menuSecureInput: NSMenuItem? @IBOutlet private var menuQuit: NSMenuItem? @IBOutlet private var menuNewWindow: NSMenuItem? @@ -294,6 +295,8 @@ class AppDelegate: NSObject, syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize) syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector) + // TODO: sync secure keyboard entry toggle + // This menu item is NOT synced with the configuration because it disables macOS // global fullscreen keyboard shortcut. The shortcut in the Ghostty config will continue // to work but it won't be reflected in the menu item. @@ -484,4 +487,10 @@ class AppDelegate: NSObject, guard let url = URL(string: "https://github.com/ghostty-org/ghostty") else { return } NSWorkspace.shared.open(url) } + + @IBAction func toggleSecureInput(_ sender: Any) { + let input = SecureInput.shared + input.global.toggle() + self.menuSecureInput?.state = if (input.global) { .on } else { .off } + } } diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib index bbfd59eae..beb411987 100644 --- a/macos/Sources/App/macOS/MainMenu.xib +++ b/macos/Sources/App/macOS/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -35,14 +35,15 @@ + - + @@ -76,6 +77,12 @@ + + + + + + diff --git a/macos/Sources/Features/Secure Input/SecureInput.swift b/macos/Sources/Features/Secure Input/SecureInput.swift new file mode 100644 index 000000000..231306f5d --- /dev/null +++ b/macos/Sources/Features/Secure Input/SecureInput.swift @@ -0,0 +1,135 @@ +import Carbon +import Cocoa +import OSLog + +// Manages the secure keyboard input state. Secure keyboard input is an old Carbon +// API still in use by applications such as Webkit. From the old Carbon docs: +// "When secure event input mode is enabled, keyboard input goes only to the +// application with keyboard focus and is not echoed to other applications that +// might be using the event monitor target to watch keyboard input." +// +// Secure input is global and stateful so you need a singleton class to manage +// it. You have to yield secure input on application deactivation (because +// it'll affect other apps) and reacquire on reactivation, and every enable +// needs to be balanced with a disable. +class SecureInput { + static let shared = SecureInput() + + private static let logger = Logger( + subsystem: Bundle.main.bundleIdentifier!, + category: String(describing: SecureInput.self) + ) + + // True if you want to enable secure input globally. + var global: Bool = false { + didSet { + apply() + } + } + + // The scoped objects and whether they're currently in focus. + private var scoped: [ObjectIdentifier: Bool] = [:] + + // This is set to true when we've successfully called EnableSecureInput. + private var enabled: Bool = false + + // This is true if we want to enable secure input. We want to enable + // secure input if its enabled globally or any of the scoped objects are + // in focus. + private var desired: Bool { + global || scoped.contains(where: { $0.value }) + } + + private init() { + // Add notifications for application active/resign so we can disable + // secure input. This is only useful for global enabling of secure + // input. + let center = NotificationCenter.default + center.addObserver( + self, + selector: #selector(onDidResignActive(notification:)), + name: NSApplication.didResignActiveNotification, + object: nil) + center.addObserver( + self, + selector: #selector(onDidBecomeActive(notification:)), + name: NSApplication.didBecomeActiveNotification, + object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + + // Reset our state so that we can ensure we set the proper secure input + // system state + scoped.removeAll() + global = false + apply() + } + + // Add a scoped object that has secure input enabled. The focused value will + // determine if the object currently has focus. This is used so that secure + // input is only enabled while the object is focused. + func setScoped(_ object: ObjectIdentifier, focused: Bool) { + scoped[object] = focused + apply() + } + + // Remove a scoped object completely. + func removeScoped(_ object: ObjectIdentifier) { + scoped[object] = nil + apply() + } + + private func apply() { + // If we aren't active then we don't do anything. The become/resign + // active notifications will handle applying for us. + guard NSApp.isActive else { return } + + // We only need to apply if we're not in our desired state + guard enabled != desired else { return } + + let err: OSStatus + if (enabled) { + err = DisableSecureEventInput() + } else { + err = EnableSecureEventInput() + } + if (err == noErr) { + enabled = desired + Self.logger.debug("secure input state=\(self.enabled)") + return + } + + Self.logger.warning("secure input apply failed err=\(err)") + } + + // MARK: Notifications + + @objc private func onDidBecomeActive(notification: NSNotification) { + // We only want to re-enable if we're not already enabled and we + // desire to be enabled. + guard !enabled && desired else { return } + let err = EnableSecureEventInput() + if (err == noErr) { + enabled = true + Self.logger.debug("secure input enabled on activation") + return + } + + Self.logger.warning("secure input apply failed err=\(err)") + } + + @objc private func onDidResignActive(notification: NSNotification) { + // We only want to disable if we're enabled. + guard enabled else { return } + let err = DisableSecureEventInput() + if (err == noErr) { + enabled = false + Self.logger.debug("secure input disabled on deactivation") + return + } + + Self.logger.warning("secure input apply failed err=\(err)") + } +} From c0e0eff46829fcfdf153c1a6b3dd84fed93fd5c9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 10:20:20 -0700 Subject: [PATCH 03/12] core: add toggle_secure_input keybinding --- include/ghostty.h | 2 ++ macos/Sources/App/macOS/AppDelegate.swift | 2 +- macos/Sources/Ghostty/Ghostty.App.swift | 9 ++++++++- src/Surface.zig | 6 ++++++ src/apprt/embedded.zig | 12 ++++++++++++ src/input/Binding.zig | 10 ++++++++++ 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index b413dec41..b4f6a89a2 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -464,6 +464,7 @@ typedef void (*ghostty_runtime_show_desktop_notification_cb)(void*, typedef void ( *ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e); typedef void (*ghostty_runtime_mouse_over_link_cb)(void*, const char*, size_t); +typedef void (*ghostty_runtime_toggle_secure_input_cb)(); typedef struct { void* userdata; @@ -494,6 +495,7 @@ typedef struct { ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb; ghostty_runtime_update_renderer_health update_renderer_health_cb; ghostty_runtime_mouse_over_link_cb mouse_over_link_cb; + ghostty_runtime_toggle_secure_input_cb toggle_secure_input_cb; } ghostty_runtime_config_s; //------------------------------------------------------------------- diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 60750517c..d686474f1 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -295,7 +295,7 @@ class AppDelegate: NSObject, syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize) syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector) - // TODO: sync secure keyboard entry toggle + syncMenuShortcut(action: "toggle_secure_input", menuItem: self.menuSecureInput) // This menu item is NOT synced with the configuration because it disables macOS // global fullscreen keyboard shortcut. The shortcut in the Ghostty config will continue diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 69cbfbfc6..190bb3224 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -94,7 +94,8 @@ extension Ghostty { show_desktop_notification_cb: { userdata, title, body in App.showUserNotification(userdata, title: title, body: body) }, update_renderer_health_cb: { userdata, health in App.updateRendererHealth(userdata, health: health) }, - mouse_over_link_cb: { userdata, ptr, len in App.mouseOverLink(userdata, uri: ptr, len: len) } + mouse_over_link_cb: { userdata, ptr, len in App.mouseOverLink(userdata, uri: ptr, len: len) }, + toggle_secure_input_cb: { App.toggleSecureInput() } ) // Create the ghostty app. @@ -299,6 +300,7 @@ extension Ghostty { static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer?, body: UnsafePointer?) {} static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {} static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer?, len: Int) {} + static func toggleSecureInput() {} #endif #if os(macOS) @@ -544,6 +546,11 @@ extension Ghostty { surfaceView.hoverUrl = String(data: buffer, encoding: .utf8) } + static func toggleSecureInput() { + guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return } + appDelegate.toggleSecureInput(self) + } + static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer?, body: UnsafePointer?) { let surfaceView = self.surfaceUserdata(from: userdata) guard let title = String(cString: title!, encoding: .utf8) else { return } diff --git a/src/Surface.zig b/src/Surface.zig index cb7f8a9ae..b0c3d67f4 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3717,6 +3717,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } else log.warn("runtime doesn't implement toggleWindowDecorations", .{}); }, + .toggle_secure_input => { + if (@hasDecl(apprt.Surface, "toggleSecureInput")) { + self.rt_surface.toggleSecureInput(); + } else log.warn("runtime doesn't implement toggleSecureInput", .{}); + }, + .select_all => { const sel = self.io.terminal.screen.selectAll(); if (sel) |s| { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 9127bb5bd..846805f77 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -133,6 +133,9 @@ pub const App = struct { /// parameter. The link target will be null if the mouse is no longer /// over a link. mouse_over_link: ?*const fn (SurfaceUD, ?[*]const u8, usize) void = null, + + /// Toggle secure input for the application. + toggle_secure_input: ?*const fn () callconv(.C) void = null, }; core_app: *CoreApp, @@ -1005,6 +1008,15 @@ pub const Surface = struct { func(self.userdata, nonNativeFullscreen); } + pub fn toggleSecureInput(self: *Surface) void { + const func = self.app.opts.toggle_secure_input orelse { + log.info("runtime embedder does not toggle_secure_input", .{}); + return; + }; + + func(); + } + pub fn newTab(self: *const Surface) !void { const func = self.app.opts.new_tab orelse { log.info("runtime embedder does not support new_tab", .{}); diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 37b18f581..b347d263b 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -297,6 +297,16 @@ pub const Action = union(enum) { /// Toggle window decorations on and off. This only works on Linux. toggle_window_decorations: void, + /// Toggle secure input mode on or off. This is used to prevent apps + /// that monitor input from seeing what you type. This is useful for + /// entering passwords or other sensitive information. + /// + /// This applies to the entire application, not just the focused + /// terminal. You must toggle it off to disable it, or quit Ghostty. + /// + /// This only works on macOS, since this is a system API on macOS. + toggle_secure_input: void, + /// Quit ghostty. quit: void, From c3d6356a8773f843244a3dfd7ea576fb5c15a1bf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 16:24:19 -0700 Subject: [PATCH 04/12] macos: show secure input overlay when it is enabled --- macos/Ghostty.xcodeproj/project.pbxproj | 14 +++- .../Features/Secure Input/SecureInput.swift | 4 +- .../Secure Input/SecureInputOverlay.swift | 67 +++++++++++++++++++ macos/Sources/Ghostty/SurfaceView.swift | 11 +++ macos/Sources/Helpers/View+Extension.swift | 18 +++++ 5 files changed, 109 insertions(+), 5 deletions(-) create mode 100644 macos/Sources/Features/Secure Input/SecureInputOverlay.swift create mode 100644 macos/Sources/Helpers/View+Extension.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 88c90ba33..713058e19 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -58,6 +58,8 @@ A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; }; A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; }; A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; + A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */; }; + A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CC36142C9CDA03004D6760 /* View+Extension.swift */; }; A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; }; A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */; }; A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */; }; @@ -124,6 +126,8 @@ A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; }; A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = ""; }; + A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInputOverlay.swift; sourceTree = ""; }; + A5CC36142C9CDA03004D6760 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = ""; }; A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsController.swift; sourceTree = ""; }; A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsView.swift; sourceTree = ""; }; @@ -214,6 +218,7 @@ C1F26EA62B738B9900404083 /* NSView+Extension.swift */, AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, A5985CD62C320C4500C57AD3 /* String+Extension.swift */, + A5CC36142C9CDA03004D6760 /* View+Extension.swift */, C1F26EE72B76CBFC00404083 /* VibrantLayer.h */, C1F26EE82B76CBFC00404083 /* VibrantLayer.m */, A5CEAFDA29B8005900646FDA /* SplitView */, @@ -302,6 +307,7 @@ isa = PBXGroup; children = ( A57D79262C9C8798001D522E /* SecureInput.swift */, + A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */, ); path = "Secure Input"; sourceTree = ""; @@ -507,6 +513,7 @@ A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */, C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */, A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */, + A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */, A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */, A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */, A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, @@ -532,6 +539,7 @@ A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */, A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */, A55685E029A03A9F004303CE /* AppError.swift in Sources */, + A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */, A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */, @@ -646,7 +654,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; @@ -797,7 +805,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; @@ -836,7 +844,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 12.4; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; diff --git a/macos/Sources/Features/Secure Input/SecureInput.swift b/macos/Sources/Features/Secure Input/SecureInput.swift index 231306f5d..f999ce5ca 100644 --- a/macos/Sources/Features/Secure Input/SecureInput.swift +++ b/macos/Sources/Features/Secure Input/SecureInput.swift @@ -12,7 +12,7 @@ import OSLog // it. You have to yield secure input on application deactivation (because // it'll affect other apps) and reacquire on reactivation, and every enable // needs to be balanced with a disable. -class SecureInput { +class SecureInput : ObservableObject { static let shared = SecureInput() private static let logger = Logger( @@ -31,7 +31,7 @@ class SecureInput { private var scoped: [ObjectIdentifier: Bool] = [:] // This is set to true when we've successfully called EnableSecureInput. - private var enabled: Bool = false + @Published private(set) var enabled: Bool = false // This is true if we want to enable secure input. We want to enable // secure input if its enabled globally or any of the scoped objects are diff --git a/macos/Sources/Features/Secure Input/SecureInputOverlay.swift b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift new file mode 100644 index 000000000..50b94fdf8 --- /dev/null +++ b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift @@ -0,0 +1,67 @@ +import SwiftUI + +struct SecureInputOverlay: View { + // Animations + @State private var shadowAngle: Angle = .degrees(0) + @State private var shadowWidth: CGFloat = 6 + + // Popover explainer text + @State private var isPopover = false + + var body: some View { + VStack { + HStack { + Spacer() + + Image(systemName: "lock.shield.fill") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 25, height: 25) + .foregroundColor(.primary) + .padding(5) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(.background) + .innerShadow( + using: RoundedRectangle(cornerRadius: 12), + stroke: AngularGradient( + gradient: Gradient(colors: [.cyan, .blue, .yellow, .blue, .cyan]), + center: .center, + angle: shadowAngle + ), + width: shadowWidth + ) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.gray, lineWidth: 1) + ) + .onTapGesture { + isPopover = true + } + .padding(.top, 10) + .padding(.trailing, 10) + .popover(isPresented: $isPopover, arrowEdge: .bottom) { + Text(""" + Secure Input is active. Secure Input is a macOS security feature that + prevents applications from reading keyboard events. Ghostty turns + this on manually if `Ghostty > Secure Keyboard Entry` is enabled or + automatically when at a password prompt. + """) + .padding(.all) + } + } + + Spacer() + } + .onAppear { + withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) { + shadowAngle = .degrees(360) + } + + withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: true)) { + shadowWidth = 12 + } + } + } +} diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index cd3967052..5cfd3b732 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -52,6 +52,9 @@ extension Ghostty { // True if we're hovering over the left URL view, so we can show it on the right. @State private var isHoveringURLLeft: Bool = false + // Observe SecureInput to detect when its enabled + @ObservedObject private var secureInput = SecureInput.shared + @EnvironmentObject private var ghostty: Ghostty.App var body: some View { @@ -197,6 +200,14 @@ extension Ghostty { } } + // If we have secure input enabled and we're the focused surface and window + // then we want to show the secure input overlay. + if (secureInput.enabled && + surfaceFocus && + windowFocus) { + SecureInputOverlay() + } + // If our surface is not healthy, then we render an error view over it. if (!surfaceView.healthy) { Rectangle().fill(ghostty.config.backgroundColor) diff --git a/macos/Sources/Helpers/View+Extension.swift b/macos/Sources/Helpers/View+Extension.swift new file mode 100644 index 000000000..db17b441f --- /dev/null +++ b/macos/Sources/Helpers/View+Extension.swift @@ -0,0 +1,18 @@ +import SwiftUI + +extension View { + func innerShadow( + using shape: S = Rectangle(), + stroke: ST = Color.black, + width: CGFloat = 6, + blur: CGFloat = 6 + ) -> some View { + return self + .overlay( + shape + .stroke(stroke, lineWidth: width) + .blur(radius: blur) + .mask(shape) + ) + } +} From 1ed1c73c1a1a9d64a52a5e62a379ba0100a953b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 16:37:25 -0700 Subject: [PATCH 05/12] macos: enable secure input on password input --- include/ghostty.h | 2 ++ macos/Sources/Ghostty/Ghostty.App.swift | 7 ++++++ .../Sources/Ghostty/SurfaceView_AppKit.swift | 24 +++++++++++++++++++ src/Surface.zig | 5 ++++ src/apprt/embedded.zig | 14 +++++++++++ 5 files changed, 52 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index b4f6a89a2..c82411820 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -464,6 +464,7 @@ typedef void (*ghostty_runtime_show_desktop_notification_cb)(void*, typedef void ( *ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e); typedef void (*ghostty_runtime_mouse_over_link_cb)(void*, const char*, size_t); +typedef void (*ghostty_runtime_set_password_input_cb)(void*, bool); typedef void (*ghostty_runtime_toggle_secure_input_cb)(); typedef struct { @@ -495,6 +496,7 @@ typedef struct { ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb; ghostty_runtime_update_renderer_health update_renderer_health_cb; ghostty_runtime_mouse_over_link_cb mouse_over_link_cb; + ghostty_runtime_set_password_input_cb set_password_input_cb; ghostty_runtime_toggle_secure_input_cb toggle_secure_input_cb; } ghostty_runtime_config_s; diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 190bb3224..366f83711 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -95,6 +95,7 @@ extension Ghostty { App.showUserNotification(userdata, title: title, body: body) }, update_renderer_health_cb: { userdata, health in App.updateRendererHealth(userdata, health: health) }, mouse_over_link_cb: { userdata, ptr, len in App.mouseOverLink(userdata, uri: ptr, len: len) }, + set_password_input_cb: { userdata, value in App.setPasswordInput(userdata, value: value) }, toggle_secure_input_cb: { App.toggleSecureInput() } ) @@ -300,6 +301,7 @@ extension Ghostty { static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer?, body: UnsafePointer?) {} static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {} static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer?, len: Int) {} + static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) {} static func toggleSecureInput() {} #endif @@ -546,6 +548,11 @@ extension Ghostty { surfaceView.hoverUrl = String(data: buffer, encoding: .utf8) } + static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) { + let surfaceView = self.surfaceUserdata(from: userdata) + surfaceView.passwordInput = value + } + static func toggleSecureInput() { guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return } appDelegate.toggleSecureInput(self) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 8a617fdd6..c3ae03138 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -42,6 +42,21 @@ extension Ghostty { // then the view is moved to a new window. var initialSize: NSSize? = nil + // Set whether the surface is currently on a password input or not. This is + // detected with the set_password_input_cb on the Ghostty state. + var passwordInput: Bool = false { + didSet { + // We need to update our state within the SecureInput manager. + let input = SecureInput.shared + let id = ObjectIdentifier(self) + if (passwordInput) { + input.setScoped(id, focused: focused) + } else { + input.removeScoped(id) + } + } + } + // Returns true if quit confirmation is required for this surface to // exit safely. var needsConfirmQuit: Bool { @@ -59,6 +74,7 @@ extension Ghostty { if (v.count == 0) { return nil } return v } + // Returns the inspector instance for this surface, or nil if the // surface has been closed. var inspector: ghostty_inspector_t? { @@ -185,6 +201,9 @@ extension Ghostty { mouseExited(with: NSEvent()) } + // Remove ourselves from secure input if we have to + SecureInput.shared.removeScoped(ObjectIdentifier(self)) + guard let surface = self.surface else { return } ghostty_surface_free(surface) } @@ -209,6 +228,11 @@ extension Ghostty { self.focused = focused ghostty_surface_set_focus(surface, focused) + // Update our secure input state if we are a password input + if (passwordInput) { + SecureInput.shared.setScoped(ObjectIdentifier(self), focused: focused) + } + // On macOS 13+ we can store our continuous clock... if #available(macOS 13, iOS 16, *) { if (focused) { diff --git a/src/Surface.zig b/src/Surface.zig index b0c3d67f4..3700df7d9 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -837,6 +837,11 @@ fn passwordInput(self: *Surface, v: bool) !void { self.io.terminal.flags.password_input = v; } + // Notify our apprt so it can do whatever it wants. + if (@hasDecl(apprt.Surface, "setPasswordInput")) { + self.rt_surface.setPasswordInput(v); + } + try self.queueRender(); } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 846805f77..f57b16272 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -134,6 +134,11 @@ pub const App = struct { /// over a link. mouse_over_link: ?*const fn (SurfaceUD, ?[*]const u8, usize) void = null, + /// Notifies that a password input has been started for the given + /// surface. The apprt can use this to modify UI, enable features + /// such as macOS secure input, etc. + set_password_input: ?*const fn (SurfaceUD, bool) callconv(.C) void = null, + /// Toggle secure input for the application. toggle_secure_input: ?*const fn () callconv(.C) void = null, }; @@ -1017,6 +1022,15 @@ pub const Surface = struct { func(); } + pub fn setPasswordInput(self: *Surface, v: bool) void { + const func = self.app.opts.set_password_input orelse { + log.info("runtime embedder does not set_password_input", .{}); + return; + }; + + func(self.userdata, v); + } + pub fn newTab(self: *const Surface) !void { const func = self.app.opts.new_tab orelse { log.info("runtime embedder does not support new_tab", .{}); From 6b85a152d87c2603b6580392556f4c4947c0f8d5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 16:43:38 -0700 Subject: [PATCH 06/12] macos: fix deployment target back to 12.0 --- macos/Ghostty.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 713058e19..0c0b95418 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -654,7 +654,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.4; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; @@ -805,7 +805,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.4; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; @@ -844,7 +844,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.4; + MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 0.1; "OTHER_LDFLAGS[arch=*]" = "-lstdc++"; PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty; From 9f03aae764eb4efc3b5adb988f58f5782ccf77b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 16:56:37 -0700 Subject: [PATCH 07/12] ios: disable secure input --- macos/Sources/Ghostty/SurfaceView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 5cfd3b732..a00ff7ce9 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -52,8 +52,10 @@ extension Ghostty { // True if we're hovering over the left URL view, so we can show it on the right. @State private var isHoveringURLLeft: Bool = false + #if canImport(AppKit) // Observe SecureInput to detect when its enabled @ObservedObject private var secureInput = SecureInput.shared + #endif @EnvironmentObject private var ghostty: Ghostty.App @@ -200,6 +202,7 @@ extension Ghostty { } } + #if canImport(AppKit) // If we have secure input enabled and we're the focused surface and window // then we want to show the secure input overlay. if (secureInput.enabled && @@ -207,6 +210,7 @@ extension Ghostty { windowFocus) { SecureInputOverlay() } + #endif // If our surface is not healthy, then we render an error view over it. if (!surfaceView.healthy) { From ced8395c77b4e6e241b5bb89307a9eca7543cc93 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 17:06:43 -0700 Subject: [PATCH 08/12] macos: copy changes --- macos/Sources/Features/Secure Input/SecureInputOverlay.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Features/Secure Input/SecureInputOverlay.swift b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift index 50b94fdf8..9f14dfad4 100644 --- a/macos/Sources/Features/Secure Input/SecureInputOverlay.swift +++ b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift @@ -44,8 +44,8 @@ struct SecureInputOverlay: View { .popover(isPresented: $isPopover, arrowEdge: .bottom) { Text(""" Secure Input is active. Secure Input is a macOS security feature that - prevents applications from reading keyboard events. Ghostty turns - this on manually if `Ghostty > Secure Keyboard Entry` is enabled or + prevents applications from reading keyboard events. This is enabled + when `Ghostty > Secure Keyboard Entry` is active, as well as automatically when at a password prompt. """) .padding(.all) From a513a02328dc2ab853724e76793532121045e81a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 17:20:42 -0700 Subject: [PATCH 09/12] config: config to disable auto secure input and secure input overlay --- macos/Sources/Ghostty/Ghostty.App.swift | 2 ++ macos/Sources/Ghostty/Ghostty.Config.swift | 16 +++++++++++++ macos/Sources/Ghostty/SurfaceView.swift | 3 ++- src/build/fish_completions.zig | 2 +- src/config/Config.zig | 28 ++++++++++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 366f83711..7b8c5688f 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -550,6 +550,8 @@ extension Ghostty { static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) { let surfaceView = self.surfaceUserdata(from: userdata) + guard let appState = self.appState(fromView: surfaceView) else { return } + guard appState.config.autoSecureInput else { return } surfaceView.passwordInput = value } diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 8ea9371fe..6288e54f7 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -371,6 +371,22 @@ extension Ghostty { let str = String(cString: ptr) return AutoUpdate(rawValue: str) ?? defaultValue } + + var autoSecureInput: Bool { + guard let config = self.config else { return true } + var v = false; + let key = "macos-auto-secure-input" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } + + var secureInputOverlay: Bool { + guard let config = self.config else { return true } + var v = false; + let key = "macos-secure-input-overlay" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } } } diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index a00ff7ce9..75d821cee 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -205,7 +205,8 @@ extension Ghostty { #if canImport(AppKit) // If we have secure input enabled and we're the focused surface and window // then we want to show the secure input overlay. - if (secureInput.enabled && + if (ghostty.config.secureInputOverlay && + secureInput.enabled && surfaceFocus && windowFocus) { SecureInputOverlay() diff --git a/src/build/fish_completions.zig b/src/build/fish_completions.zig index b6fe9b0dc..0ff0a2163 100644 --- a/src/build/fish_completions.zig +++ b/src/build/fish_completions.zig @@ -12,7 +12,7 @@ pub const fish_completions = comptimeGenerateFishCompletions(); fn comptimeGenerateFishCompletions() []const u8 { comptime { - @setEvalBranchQuota(16000); + @setEvalBranchQuota(17000); var counter = std.io.countingWriter(std.io.null_writer); try writeFishCompletions(&counter.writer()); diff --git a/src/config/Config.zig b/src/config/Config.zig index 9739b36b8..d2ab120a8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1348,6 +1348,34 @@ keybind: Keybinds = .{}, /// find false more visually appealing. @"macos-window-shadow": bool = true, +/// If true, Ghostty on macOS will automatically enable the "Secure Input" +/// feature when it detects that a password prompt is being displayed. +/// +/// "Secure Input" is a macOS security feature that prevents applications from +/// reading keyboard events. This can always be enabled manually using the +/// `Ghostty > Secure Keyboard Entry` menu item. +/// +/// Note that automatic password prompt detection is based on heuristics +/// and may not always work as expected. Specifically, it does not work +/// over SSH connections, but there may be other cases where it also +/// doesn't work. +/// +/// A reason to disable this feature is if you find that it is interfering +/// with legitimate accessibility software (or software that uses the +/// accessibility APIs), since secure input prevents any application from +/// reading keyboard events. +@"macos-auto-secure-input": bool = true, + +/// If true, Ghostty will show a graphical overlay when secure input is +/// enabled. This overlay is generally recommended to know when secure input +/// is enabled. +/// +/// Normally, secure input is only active when a password prompt is displayed +/// or it is manually (and typically temporarily) enabled. However, if you +/// always have secure input enabled, this overlay can be distracting and +/// you may want to disable it. +@"macos-secure-input-overlay": bool = true, + /// Put every surface (tab, split, window) into a dedicated Linux cgroup. /// /// This makes it so that resource management can be done on a per-surface From fa9df4f6f0521b3acd64b9be36cdf55dd008e17e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 19:54:42 -0700 Subject: [PATCH 10/12] macos: persist secure input state across restarts --- macos/Sources/App/macOS/AppDelegate.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index d686474f1..41815631d 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -106,6 +106,11 @@ class AppDelegate: NSObject, "ApplePressAndHoldEnabled": false, ]) + // Check if secure input was enabled when we last quit. + if (UserDefaults.standard.bool(forKey: "SecureInput") != SecureInput.shared.enabled) { + toggleSecureInput(self) + } + // Hook up updater menu menuCheckForUpdates?.target = updaterController menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:)) @@ -492,5 +497,6 @@ class AppDelegate: NSObject, let input = SecureInput.shared input.global.toggle() self.menuSecureInput?.state = if (input.global) { .on } else { .off } + UserDefaults.standard.set(input.global, forKey: "SecureInput") } } From 08a2a71ab83a01a67d8a0c91bea2f462cdeea483 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 19:57:01 -0700 Subject: [PATCH 11/12] macos: copy --- .../Sources/Features/Secure Input/SecureInputOverlay.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Features/Secure Input/SecureInputOverlay.swift b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift index 9f14dfad4..717eeb90c 100644 --- a/macos/Sources/Features/Secure Input/SecureInputOverlay.swift +++ b/macos/Sources/Features/Secure Input/SecureInputOverlay.swift @@ -44,9 +44,9 @@ struct SecureInputOverlay: View { .popover(isPresented: $isPopover, arrowEdge: .bottom) { Text(""" Secure Input is active. Secure Input is a macOS security feature that - prevents applications from reading keyboard events. This is enabled - when `Ghostty > Secure Keyboard Entry` is active, as well as - automatically when at a password prompt. + prevents applications from reading keyboard events. This is enabled + automatically whenever Ghostty detects a password prompt in the terminal, + or at all times if `Ghostty > Secure Keyboard Entry` is active. """) .padding(.all) } From df5cd719d6cfa4de7ba350efa53f07bfbc62aa42 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 19 Sep 2024 20:00:41 -0700 Subject: [PATCH 12/12] macos: rename overlay config to indication --- macos/Sources/Ghostty/Ghostty.Config.swift | 4 ++-- macos/Sources/Ghostty/SurfaceView.swift | 2 +- src/config/Config.zig | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 6288e54f7..7ecd45cc4 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -380,10 +380,10 @@ extension Ghostty { return v } - var secureInputOverlay: Bool { + var secureInputIndication: Bool { guard let config = self.config else { return true } var v = false; - let key = "macos-secure-input-overlay" + let key = "macos-secure-input-indication" _ = ghostty_config_get(config, &v, key, UInt(key.count)) return v } diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 75d821cee..6d20e1e82 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -205,7 +205,7 @@ extension Ghostty { #if canImport(AppKit) // If we have secure input enabled and we're the focused surface and window // then we want to show the secure input overlay. - if (ghostty.config.secureInputOverlay && + if (ghostty.config.secureInputIndication && secureInput.enabled && surfaceFocus && windowFocus) { diff --git a/src/config/Config.zig b/src/config/Config.zig index d2ab120a8..fd7ce996f 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1366,15 +1366,15 @@ keybind: Keybinds = .{}, /// reading keyboard events. @"macos-auto-secure-input": bool = true, -/// If true, Ghostty will show a graphical overlay when secure input is -/// enabled. This overlay is generally recommended to know when secure input +/// If true, Ghostty will show a graphical indication when secure input is +/// enabled. This indication is generally recommended to know when secure input /// is enabled. /// /// Normally, secure input is only active when a password prompt is displayed /// or it is manually (and typically temporarily) enabled. However, if you -/// always have secure input enabled, this overlay can be distracting and +/// always have secure input enabled, the indication can be distracting and /// you may want to disable it. -@"macos-secure-input-overlay": bool = true, +@"macos-secure-input-indication": bool = true, /// Put every surface (tab, split, window) into a dedicated Linux cgroup. ///