Detect left and right click events on NSStatusItem (Swift) - swift

I’m building a status bar app and want to call different actions depending on if the user clicked left or right. Here’s what I have so far:
var statusItem = NSStatusBar.system().statusItem(withLength: -1)
statusItem.action = #selector(AppDelegate.doSomeAction(sender:))
let leftClick = NSEventMask.leftMouseDown
let rightClick = NSEventMask.rightMouseDown
statusItem.button?.sendAction(on: leftClick)
statusItem.button?.sendAction(on: rightClick)
func doSomeAction(sender: NSStatusItem) {
print("hello world")
}
My function is not called and I couldn’t find our why. I appreciate any help!

Have you tried:
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
Then seeing which mouse button was pressed in the doSomeAction() function?
So it will look something like...
let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.action = #selector(self.doSomeAction(sender:))
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
}
}
func doSomeAction(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
// Right button click
} else {
// Left button click
}
}
Thanks to #dbrownjave for noticing the change in Swift 4 from NSEventType.rightMouseUp to NSEvent.EventType.rightMouseUp.
https://github.com/craigfrancis/datetime/blob/master/xcode/DateTime/AppDelegate.swift

Updated: SWIFT 4
I've updated (Craig Francis) answer
func doSomeAction(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp{
// Right button click
} else {
// Left button click
}

Related

Can't hide NSStatusItem from status bar?

I'm trying to add an option to my app settings to hide the icon from the status bar. It currently looks like this inside my AppDelegate :
func applicationDidFinishLaunching(_ notification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem?.button?.image = NSImage(systemSymbolName: "paperplane.fill", accessibilityDescription: nil)
if let menu = menu {
statusItem?.menu = menu
}
}
I'm trying to hide it from a preference pane view controller with the following code :
#IBAction func hideicon(_ sender: Any) {
if hideicon.state.rawValue == 1 {
NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength).isVisible = false
} else {
NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength).isVisible = true
}
}
But the button is still showing. Trying to edit the button image isn't working either.
What am I missing here?
This :
#IBAction func hideicon(_ sender: Any) {
if hideicon.state.rawValue == 1 {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.statusItem?.isVisible = false
} else {
let appDelegate = NSApplication.shared.delegate as! AppDelegate
appDelegate.statusItem?.isVisible = true
}
}
Simply solved my issue.

How to ensure NSAlert pop up on window top?

The application is background only
This is a timer app
A dialog box(NSAlert) pops up after a specified time to prompt the user. How to ensure NSAlert on window top?
StatusBarMenu
class StatusBarMenu: NSObject {
private let statusItem: NSStatusItem!
init(statusItem: NSStatusItem) {
self.statusItem = statusItem
}
private func createMenu() {
selft.addMenuItem()
}
func refresh() {
self.statusItem.menu?.removeAllItems()
self.createMenu()
}
func addMenuItem() {
let item = NSMenuItem(title: "show alert", action: #selector(self.showAlert), keyEquivalent: "")
item.target = self
self.statusItem.menu?.addItem(item)
}
#objc func showAlert() {
DispatchQueue.main.sync {
let alert = NSAlert()
alert.icon = NSImage(named: "Alert")
alert.messageText = title
alert.informativeText = text
alert.alertStyle = .informational
alert.addButton(withTitle: "ok")
alert.runModal()
}
}
}
I found an answer to my own question:
alert.window.level = .floating
This works.
This is an example of how to keep NSAlert window on top
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
title, ok, cancel, other, message)
alert.setAlertStyle_(0) # informational style
# customize# floating window
alert.window().setLevel_(3)

In 2020, is there a way to deal with left and right click for menu bar separately using Swift? [duplicate]

