diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 0b69f103d..0743adf76 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -561,6 +561,133 @@ translateKeyToModifierFlag(uint32_t key) static const NSRange kEmptyRange = { NSNotFound, 0 }; +// SecureKeyboardEntryController {{{ +@interface SecureKeyboardEntryController : NSObject + +@property (nonatomic, readonly) BOOL isDesired; +@property (nonatomic, readonly, getter=isEnabled) BOOL enabled; + ++ (instancetype)sharedInstance; + +- (void)toggle; +- (void)update; + +@end + +@implementation SecureKeyboardEntryController { + int _count; + BOOL _desired; +} + ++ (instancetype)sharedInstance { + static id instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[self alloc] init]; + }); + return instance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _desired = false; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidResignActive:) + name:NSApplicationDidResignActiveNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidBecomeActive:) + name:NSApplicationDidBecomeActiveNotification + object:nil]; + if ([NSApp isActive]) { + [self update]; + } + } + return self; +} + +#pragma mark - API + +- (void)toggle { + // Set _desired to the opposite of the current state. + _desired = !_desired; + debug_key(@"toggle called. Setting desired to %@", @(_desired)); + + // Try to set the system's state of secure input to the desired state. + [self update]; +} + +- (BOOL)isEnabled { + return !!IsSecureEventInputEnabled(); +} + +- (BOOL)isDesired { + return _desired; +} + +#pragma mark - Notifications + +- (void)applicationDidResignActive:(NSNotification *)notification { + if (_count > 0) { + debug_key(@"Application resigning active."); + [self update]; + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (self.isDesired) { + debug_key(@"Application became active."); + [self update]; + } +} + +#pragma mark - Private + +- (BOOL)allowed { + return [NSApp isActive]; +} + +- (void)update { + debug_key(@"Update secure keyboard entry. desired=%@ active=%@", + @(self.isDesired), @([NSApp isActive])); + const BOOL secure = self.isDesired && [self allowed]; + + if (secure && _count > 0) { + debug_key(@"Want to turn on secure input but it's already on"); + return; + } + + if (!secure && _count == 0) { + debug_key(@"Want to turn off secure input but it's already off"); + return; + } + + debug_key(@"Before: IsSecureEventInputEnabled returns %d", (int)self.isEnabled); + if (secure) { + OSErr err = EnableSecureEventInput(); + debug_key(@"EnableSecureEventInput err=%d", (int)err); + if (err) { + debug_key(@"EnableSecureEventInput failed with error %d", (int)err); + } else { + _count += 1; + } + } else { + OSErr err = DisableSecureEventInput(); + debug_key(@"DisableSecureEventInput err=%d", (int)err); + if (err) { + debug_key(@"DisableSecureEventInput failed with error %d", (int)err); + } else { + _count -= 1; + } + } + debug_key(@"After: IsSecureEventInputEnabled returns %d", (int)self.isEnabled); +} + +@end +// }}} + // Delegate for window related notifications {{{ @interface GLFWWindowDelegate : NSObject @@ -1401,6 +1528,19 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { - (BOOL)validateMenuItem:(NSMenuItem *)item { if (item.action == @selector(performMiniaturize:)) return YES; + if (item.action == @selector(toggleSecureInput:)) { + SecureKeyboardEntryController *controller = [SecureKeyboardEntryController sharedInstance]; + if (controller.isEnabled) { + if (controller.isDesired) { + item.state = NSControlStateValueOn; + } else { + item.state = NSControlStateValueMixed; + } + } else { + item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff; + } + return YES; + } return [super validateMenuItem:item]; } @@ -1410,6 +1550,10 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) { else [super performMiniaturize:sender]; } +- (void)toggleSecureInput:(id)sender { + [[SecureKeyboardEntryController sharedInstance] toggle]; +} + - (BOOL)canBecomeKeyWindow { // Required for NSWindowStyleMaskBorderless windows diff --git a/glfw/glfw.py b/glfw/glfw.py index 837a58eda..e841ed8d8 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -89,7 +89,7 @@ def init_env(env: Env, pkg_config: Callable, pkg_version: Callable, at_least_ver elif module == 'cocoa': ans.cppflags.append('-DGL_SILENCE_DEPRECATION') - for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split(): + for f_ in 'Cocoa Carbon IOKit CoreFoundation CoreVideo'.split(): ans.ldpaths.extend(('-framework', f_)) elif module == 'wayland': diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index 4c2bde9cf..1303c7c33 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -397,6 +397,12 @@ cocoa_create_global_menu(void) { [appMenu addItem:[NSMenuItem separatorItem]]; + [[appMenu addItemWithTitle:@"Secure Keyboard Entry" + action:@selector(toggleSecureInput:) + keyEquivalent:@"s"] + setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand]; + [appMenu addItem:[NSMenuItem separatorItem]]; + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name] action:@selector(terminate:) keyEquivalent:@"q"];