Skip to content

Instantly share code, notes, and snippets.

@Wowfunhappy
Last active February 19, 2026 17:50
Show Gist options
  • Select an option

  • Save Wowfunhappy/b09900c5420767651338a57692348e31 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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.

@MikeRich88
Copy link
Copy Markdown

Default shortcuts still work if nothing else is assigned to them...
Although this doesn't match the behavior of proper cocoa apps,
it's not terrible and I don't see an easy way to fix it.

It is terrible though. I keep hitting Cmd-M by mistake for some reason in daily usage of the Mac, so I reassigned Minimize to Cmd-Opt-Ctrl-Shift-M or something like that, but in Firefox, the app I probably use the most, Cmd-M still minimizes the window.

@Wowfunhappy
Copy link
Copy Markdown
Author

It is terrible though. I keep hitting Cmd-M by mistake for some reason in daily usage of the Mac, so I reassigned Minimize to Cmd-Opt-Ctrl-Shift-M or something like that, but in Firefox, the app I probably use the most, Cmd-M still minimizes the window.

That’s an annoying use case I didn’t think of! Open to ideas on how to resolve this one.

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