Last active
March 5, 2025 10:39
-
-
Save Wowfunhappy/b09900c5420767651338a57692348e31 to your computer and use it in GitHub Desktop.
Firefox Key Equivalent Fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import <AppKit/AppKit.h> | |
#import "ZKSwizzle.h" | |
@interface myNSApplication : NSApplication | |
@end | |
@implementation myNSApplication | |
- (void)sendEvent:(NSEvent *)event { | |
// Firefox does not handle user-defined key equivalents properly | |
// https://bugzilla.mozilla.org/show_bug.cgi?id=1333781 | |
// Check if the event is a key down event with a modifier key. (Otherwise, it can't be a keyboard shortcut.) | |
if ( | |
[event type] == NSKeyDown && | |
[event modifierFlags] & (NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask) | |
//We don't check NSShiftKeyMask because shortcuts aren't allowed to use Shift as the only modifier key. | |
) { | |
// Query user-defined key equivalents | |
NSDictionary *userKeyEquivalents = [[NSUserDefaults standardUserDefaults] objectForKey:@"NSUserKeyEquivalents"]; | |
if (userKeyEquivalents) { | |
// Check if the event matches any user-defined key equivalents | |
for (NSString *menuItemTitle in userKeyEquivalents) { | |
NSString *shortcut = userKeyEquivalents[menuItemTitle]; | |
if ([self event:event matchesShortcut:shortcut]) { | |
[self performKeyEquivalent:event]; | |
[self performActionForItemWithTitle:menuItemTitle inMenu:[NSApp mainMenu]]; | |
return; | |
} | |
} | |
} | |
} | |
// Pass event to Firefox to handle normally. | |
ZKOrig(void, event); | |
} | |
- (BOOL)event:(NSEvent *)event matchesShortcut:(NSString *)shortcut { | |
// Convert the shortcut string to a key equivalent and modifier mask | |
NSString *characterKey = [shortcut substringFromIndex:[shortcut length] - 1]; | |
NSString *modifierString = [shortcut substringToIndex:[shortcut length] - 1]; | |
NSUInteger modifierMask = 0; | |
for (int i = 0; i < [modifierString length]; i++) { | |
switch ([modifierString characterAtIndex:i]) { | |
case '@': | |
modifierMask |= NSCommandKeyMask; | |
break; | |
case '~': | |
modifierMask |= NSAlternateKeyMask; | |
break; | |
case '^': | |
modifierMask |= NSControlKeyMask; | |
break; | |
case '$': | |
modifierMask |= NSShiftKeyMask; | |
break; | |
} | |
} | |
// Compare with the event | |
return ( | |
[[[event charactersIgnoringModifiers] lowercaseString] isEqualToString:characterKey] && | |
([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == modifierMask | |
); | |
} | |
- (void)performActionForItemWithTitle:(NSString *)title inMenu:(NSMenu *)menu { | |
for (NSMenuItem *menuItem in [menu itemArray]) { | |
if ([menuItem hasSubmenu]) { | |
[self performActionForItemWithTitle:title inMenu:[menuItem submenu]]; | |
} else { | |
//Ensure three periods ("...") matches the elipses character ("…"). | |
title = [title stringByReplacingOccurrencesOfString:@"..." withString:@"…"]; | |
NSString *menuItemTitle = [[menuItem title] stringByReplacingOccurrencesOfString:@"..." withString:@"…"]; | |
if ([menuItemTitle isEqualToString:title]) { | |
[[menuItem menu] update]; //highlight menu | |
[[menuItem menu] performActionForItemAtIndex:[[menuItem menu] indexOfItem:menuItem]]; | |
return; | |
} | |
} | |
} | |
} | |
@end | |
@interface myNSWindow : NSWindow | |
@end | |
@implementation myNSWindow | |
- (BOOL)makeFirstResponder:(NSResponder *)responder { | |
//Initialize menus to ensure: | |
//1. Every item can be triggerred by its key equivalents | |
//2. Every item can appear in the search results of the `Help` search box. | |
[self initializeSubmenusOf: [NSApp mainMenu]]; | |
//We should also listen for menu changes. Unfortunately, the below code will crash Firefox. | |
//Todo: figure out how to do this properly! | |
/*[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(initializeSubmenusOf:) name:@"NSMenuDidAddItemNotification" object:[NSApp mainMenu]]; | |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(initializeSubmenusOf:) name:@"NSMenuDidRemoveItemNotification" object:[NSApp mainMenu]];*/ | |
return ZKOrig(BOOL, responder); | |
} | |
- (void)initializeSubmenusOf:(NSMenu *)menu { | |
[[menu delegate] performSelector:@selector(menuWillOpen:) withObject:menu]; | |
[[menu delegate] performSelector:@selector(menuDidClose:) withObject:menu]; | |
for (NSMenuItem *menuItem in [menu itemArray]) { | |
if ([menuItem hasSubmenu]) { | |
[self initializeSubmenusOf:[menuItem submenu]]; | |
} | |
[self fixUpMenuItem: menuItem]; | |
} | |
} | |
- (void)fixUpMenuItem:(NSMenuItem *)menuItem { | |
// In Firefox, `Select All` menu item is initially disabled for some reason | |
if (![menuItem isEnabled] && [[menuItem title] isEqualToString:NSLocalizedString(@"Select All", nil)]) { | |
[menuItem setEnabled:YES]; | |
} | |
} | |
@end | |
@implementation NSObject (main) | |
+ (void)load { | |
ZKSwizzle(myNSApplication, NSApplication); | |
ZKSwizzle(myNSWindow, NSWindow); | |
} | |
@end | |
int main() {} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@isolinear I can use DYLD_INSERT_LIBRARIES, but I'm on a truly ancient version of macOS (10.9). I think on 10.15 you should be able to use
DYLD_INSERT_LIBRARIES
if you disable System Integrity Protection, is that turned on? If you want SIP, I think you could also use insert_dylib to add the library to the Firefox binary; you may need to re-sign Firefox afterwards. However, I'm unfortunately not familiar with modern macOS anymore, I know Apple has added tons of restrictions on code injection and it's one of the reasons I'm on this ancient version.