mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #2270 from ghostty-org/secureinput
macOS Secure Input
This commit is contained in:
@ -464,6 +464,8 @@ typedef void (*ghostty_runtime_show_desktop_notification_cb)(void*,
|
|||||||
typedef void (
|
typedef void (
|
||||||
*ghostty_runtime_update_renderer_health)(void*, ghostty_renderer_health_e);
|
*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_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 {
|
typedef struct {
|
||||||
void* userdata;
|
void* userdata;
|
||||||
@ -494,6 +496,8 @@ typedef struct {
|
|||||||
ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb;
|
ghostty_runtime_show_desktop_notification_cb show_desktop_notification_cb;
|
||||||
ghostty_runtime_update_renderer_health update_renderer_health_cb;
|
ghostty_runtime_update_renderer_health update_renderer_health_cb;
|
||||||
ghostty_runtime_mouse_over_link_cb mouse_over_link_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;
|
} ghostty_runtime_config_s;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
|
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
|
||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */; };
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */; };
|
||||||
A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
|
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 */; };
|
A586167C2B7703CC009BDB1D /* fish in Resources */ = {isa = PBXBuildFile; fileRef = A586167B2B7703CC009BDB1D /* fish */; };
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
||||||
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59630962AEE163600D64628 /* HostingWindow.swift */; };
|
A59630972AEE163600D64628 /* HostingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59630962AEE163600D64628 /* HostingWindow.swift */; };
|
||||||
@ -57,6 +58,8 @@
|
|||||||
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
|
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
|
||||||
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
||||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
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 */; };
|
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; };
|
||||||
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */; };
|
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */; };
|
||||||
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */; };
|
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */; };
|
||||||
@ -105,6 +108,7 @@
|
|||||||
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Shell.swift; sourceTree = "<group>"; };
|
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Shell.swift; sourceTree = "<group>"; };
|
||||||
A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
|
A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
|
||||||
A571AB1C2A206FC600248498 /* Ghostty-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Ghostty-Info.plist"; sourceTree = "<group>"; };
|
A571AB1C2A206FC600248498 /* Ghostty-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Ghostty-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
A57D79262C9C8798001D522E /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = "<group>"; };
|
||||||
A586167B2B7703CC009BDB1D /* fish */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fish; path = "../zig-out/share/fish"; sourceTree = "<group>"; };
|
A586167B2B7703CC009BDB1D /* fish */ = {isa = PBXFileReference; lastKnownFileType = folder; name = fish; path = "../zig-out/share/fish"; sourceTree = "<group>"; };
|
||||||
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
A59630962AEE163600D64628 /* HostingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingWindow.swift; sourceTree = "<group>"; };
|
A59630962AEE163600D64628 /* HostingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingWindow.swift; sourceTree = "<group>"; };
|
||||||
@ -122,6 +126,8 @@
|
|||||||
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||||
|
A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInputOverlay.swift; sourceTree = "<group>"; };
|
||||||
|
A5CC36142C9CDA03004D6760 /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = "<group>"; };
|
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = "<group>"; };
|
||||||
A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsController.swift; sourceTree = "<group>"; };
|
A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsController.swift; sourceTree = "<group>"; };
|
||||||
A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsView.swift; sourceTree = "<group>"; };
|
A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsView.swift; sourceTree = "<group>"; };
|
||||||
@ -191,6 +197,7 @@
|
|||||||
A56D58872ACDE6BE00508D2C /* Services */,
|
A56D58872ACDE6BE00508D2C /* Services */,
|
||||||
A59630982AEE1C4400D64628 /* Terminal */,
|
A59630982AEE1C4400D64628 /* Terminal */,
|
||||||
A5E112912AF73E4D00C6E0C2 /* ClipboardConfirmation */,
|
A5E112912AF73E4D00C6E0C2 /* ClipboardConfirmation */,
|
||||||
|
A57D79252C9C8782001D522E /* Secure Input */,
|
||||||
A534263E2A7DCC5800EBB7A2 /* Settings */,
|
A534263E2A7DCC5800EBB7A2 /* Settings */,
|
||||||
A51BFC1C2B2FB5AB00E92F16 /* About */,
|
A51BFC1C2B2FB5AB00E92F16 /* About */,
|
||||||
A51BFC292B30F69F00E92F16 /* Update */,
|
A51BFC292B30F69F00E92F16 /* Update */,
|
||||||
@ -211,6 +218,7 @@
|
|||||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
||||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
||||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||||
|
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
||||||
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
||||||
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
||||||
A5CEAFDA29B8005900646FDA /* SplitView */,
|
A5CEAFDA29B8005900646FDA /* SplitView */,
|
||||||
@ -295,6 +303,15 @@
|
|||||||
path = Services;
|
path = Services;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A57D79252C9C8782001D522E /* Secure Input */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A57D79262C9C8798001D522E /* SecureInput.swift */,
|
||||||
|
A5CC36122C9CD729004D6760 /* SecureInputOverlay.swift */,
|
||||||
|
);
|
||||||
|
path = "Secure Input";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A59630982AEE1C4400D64628 /* Terminal */ = {
|
A59630982AEE1C4400D64628 /* Terminal */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -496,6 +513,7 @@
|
|||||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
|
||||||
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */,
|
||||||
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
A596309C2AEE1C9E00D64628 /* TerminalController.swift in Sources */,
|
||||||
|
A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */,
|
||||||
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
|
||||||
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||||
@ -516,10 +534,12 @@
|
|||||||
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
||||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||||
A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */,
|
A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */,
|
||||||
|
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||||
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
|
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
|
||||||
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
|
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
|
||||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
||||||
|
A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */,
|
||||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
||||||
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
||||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
||||||
|
@ -22,6 +22,7 @@ class AppDelegate: NSObject,
|
|||||||
@IBOutlet private var menuCheckForUpdates: NSMenuItem?
|
@IBOutlet private var menuCheckForUpdates: NSMenuItem?
|
||||||
@IBOutlet private var menuOpenConfig: NSMenuItem?
|
@IBOutlet private var menuOpenConfig: NSMenuItem?
|
||||||
@IBOutlet private var menuReloadConfig: NSMenuItem?
|
@IBOutlet private var menuReloadConfig: NSMenuItem?
|
||||||
|
@IBOutlet private var menuSecureInput: NSMenuItem?
|
||||||
@IBOutlet private var menuQuit: NSMenuItem?
|
@IBOutlet private var menuQuit: NSMenuItem?
|
||||||
|
|
||||||
@IBOutlet private var menuNewWindow: NSMenuItem?
|
@IBOutlet private var menuNewWindow: NSMenuItem?
|
||||||
@ -105,6 +106,11 @@ class AppDelegate: NSObject,
|
|||||||
"ApplePressAndHoldEnabled": false,
|
"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
|
// Hook up updater menu
|
||||||
menuCheckForUpdates?.target = updaterController
|
menuCheckForUpdates?.target = updaterController
|
||||||
menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:))
|
menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:))
|
||||||
@ -294,6 +300,8 @@ class AppDelegate: NSObject,
|
|||||||
syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize)
|
syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize)
|
||||||
syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
||||||
|
|
||||||
|
syncMenuShortcut(action: "toggle_secure_input", menuItem: self.menuSecureInput)
|
||||||
|
|
||||||
// This menu item is NOT synced with the configuration because it disables macOS
|
// 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
|
// global fullscreen keyboard shortcut. The shortcut in the Ghostty config will continue
|
||||||
// to work but it won't be reflected in the menu item.
|
// to work but it won't be reflected in the menu item.
|
||||||
@ -484,4 +492,11 @@ class AppDelegate: NSObject,
|
|||||||
guard let url = URL(string: "https://github.com/ghostty-org/ghostty") else { return }
|
guard let url = URL(string: "https://github.com/ghostty-org/ghostty") else { return }
|
||||||
NSWorkspace.shared.open(url)
|
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 }
|
||||||
|
UserDefaults.standard.set(input.global, forKey: "SecureInput")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23094" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
@ -35,14 +35,15 @@
|
|||||||
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
|
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
|
||||||
<outlet property="menuReloadConfig" destination="KKH-XX-5py" id="Wvp-7J-wqX"/>
|
<outlet property="menuReloadConfig" destination="KKH-XX-5py" id="Wvp-7J-wqX"/>
|
||||||
<outlet property="menuResetFontSize" destination="Jah-MY-aLX" id="ger-qM-wrm"/>
|
<outlet property="menuResetFontSize" destination="Jah-MY-aLX" id="ger-qM-wrm"/>
|
||||||
|
<outlet property="menuSecureInput" destination="oC6-w4-qI7" id="PCc-pe-Mda"/>
|
||||||
<outlet property="menuSelectAll" destination="q2h-lq-e4r" id="s98-r1-Jcv"/>
|
<outlet property="menuSelectAll" destination="q2h-lq-e4r" id="s98-r1-Jcv"/>
|
||||||
<outlet property="menuSelectSplitAbove" destination="0yU-hC-8xF" id="aPc-lS-own"/>
|
<outlet property="menuSelectSplitAbove" destination="0yU-hC-8xF" id="aPc-lS-own"/>
|
||||||
<outlet property="menuSelectSplitBelow" destination="QDz-d9-CBr" id="FsH-Dq-jij"/>
|
<outlet property="menuSelectSplitBelow" destination="QDz-d9-CBr" id="FsH-Dq-jij"/>
|
||||||
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
|
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
|
||||||
<outlet property="menuSelectSplitRight" destination="upj-mc-L7X" id="nLY-o1-lky"/>
|
<outlet property="menuSelectSplitRight" destination="upj-mc-L7X" id="nLY-o1-lky"/>
|
||||||
<outlet property="menuServices" destination="aQe-vS-j8Q" id="uWQ-Wo-T1L"/>
|
<outlet property="menuServices" destination="aQe-vS-j8Q" id="uWQ-Wo-T1L"/>
|
||||||
<outlet property="menuSplitRight" destination="VUR-Ld-nLx" id="RxO-Zw-ovb"/>
|
|
||||||
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="fgZ-Wb-8OR"/>
|
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="fgZ-Wb-8OR"/>
|
||||||
|
<outlet property="menuSplitRight" destination="VUR-Ld-nLx" id="RxO-Zw-ovb"/>
|
||||||
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
|
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
|
||||||
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
|
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
|
||||||
<outlet property="menuZoomSplit" destination="oPd-mn-IEH" id="wTu-jK-egI"/>
|
<outlet property="menuZoomSplit" destination="oPd-mn-IEH" id="wTu-jK-egI"/>
|
||||||
@ -76,6 +77,12 @@
|
|||||||
<action selector="reloadConfig:" target="bbz-4X-AYv" id="h5x-tu-Izk"/>
|
<action selector="reloadConfig:" target="bbz-4X-AYv" id="h5x-tu-Izk"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem title="Secure Keyboard Entry" id="oC6-w4-qI7">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleSecureInput:" target="bbz-4X-AYv" id="vWx-z8-5Sy"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
<menuItem title="Services" id="rJe-5J-bwL">
|
<menuItem title="Services" id="rJe-5J-bwL">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
135
macos/Sources/Features/Secure Input/SecureInput.swift
Normal file
135
macos/Sources/Features/Secure Input/SecureInput.swift
Normal file
@ -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 : ObservableObject {
|
||||||
|
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.
|
||||||
|
@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
|
||||||
|
// 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)")
|
||||||
|
}
|
||||||
|
}
|
67
macos/Sources/Features/Secure Input/SecureInputOverlay.swift
Normal file
67
macos/Sources/Features/Secure Input/SecureInputOverlay.swift
Normal file
@ -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. 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
|
||||||
|
shadowAngle = .degrees(360)
|
||||||
|
}
|
||||||
|
|
||||||
|
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: true)) {
|
||||||
|
shadowWidth = 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -94,7 +94,9 @@ extension Ghostty {
|
|||||||
show_desktop_notification_cb: { userdata, title, body in
|
show_desktop_notification_cb: { userdata, title, body in
|
||||||
App.showUserNotification(userdata, title: title, body: body) },
|
App.showUserNotification(userdata, title: title, body: body) },
|
||||||
update_renderer_health_cb: { userdata, health in App.updateRendererHealth(userdata, health: health) },
|
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) },
|
||||||
|
set_password_input_cb: { userdata, value in App.setPasswordInput(userdata, value: value) },
|
||||||
|
toggle_secure_input_cb: { App.toggleSecureInput() }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the ghostty app.
|
// Create the ghostty app.
|
||||||
@ -299,6 +301,8 @@ extension Ghostty {
|
|||||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {}
|
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {}
|
||||||
static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {}
|
static func updateRendererHealth(_ userdata: UnsafeMutableRawPointer?, health: ghostty_renderer_health_e) {}
|
||||||
static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer<CChar>?, len: Int) {}
|
static func mouseOverLink(_ userdata: UnsafeMutableRawPointer?, uri: UnsafePointer<CChar>?, len: Int) {}
|
||||||
|
static func setPasswordInput(_ userdata: UnsafeMutableRawPointer?, value: Bool) {}
|
||||||
|
static func toggleSecureInput() {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@ -544,6 +548,18 @@ extension Ghostty {
|
|||||||
surfaceView.hoverUrl = String(data: buffer, encoding: .utf8)
|
surfaceView.hoverUrl = String(data: buffer, encoding: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
static func toggleSecureInput() {
|
||||||
|
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
appDelegate.toggleSecureInput(self)
|
||||||
|
}
|
||||||
|
|
||||||
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {
|
static func showUserNotification(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?, body: UnsafePointer<CChar>?) {
|
||||||
let surfaceView = self.surfaceUserdata(from: userdata)
|
let surfaceView = self.surfaceUserdata(from: userdata)
|
||||||
guard let title = String(cString: title!, encoding: .utf8) else { return }
|
guard let title = String(cString: title!, encoding: .utf8) else { return }
|
||||||
|
@ -371,6 +371,22 @@ extension Ghostty {
|
|||||||
let str = String(cString: ptr)
|
let str = String(cString: ptr)
|
||||||
return AutoUpdate(rawValue: str) ?? defaultValue
|
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 secureInputIndication: Bool {
|
||||||
|
guard let config = self.config else { return true }
|
||||||
|
var v = false;
|
||||||
|
let key = "macos-secure-input-indication"
|
||||||
|
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||||
|
return v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,11 @@ extension Ghostty {
|
|||||||
// True if we're hovering over the left URL view, so we can show it on the right.
|
// True if we're hovering over the left URL view, so we can show it on the right.
|
||||||
@State private var isHoveringURLLeft: Bool = false
|
@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
|
@EnvironmentObject private var ghostty: Ghostty.App
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -197,6 +202,17 @@ 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.secureInputIndication &&
|
||||||
|
secureInput.enabled &&
|
||||||
|
surfaceFocus &&
|
||||||
|
windowFocus) {
|
||||||
|
SecureInputOverlay()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// If our surface is not healthy, then we render an error view over it.
|
// If our surface is not healthy, then we render an error view over it.
|
||||||
if (!surfaceView.healthy) {
|
if (!surfaceView.healthy) {
|
||||||
Rectangle().fill(ghostty.config.backgroundColor)
|
Rectangle().fill(ghostty.config.backgroundColor)
|
||||||
|
@ -42,6 +42,21 @@ extension Ghostty {
|
|||||||
// then the view is moved to a new window.
|
// then the view is moved to a new window.
|
||||||
var initialSize: NSSize? = nil
|
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
|
// Returns true if quit confirmation is required for this surface to
|
||||||
// exit safely.
|
// exit safely.
|
||||||
var needsConfirmQuit: Bool {
|
var needsConfirmQuit: Bool {
|
||||||
@ -59,6 +74,7 @@ extension Ghostty {
|
|||||||
if (v.count == 0) { return nil }
|
if (v.count == 0) { return nil }
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the inspector instance for this surface, or nil if the
|
// Returns the inspector instance for this surface, or nil if the
|
||||||
// surface has been closed.
|
// surface has been closed.
|
||||||
var inspector: ghostty_inspector_t? {
|
var inspector: ghostty_inspector_t? {
|
||||||
@ -185,6 +201,9 @@ extension Ghostty {
|
|||||||
mouseExited(with: NSEvent())
|
mouseExited(with: NSEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove ourselves from secure input if we have to
|
||||||
|
SecureInput.shared.removeScoped(ObjectIdentifier(self))
|
||||||
|
|
||||||
guard let surface = self.surface else { return }
|
guard let surface = self.surface else { return }
|
||||||
ghostty_surface_free(surface)
|
ghostty_surface_free(surface)
|
||||||
}
|
}
|
||||||
@ -209,6 +228,11 @@ extension Ghostty {
|
|||||||
self.focused = focused
|
self.focused = focused
|
||||||
ghostty_surface_set_focus(surface, 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...
|
// On macOS 13+ we can store our continuous clock...
|
||||||
if #available(macOS 13, iOS 16, *) {
|
if #available(macOS 13, iOS 16, *) {
|
||||||
if (focused) {
|
if (focused) {
|
||||||
|
18
macos/Sources/Helpers/View+Extension.swift
Normal file
18
macos/Sources/Helpers/View+Extension.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func innerShadow<S: Shape, ST: ShapeStyle>(
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
5
pkg/macos/carbon.zig
Normal file
5
pkg/macos/carbon.zig
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub const c = @import("carbon/c.zig").c;
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
3
pkg/macos/carbon/c.zig
Normal file
3
pkg/macos/carbon/c.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub const c = @cImport({
|
||||||
|
@cInclude("Carbon/Carbon.h");
|
||||||
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
pub const carbon = @import("carbon.zig");
|
||||||
pub const foundation = @import("foundation.zig");
|
pub const foundation = @import("foundation.zig");
|
||||||
pub const animation = @import("animation.zig");
|
pub const animation = @import("animation.zig");
|
||||||
pub const dispatch = @import("dispatch.zig");
|
pub const dispatch = @import("dispatch.zig");
|
||||||
|
@ -837,6 +837,11 @@ fn passwordInput(self: *Surface, v: bool) !void {
|
|||||||
self.io.terminal.flags.password_input = v;
|
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();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3717,6 +3722,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
} else log.warn("runtime doesn't implement toggleWindowDecorations", .{});
|
} 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 => {
|
.select_all => {
|
||||||
const sel = self.io.terminal.screen.selectAll();
|
const sel = self.io.terminal.screen.selectAll();
|
||||||
if (sel) |s| {
|
if (sel) |s| {
|
||||||
|
@ -133,6 +133,14 @@ pub const App = struct {
|
|||||||
/// parameter. The link target will be null if the mouse is no longer
|
/// parameter. The link target will be null if the mouse is no longer
|
||||||
/// over a link.
|
/// over a link.
|
||||||
mouse_over_link: ?*const fn (SurfaceUD, ?[*]const u8, usize) void = null,
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
core_app: *CoreApp,
|
core_app: *CoreApp,
|
||||||
@ -1005,6 +1013,24 @@ pub const Surface = struct {
|
|||||||
func(self.userdata, nonNativeFullscreen);
|
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 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 {
|
pub fn newTab(self: *const Surface) !void {
|
||||||
const func = self.app.opts.new_tab orelse {
|
const func = self.app.opts.new_tab orelse {
|
||||||
log.info("runtime embedder does not support new_tab", .{});
|
log.info("runtime embedder does not support new_tab", .{});
|
||||||
|
@ -12,7 +12,7 @@ pub const fish_completions = comptimeGenerateFishCompletions();
|
|||||||
|
|
||||||
fn comptimeGenerateFishCompletions() []const u8 {
|
fn comptimeGenerateFishCompletions() []const u8 {
|
||||||
comptime {
|
comptime {
|
||||||
@setEvalBranchQuota(16000);
|
@setEvalBranchQuota(17000);
|
||||||
var counter = std.io.countingWriter(std.io.null_writer);
|
var counter = std.io.countingWriter(std.io.null_writer);
|
||||||
try writeFishCompletions(&counter.writer());
|
try writeFishCompletions(&counter.writer());
|
||||||
|
|
||||||
|
@ -1348,6 +1348,34 @@ keybind: Keybinds = .{},
|
|||||||
/// find false more visually appealing.
|
/// find false more visually appealing.
|
||||||
@"macos-window-shadow": bool = true,
|
@"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 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, the indication can be distracting and
|
||||||
|
/// you may want to disable it.
|
||||||
|
@"macos-secure-input-indication": bool = true,
|
||||||
|
|
||||||
/// Put every surface (tab, split, window) into a dedicated Linux cgroup.
|
/// 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
|
/// This makes it so that resource management can be done on a per-surface
|
||||||
|
@ -297,6 +297,16 @@ pub const Action = union(enum) {
|
|||||||
/// Toggle window decorations on and off. This only works on Linux.
|
/// Toggle window decorations on and off. This only works on Linux.
|
||||||
toggle_window_decorations: void,
|
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 ghostty.
|
||||||
quit: void,
|
quit: void,
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user