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,