Programmatically open Mac Help menu
Asked Answered
G

2

15

I'm integrating a GTK# application into Mac OS X. GTK on Mac OS X is a wrapper over some Cocoa and Carbon fundamentals. We have some platform-specific stuff directly using Carbon global menu APIs (it's more low-level and flexible than Cocoa, and we don't need to be 64-bit).

It seems that GTK swallows up all the keyboard events before Carbon dispatches them as commands. This makes sense, because there is no mapping of Carbon commands into the GTK world. In general, this isn't a problem, because we have a global key event handler and dispatch everything via our own command system. However, this seems to be preventing Cmd-? from opening the Help search menu, and I cannot find a way to do this programmatically.

Menu Manager's MenuSelect function is promising, but I haven't figured out a way to determine the coordinate automatically, and for some reason it only works when I hit the combination twice...

Alternatively, a way to dispatch the Cmd-? keystroke to Carbon's command handling or synthesize the command event directly would be good, but I haven't had any luck in that area.

Carbon's ProcessHICommand isn't any use without a command ID and I can't figure out what it is (if there is one)

Regarding Cocoa, I can get hold of the NSWindow and call InterpretKeyEvents, but I haven't had any luck success synthesizing the NSEvent - it just beeps. The event I'm using is

var evt = NSEvent.KeyEvent (NSEventType.KeyDown, System.Drawing.PointF.Empty,
    NSEventModifierMask.CommandKeyMask | NSEventModifierMask.ShiftKeyMask,
    0, win.WindowNumber, NSGraphicsContext.CurrentContext, "?", "?",
    false, (ushort) keycode);

Keycode is determined from a GTK keymap to be 44. I confirmed that the keycode was correct using a plain MonoMac (Cocoa) app but InterpretKeyEvents did not work with the event in that app either. And I can't find any selector associated with the command.

Gefen answered 17/11, 2010 at 2:24 Comment(7)
Users have been asking for the Cmd-Shift-? keybinding to open the Help search menu. Somehow, GTK's event loop prevents the built-in handling from working.Gefen
With the cocoa event, have you tried using "/" instead of "?".Chelsae
On a side note, your statement that carbon is "more low-level and flexible than Cocoa" is incorrect. Carbon is a completely separate API that was designed to be compatible with Mac OS 8/9. It is deprecated and poorly maintained and is not "underneath" cocoa in any way. Cocoa is actually much older than Carbon, even though it's more modern looking at a first glance. You should really be interfacing with Cocoa instead of Carbon.Chelsae
So why do I see HIToolbox calls in the Cocoa menu callstack?Gefen
Interesting. I guess they must have decided some of Carbon's menu stack is better than the one in Cocoa, so they used it. Still, I wouldn't use any carbon API's in an app that you're going to be working on for a long time. Apple started seriously moving away from Carbon in the last release of Mac OS X, and anyone who writes Carbon code today, should expect it will need to be re-written in Cocoa in the near future.Chelsae
Yeah, it wasn't actually public Carbon API, but I think it's reasonable to say that Cocoa's menu system is built on top of primitives from Carbon's HIToolbox menu system. Also, Cocoa is certainly built on top of lever-level C APIs (Core Foundation, Obj-C runtime etc). However, I agree that using deprecated Carbon APIs such as the HIToolbox is a bad idea.Gefen
Carbon is also built on top of Core Foundation. Core Foundation does use C, but only because it is necessary for extra performance, and most of it is compatible with Obj-C code. For example you can cast a CFDictionaryRef to NSDictionary * or id and it will behave exactly the same as an NSDictionary object (there are some subtle differences to make CFDictionary faster than NSDictionary. But they are negligible).Chelsae
C
1

You can use accessibility APIs to fake a press on the menu item.