I have the following code: (can be copy-pasted to New macOS project)
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.menu = statusBarMenu
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
Actual behaviour: Menu opens on both left and right click, statusBarButtonClicked is not triggered.
After removing this line:
statusBarItem.menu = statusBarMenu
statusBarButtonClicked triggers on left click, menu doesn't show up (as expected)
Desired behaviour: Menu opens on right click, on left click menu doesn't open, action is triggered. How do I achieve it?
EDIT
I managed to achieve desired behavior with help of #red_menace comment:
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
menu = statusBarMenu
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
statusBarItem.popUpMenu(menu)
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
But Xcode says that openMenu func is deprecated in 10.14 and tells me to Use the menu property instead. Is there I way to achieve desired behaviour with new API?
The usual way a to show a menu is to assign a menu to the status item, where it will be shown when the status item button is clicked. Since popUpMenu is deprecated, another way is needed to show the menu under different conditions. If you want the right click to use an actual status item menu instead of just showing a contextual menu at the status item location, the status item menu property can be kept nil until you want to show it.
I've adapted your code to keep the statusBarItem and statusBarMenu references separate, only adding the menu to the status item in the clicked action method. In the action method, once the menu is added, a normal click is performed on the status button to drop the menu. Since the status item will then always show its menu when the button is clicked, an NSMenuDelegate method is added to set the menu property to nil when the menu is closed, restoring the original operation:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
// keep status item and menu separate
var statusBarItem: NSStatusItem!
var statusBarMenu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.delegate = self
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
statusBarItem.menu = statusBarMenu // add menu to button...
statusBarItem.button?.performClick(nil) // ...and click
} else {
print("Left click!")
}
}
#objc func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil // remove menu so button works as before
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
Here is possible approach. There might be more accurate calculations for menu position, including taking into account possible differences of userInterfaceLayoutDirection, but the idea remains the same - take possible events under manual control and make own decision about what to do on each event.
Important places commented in code. (Tested on Xcode 11.2, macOS 10.15)
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp]) // << send action in both cases
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.button?.menu = statusBarMenu // << store menu in button, not item
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
if let button = statusBarItem.button { // << pop up menu programmatically
button.menu?.popUp(positioning: nil, at: CGPoint(x: -1, y: button.bounds.maxY + 5), in: button)
}
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
I'm using this code on macOS Catalina. 10.15.2. ( Xcode 11.3).
On left click It trigger action.
On right click it show menu.
//HEADER FILE
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#protocol MOSMainStatusBarDelegate
- (void) menuBarControllerStatusChanged: (BOOL) active;
#end
#interface MOSMainStatusBar : NSObject
#property (strong) NSMenu *menu;
#property (strong, nonatomic) NSImage *image;
#property (unsafe_unretained, nonatomic) id<MOSMainStatusBarDelegate> delegate;
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu;
- (NSStatusBarButton *) statusItemView;
- (void) showStatusItem;
- (void) hideStatusItem;
#end
//IMPLEMANTION FILE.
#import "MOSMainStatusBar.h"
#interface MOSMainStatusBar ()
#property (strong, nonatomic) NSStatusItem *statusItem;
#end
#implementation MOSMainStatusBar
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu {
self = [super init];
if (self) {
self.image = image;
self.menu = menu;
}
return self;
}
- (void) setImage: (NSImage *) image {
_image = image;
self.statusItem.button.image = image;
}
- (NSStatusBarButton *) statusItemView {
return self.statusItem.button;
}
- (void) showStatusItem {
if (!self.statusItem) {
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self initStatusItem10];
}
}
- (void) hideStatusItem {
if (self.statusItem) {
[[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
self.statusItem = nil;
}
}
- (void) initStatusItem10 {
self.statusItem.button.image = self.image;
self.statusItem.button.imageScaling = NSImageScaleAxesIndependently;
self.statusItem.button.appearsDisabled = NO;
self.statusItem.button.target = self;
self.statusItem.button.action = #selector(leftClick10:);
__unsafe_unretained MOSMainStatusBar *weakSelf = self;
[NSEvent addLocalMonitorForEventsMatchingMask:
(NSEventMaskRightMouseDown | NSEventModifierFlagOption | NSEventMaskLeftMouseDown) handler:^(NSEvent *incomingEvent) {
if (incomingEvent.type == NSEventTypeLeftMouseDown) {
weakSelf.statusItem.menu = nil;
}
if (incomingEvent.type == NSEventTypeRightMouseDown || [incomingEvent modifierFlags] & NSEventModifierFlagOption) {
weakSelf.statusItem.menu = weakSelf.menu;
}
return incomingEvent;
}];
}
- (void)leftClick10:(id)sender {
[self.delegate menuBarControllerStatusChanged:YES];
}
I'm using this code on Catalina. Rather than performClick like some of the other answers suggest, I had to manually position the popup. This works for me with external monitors.
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button?.action = #selector(onClick)
statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
menu = NSMenu()
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
menu.delegate = self
}
#objc func onClick(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
// right click, show quit menu
statusItem.menu = menu;
menu.popUp(positioning: nil,
at: NSPoint(x: 0, y: statusItem.statusBar!.thickness),
in: statusItem.button)
} else {
// main click
}
}
#objc func menuDidClose(_ menu: NSMenu) {
// remove menu when closed so we can override left click behavior
statusItem.menu = nil
}
Learning from the great answers already given in here I came up with an alternative.
The advantage of this approach is that you only set the NSMenu once and don't have to juggle with setting it or removing it anymore.
Setting up the NSStatusBarButton
let status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
status.button!.image = NSImage(named: "status")
status.button!.target = self
status.button!.action = #selector(triggerStatus)
status.button!.menu = /* your NSMenu */
status.button!.sendAction(on: [.leftMouseUp, .rightMouseUp])
Receiving the action
#objc private func triggerStatus(_ button: NSStatusBarButton) {
guard let event = NSApp.currentEvent else { return }
switch event.type {
case .rightMouseUp:
NSMenu.popUpContextMenu(button.menu!, with: event, for: button)
case .leftMouseUp:
/* show your Popup or any other action */
default:
break
}
}

