1

I have a Swift Cocoa app that runs as an NSStatusItem, i.e., it presents a dropdown menu in Mac's top-right status bar. I would like to display extra "advanced" menu items when the user holds down the Option key while opening my status item menu. I can detect presses of the Option key, but my problem is that I cannot determine whether the key is down or up.

Here is the code I have so far in my AppDelegate.swift:

func applicationDidFinishLaunching(_ aNotification: Notification) {
  NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.flagsChanged, handler: keyEvent);
}
    
func keyEvent(event: NSEvent) {
  if let event = NSApp.currentEvent {
    if event.modifierFlags.contains(.option) {
      print("option key up/down event")
    }
  }
}

This works insofar as it prints the message when the Option key is either pressed down or released back up. The problem is that I don't know how to determine which state the Option key is in at any given time.

How can I adapt this code to know if the Option key is down or up?

EDIT: SO suggested that I edit my question to explain why it is not answered by Hide/Show menu item in application's main menu by pressing Option key. Briefly, the answers on that page, while probably workable, seemed either overly complex (adding timers, adding run loop observers, adding zero height custom views) or they were creating menu item alternates whereas I was trying to add additional menu items. Nevertheless, it is a good page and I recommend studying it if you also have this question.

fsctl
  • 161
  • 8
  • Does this answer your question? [Hide/Show menu item in application's main menu by pressing Option key](https://stackoverflow.com/questions/11208632/hide-show-menu-item-in-applications-main-menu-by-pressing-option-key) – Willeke Jun 07 '22 at 20:58
  • I did come across this one in my research. The asker states that he tried `addLocalMonitorForEventsMatchingMask` and `addGlobalMonitorForEventsMatchingMask` and did not get any events, whereas I do get events using the latter NSEvent method. Maybe this answer is the right approach anyway, though; how would I translate it into Swift and the AppDelegate model? – fsctl Jun 07 '22 at 21:23
  • You don't need to monitor events. Do you want to change menu items when the Option-key is down or do you want to add extra menu items? – Willeke Jun 07 '22 at 22:24
  • In ObjC you can just use `NSEvent.modifierFlags` (property on the class), chances are that works in Swift as well. – Gerd K Jun 08 '22 at 03:02

2 Answers2

2

Just ask for the current event and return a Bool, it's true when the key is pressed at the moment

func isOptionKeyPressed() -> Bool {
    return NSEvent.modifierFlags.contains(.option)
}

addGlobalMonitorForEvents is not needed

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Where would I call this code from? I am already calling `NSEvent.modifierFlags.contains(.option)` in the code I posted with the original question. Now, you are also saying to eliminate `addGlobalMonitorForEvents`, which implies eliminating the `keyEvent()` method in the code I posted, but in that case I am not sure where I would call your code from. I tried calling it in my `NSMenuDelegate`'s `menuWillOpen` to decide how to set the `.isHidden` property on the "extra" menu item, but holding down Option had no effect when I put the code there. Can you expand the answer? – fsctl Jun 09 '22 at 16:10
1

I was able to find a working solution, inspired by (but not the same as) another answer here posted by vadian. This is my solution.

  1. Eliminate the addGlobalMonitorForEvents and keyEvent code in my original question.

  2. Create an an NSMenuDelegate like so:

class AppDelegate: NSObject, NSApplicationDelegate {
    @IBOutlet weak var extrasSeparator: NSMenuItem?
    @IBOutlet weak var extrasSubmenu: NSMenuItem?

    [...rest of AppDelegate...]
}

extension AppDelegate: NSMenuDelegate {
    func menuWillOpen(_ menu: NSMenu) {
        if NSEvent.modifierFlags.contains(.option) {
            extrasSeparator?.isHidden = false
            extrasSubmenu?.isHidden = false
        } else {
            extrasSeparator?.isHidden = true
            extrasSubmenu?.isHidden = true
        }
    }
}

Caveat: this solution only works if the user presses and holds the Option key before clicking the NSStatusItem's icon. That's what was asked in the original question, but it may not be desirable in all cases.

fsctl
  • 161
  • 8