swiftmacoscocoaappkit

How do I capture the right click event on status bar?


I'm building a mac app with status bar icon. I want to handle right-click event on the icon.

class StatusBar {
  var statusBar: NSStatusBar!
  var statusBarItem: NSStatusItem!

  init() {
    statusBar = NSStatusBar()
    statusBarItem = statusBar.statusItem(
      withLength: NSStatusItem.variableLength
    )
    if let button = statusBarItem.button {
      // code to set up icon is ignored

      // Doesn't work
      button.sendAction(on: .any)
      button.action = #selector(handleTwoFingerTap(_:))
      button.target = self
    }
  }

    @objc private func handleTwoFingerTap(_: Any?) {
      // this function is not called
    }
}

I suspect this is due to the limitation of sendAction, quote

The only conditions that are actually checked are associated with the NSLeftMouseDownMask, NSLeftMouseUpMask, NSLeftMouseDraggedMask, and NSPeriodicMask bits.

In that case, how to make this work?


Solution

  • This is what works for me after many trials and errors. I use it in my app.

    What I did is to handle left click and right click differently. Got a lot of help from this answer Show NSMenu only on NSStatusBarButton right click?. When right click, it will turn the app on and off. When left click, it shows the status menu.

    class StatusBar: NSObject, NSMenuDelegate {
      var statusBar = NSStatusBar()
      var statusItem: NSStatusItem!
      var statusMenu: NSMenu!
      var button: NSStatusBarButton!
    
      @objc func menuDidClose(_: NSMenu) {
        statusItem.menu = nil
      }
    
      // Left click to trigger menu
      // Right click to turn app on/off
      // Solution from https://stackoverflow.com/q/59635971
      @objc func statusBarButtonClicked(sender _: NSStatusBarButton) {
        switch NSApp.currentEvent!.type {
        case .leftMouseUp:
          statusItem.menu = statusMenu
          statusItem.button?.performClick(nil)
    
        case .rightMouseDown:
          Defaults[.appEnabled].toggle()
          updateButtonStatus()
    
        default:
          return
        }
      }
    
      override init() {
        super.init()
        statusItem = statusBar.statusItem(
          withLength: NSStatusItem.variableLength
        )
        setUpStatusButton()
        setupMenu()
      }
    
      func setUpStatusButton() {
        button = statusItem.button
        button.toolTip = "Clicknow"
        button.image = NSImage(named: "status bar")
        button.action = #selector(statusBarButtonClicked(sender:))
        button.sendAction(on: [.leftMouseUp, .rightMouseDown])
        button.target = self
        updateButtonStatus()
      }
    
      func updateButtonStatus() {
        if Defaults[.appEnabled] {
          button.appearsDisabled = false
        } else {
          button.appearsDisabled = true
        }
      }
    
      func setupMenu() {
        statusMenu = NSMenu()
        statusMenu.delegate = self // important
        ...
      }
    }