How do I open a setting menù from a Status Item on right-click? Swift on MacOS [duplicate]

I’m building a status bar app and want to call different actions depending on if the user clicked left or right. Here’s what I have so far:
var statusItem = NSStatusBar.system().statusItem(withLength: -1)
statusItem.action = #selector(AppDelegate.doSomeAction(sender:))
let leftClick = NSEventMask.leftMouseDown
let rightClick = NSEventMask.rightMouseDown
statusItem.button?.sendAction(on: leftClick)
statusItem.button?.sendAction(on: rightClick)
func doSomeAction(sender: NSStatusItem) {
print("hello world")
}
My function is not called and I couldn’t find our why. I appreciate any help!
Have you tried:
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
Then seeing which mouse button was pressed in the doSomeAction() function?
So it will look something like...
let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
if let button = statusItem.button {
button.action = #selector(self.doSomeAction(sender:))
button.sendAction(on: [.leftMouseUp, .rightMouseUp])
}
}
func doSomeAction(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
// Right button click
} else {
// Left button click
}
}
Thanks to #dbrownjave for noticing the change in Swift 4 from NSEventType.rightMouseUp to NSEvent.EventType.rightMouseUp.
https://github.com/craigfrancis/datetime/blob/master/xcode/DateTime/AppDelegate.swift
Updated: SWIFT 4
I've updated (Craig Francis) answer
func doSomeAction(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp{
// Right button click
} else {
// Left button click
}

Show NSMenu only on NSStatusBarButton right click?

I have the following code: (can be copy-pasted to New macOS project)
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.menu = statusBarMenu
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
Actual behaviour: Menu opens on both left and right click, statusBarButtonClicked is not triggered.
After removing this line:
statusBarItem.menu = statusBarMenu
statusBarButtonClicked triggers on left click, menu doesn't show up (as expected)
Desired behaviour: Menu opens on right click, on left click menu doesn't open, action is triggered. How do I achieve it?
EDIT
I managed to achieve desired behavior with help of #red_menace comment:
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
menu = statusBarMenu
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
statusBarItem.popUpMenu(menu)
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
But Xcode says that openMenu func is deprecated in 10.14 and tells me to Use the menu property instead. Is there I way to achieve desired behaviour with new API?
The usual way a to show a menu is to assign a menu to the status item, where it will be shown when the status item button is clicked. Since popUpMenu is deprecated, another way is needed to show the menu under different conditions. If you want the right click to use an actual status item menu instead of just showing a contextual menu at the status item location, the status item menu property can be kept nil until you want to show it.
I've adapted your code to keep the statusBarItem and statusBarMenu references separate, only adding the menu to the status item in the clicked action method. In the action method, once the menu is added, a normal click is performed on the status button to drop the menu. Since the status item will then always show its menu when the button is clicked, an NSMenuDelegate method is added to set the menu property to nil when the menu is closed, restoring the original operation:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
// keep status item and menu separate
var statusBarItem: NSStatusItem!
var statusBarMenu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.delegate = self
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
statusBarItem.menu = statusBarMenu // add menu to button...
statusBarItem.button?.performClick(nil) // ...and click
} else {
print("Left click!")
}
}
#objc func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil // remove menu so button works as before
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
Here is possible approach. There might be more accurate calculations for menu position, including taking into account possible differences of userInterfaceLayoutDirection, but the idea remains the same - take possible events under manual control and make own decision about what to do on each event.
Important places commented in code. (Tested on Xcode 11.2, macOS 10.15)
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp]) // << send action in both cases
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.button?.menu = statusBarMenu // << store menu in button, not item
}
#objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
if let button = statusBarItem.button { // << pop up menu programmatically
button.menu?.popUp(positioning: nil, at: CGPoint(x: -1, y: button.bounds.maxY + 5), in: button)
}
} else {
print("Left click!")
}
}
#objc func orderAnApple() {
print("Ordering a apple!")
}
#objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
I'm using this code on macOS Catalina. 10.15.2. ( Xcode 11.3).
On left click It trigger action.
On right click it show menu.
//HEADER FILE
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#protocol MOSMainStatusBarDelegate
- (void) menuBarControllerStatusChanged: (BOOL) active;
#end
#interface MOSMainStatusBar : NSObject
#property (strong) NSMenu *menu;
#property (strong, nonatomic) NSImage *image;
#property (unsafe_unretained, nonatomic) id<MOSMainStatusBarDelegate> delegate;
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu;
- (NSStatusBarButton *) statusItemView;
- (void) showStatusItem;
- (void) hideStatusItem;
#end
//IMPLEMANTION FILE.
#import "MOSMainStatusBar.h"
#interface MOSMainStatusBar ()
#property (strong, nonatomic) NSStatusItem *statusItem;
#end
#implementation MOSMainStatusBar
- (instancetype) initWithImage: (NSImage *) image menu: (NSMenu *) menu {
self = [super init];
if (self) {
self.image = image;
self.menu = menu;
}
return self;
}
- (void) setImage: (NSImage *) image {
_image = image;
self.statusItem.button.image = image;
}
- (NSStatusBarButton *) statusItemView {
return self.statusItem.button;
}
- (void) showStatusItem {
if (!self.statusItem) {
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[self initStatusItem10];
}
}
- (void) hideStatusItem {
if (self.statusItem) {
[[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
self.statusItem = nil;
}
}
- (void) initStatusItem10 {
self.statusItem.button.image = self.image;
self.statusItem.button.imageScaling = NSImageScaleAxesIndependently;
self.statusItem.button.appearsDisabled = NO;
self.statusItem.button.target = self;
self.statusItem.button.action = #selector(leftClick10:);
__unsafe_unretained MOSMainStatusBar *weakSelf = self;
[NSEvent addLocalMonitorForEventsMatchingMask:
(NSEventMaskRightMouseDown | NSEventModifierFlagOption | NSEventMaskLeftMouseDown) handler:^(NSEvent *incomingEvent) {
if (incomingEvent.type == NSEventTypeLeftMouseDown) {
weakSelf.statusItem.menu = nil;
}
if (incomingEvent.type == NSEventTypeRightMouseDown || [incomingEvent modifierFlags] & NSEventModifierFlagOption) {
weakSelf.statusItem.menu = weakSelf.menu;
}
return incomingEvent;
}];
}
- (void)leftClick10:(id)sender {
[self.delegate menuBarControllerStatusChanged:YES];
}
I'm using this code on Catalina. Rather than performClick like some of the other answers suggest, I had to manually position the popup. This works for me with external monitors.
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem.button?.action = #selector(onClick)
statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
menu = NSMenu()
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
menu.delegate = self
}
#objc func onClick(sender: NSStatusItem) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
// right click, show quit menu
statusItem.menu = menu;
menu.popUp(positioning: nil,
at: NSPoint(x: 0, y: statusItem.statusBar!.thickness),
in: statusItem.button)
} else {
// main click
}
}
#objc func menuDidClose(_ menu: NSMenu) {
// remove menu when closed so we can override left click behavior
statusItem.menu = nil
}
Learning from the great answers already given in here I came up with an alternative.
The advantage of this approach is that you only set the NSMenu once and don't have to juggle with setting it or removing it anymore.
Setting up the NSStatusBarButton
let status = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
status.button!.image = NSImage(named: "status")
status.button!.target = self
status.button!.action = #selector(triggerStatus)
status.button!.menu = /* your NSMenu */
status.button!.sendAction(on: [.leftMouseUp, .rightMouseUp])
Receiving the action
#objc private func triggerStatus(_ button: NSStatusBarButton) {
guard let event = NSApp.currentEvent else { return }
switch event.type {
case .rightMouseUp:
NSMenu.popUpContextMenu(button.menu!, with: event, for: button)
case .leftMouseUp:
/* show your Popup or any other action */
default:
break
}
}