Skip to content

Instantly share code, notes, and snippets.

@Wowfunhappy
Last active March 5, 2025 10:39
Show Gist options
  • Save Wowfunhappy/b09900c5420767651338a57692348e31 to your computer and use it in GitHub Desktop.
Save Wowfunhappy/b09900c5420767651338a57692348e31 to your computer and use it in GitHub Desktop.
Firefox Key Equivalent Fixer
#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() {}
@Wowfunhappy
Copy link
Author

Wowfunhappy commented Nov 21, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment