Detect click on menu item in cocoa (macos)?

I would like to execute a function (say print something to the console), when a menu item in the systray is clicked. I just can't figure out how to do this. In Swift for example it would simply be

@objc func menuItemClick() {
    print("Clicked a menu item.")
}

and this can then be assigned to the item like so

statusBarMenu.addItem(
    withTitle: "An item",
    action: #selector(menuItemClick),
    keyEquivalent: ""
)

I tried to replicate the same behaviour in Rust, but failed, because I was unable to figure out how to declare a custom objective C function, that will print something to the console:

use cocoa::{
    appkit::{
        NSApp, NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
        NSMenu, NSMenuItem, NSSquareStatusItemLength, NSStatusBar, NSStatusItem,
        NSWindow,
    },
    base::{nil, selector},
    foundation::{NSAutoreleasePool, NSString},
};

fn main() {
    unsafe {
        let app = NSApp();
        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
        //app.activateIgnoringOtherApps_(cocoa::base::YES); // We probably don't want this when creating a tray menu.

        let status_bar = NSStatusBar::systemStatusBar(app);
        let status_item = status_bar.statusItemWithLength_(NSSquareStatusItemLength);

        let title = NSString::alloc(nil).init_str("😀");
        status_item.button().setTitle_(title);

        let status_bar_menu = NSMenu::new(nil).autorelease();

        let quit_title = NSString::alloc(nil).init_str("Quit ");
        let quit_action = selector("applicationShouldTerminate:");
        let quit_key = NSString::alloc(nil).init_str("q");
        let quit_item = NSMenuItem::alloc(nil)
            .initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key)
            .autorelease();
        status_bar_menu.addItem_(quit_item);

        status_item.setMenu_(status_bar_menu);
        app.setMenu_(status_bar_menu);

        app.run();
    }
}

How can I execute a Rust function, when the "Quit" entry is clicked?

1 Like

Maybe the delegate! macro helps? I don't know how addItemWithTitle:action:keyEquivalent: determines the object to send the message to though.

Yes, I am pretty sure I have to use the delegate macro, but I have only seen this used with a window so far (and I don't have one)...

I think the target field is used to determine where to send the message to. Maybe try setting the target field to your delegate?

I am not sure I understood this correctly, but I tried this, unfortunately the function is never executed:

use cocoa::{appkit::{
        NSApp, NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
        NSMenu, NSMenuItem, NSSquareStatusItemLength, NSStatusBar, NSStatusItem,
        NSWindow,
    }, base::{nil, id, selector}, delegate, foundation::{NSAutoreleasePool, NSString}};

use objc::{class, msg_send, runtime::{Object, Sel}, sel, sel_impl};

fn main() {
    unsafe {
        let app = NSApp();
        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
        //app.activateIgnoringOtherApps_(cocoa::base::YES); // We probably don't want this when creating a tray menu.

        let status_bar = NSStatusBar::systemStatusBar(app);
        let status_item = status_bar.statusItemWithLength_(NSSquareStatusItemLength);

        let title = NSString::alloc(nil).init_str("😀");
        status_item.button().setTitle_(title);

        let status_bar_menu = NSMenu::new(nil).autorelease();
        status_bar_menu.setDelegate_(delegate!("test", {
            window: id = status_bar_menu,
            (test:) => window_should_close as extern fn(&Object, Sel, id)
        }));

        let quit_title = NSString::alloc(nil).init_str("Quit ");
        let quit_action = selector("test:");
        let quit_key = NSString::alloc(nil).init_str("q");
        let quit_item = NSMenuItem::alloc(nil)
            .initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key)
            .autorelease();
        quit_item.setTarget_(status_bar_menu);
        status_bar_menu.addItem_(quit_item);

        status_item.setMenu_(status_bar_menu);
        app.setMenu_(status_bar_menu);

        app.run();
    }
}

extern fn window_should_close(_this: &Object, _cmd: Sel, _notification: id) {
    // Bye bye Rust program...
    std::process::exit(0);
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.