NSString *helpMenuTitle = [[[[NSApplication sharedApplication] mainMenu] itemWithTag:HELP_MENU_TAG] title];
AXUIElementRef appElement = AXUIElementCreateApplication(getpid());
AXUIElementRef menuBar;
AXError error = AXUIElementCopyAttributeValue(appElement,
                                              kAXMenuBarAttribute,
                                              (CFTypeRef *)&menuBar);
if (error) {
    return;
}

CFIndex count = -1;
error = AXUIElementGetAttributeValueCount(menuBar, kAXChildrenAttribute, &count);
if (error) {
    CFRelease(menuBar);
    return;
}

NSArray *children = nil;
error = AXUIElementCopyAttributeValues(menuBar, kAXChildrenAttribute, 0, count, (CFArrayRef *)&children);
if (error) {
    CFRelease(menuBar);
    return;
}

for (id child in children) {
    AXUIElementRef element = (AXUIElementRef)child;
    id title;
    AXError error = AXUIElementCopyAttributeValue(element,
                                                  kAXTitleAttribute,
                                                  (CFTypeRef *)&title);
    if ([title isEqualToString:helpMenuTitle]) {
        AXUIElementPerformAction(element, kAXPressAction);
        CFRelease(title);
        break;
    }
    CFRelease(title);
}
CFRelease(menuBar);
[children release];
Chaparral answered 24/12, 2013 at 1:47 Comment(2)
We actually found another solution that involved fixing the GTK toolkit so it wouldn't intercept that keypress but would let Cocoa handle it instead. However, I'm accepting this answer since it's a good solution for anyone willing to depend on Accessibility Frameworks.Gefen
Careful there. This will fail in any language other than English because of the hard-coded @"Help" title. You need to assign a tag value to the help menu in your application's main menu, then grab the menu's actual title and use that instead.Mohawk
A
1

You could do this via calling from C / Objective-C a AppleScript (GUI) script , that would essentially do the pointing and clicking of a user just as a user would do, to open the help menu program-matically.

Afc answered 6/2, 2012 at 15:16 Comment(0)
C
1

You can use accessibility APIs to fake a press on the menu item.

NSString *helpMenuTitle = [[[[NSApplication sharedApplication] mainMenu] itemWithTag:HELP_MENU_TAG] title];
AXUIElementRef appElement = AXUIElementCreateApplication(getpid());
AXUIElementRef menuBar;
AXError error = AXUIElementCopyAttributeValue(appElement,
                                              kAXMenuBarAttribute,
                                              (CFTypeRef *)&menuBar);
if (error) {
    return;
}

CFIndex count = -1;
error = AXUIElementGetAttributeValueCount(menuBar, kAXChildrenAttribute, &count);
if (error) {
    CFRelease(menuBar);
    return;
}

NSArray *children = nil;
error = AXUIElementCopyAttributeValues(menuBar, kAXChildrenAttribute, 0, count, (CFArrayRef *)&children);
if (error) {
    CFRelease(menuBar);
    return;
}

for (id child in children) {
    AXUIElementRef element = (AXUIElementRef)child;
    id title;
    AXError error = AXUIElementCopyAttributeValue(element,
                                                  kAXTitleAttribute,
                                                  (CFTypeRef *)&title);
    if ([title isEqualToString:helpMenuTitle]) {
        AXUIElementPerformAction(element, kAXPressAction);
        CFRelease(title);
        break;
    }
    CFRelease(title);
}
CFRelease(menuBar);
[children release];
Chaparral answered 24/12, 2013 at 1:47 Comment(2)
We actually found another solution that involved fixing the GTK toolkit so it wouldn't intercept that keypress but would let Cocoa handle it instead. However, I'm accepting this answer since it's a good solution for anyone willing to depend on Accessibility Frameworks.Gefen
Careful there. This will fail in any language other than English because of the hard-coded @"Help" title. You need to assign a tag value to the help menu in your application's main menu, then grab the menu's actual title and use that instead.Mohawk

© 2022 - 2024 — McMap. All rights reserved.