macosmonogtkmonomac

KeyEquivalents not working for MonoMac NSMenuItem when used in a GTK# app


I am trying to use improve a GTK# application which runs on Mac OS by using the API that MonoMac exposes and build a native Mac menu using the NSMenuItem and NSMenu classes.

So far everything looks good and I was able to set some keyboard accelerators (KeyEquivalent) to the menu but the problem is that they don't react to the keyboard. The key combinations are displayed correctly in the menu but pressing the keys do not do anything. Selecting the menu items using the mouse works fine.

Here is some code that I used to initialize the the environment:

NSApplication.InitDrawingBridge ();
NSApplication.Init ();

NSMenuItem super = new NSMenuItem (DataHelper.ProductName);
super.Submenu = new NSMenu (DataHelper.ProductName);

NSMenu mainMenu = new NSMenu ();
mainMenu.AddItem (super);

NSApplication.SharedApplication.MainMenu = mainMenu;

< menu initialization comes here >

NSApplication.SharedApplication.Delegate = new AppDelegate ();
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);

Is there anything else I have to do in order to make the keyboard events pass to the menu?

I looked at MonoDevelop for code reference and could not find anything that could make that difference. I used some code from there for setting the KeyEquivalent values too.


Solution

  • It turns out that GTK does not automatically send the KeyPress events to the menu if they remain unhandled. What I did was to capture the event on the outermost container in GTK and manually send it to the Mac Menu like that:

    Note: Most of the code bellow is borrowed from Monodevelop and is MIT/X11 licensed

    private void frmMain_KeyPressEvent (object o, KeyPressEventArgs args)
    {
        Gdk.ModifierType state;
        state = GtkWorkarounds.FixMacModifiers (args.Event.State, args.Event.Group);
    
        string keyEquivalent;
        NSEventModifierMask mask;
        GetKeyEquivalentForGdkKey (args.Event.Key, state, out keyEquivalent, out mask);
        if (keyEquivalent.Length == 0)
            return false;
    
        var nsEvent = NSEvent.KeyEvent (
            NSEventType.KeyDown, PointF.Empty, mask, 0, 0,
            NSGraphicsContext.CurrentContext, keyEquivalent, keyEquivalent, false, 0);
    
        if (NSApplication.SharedApplication.MainMenu.PerformKeyEquivalent (nsEvent)) {
            args.RetVal = true;
            return true;
        }
        return false;
    }
    
    public static void GetKeyEquivalentForGdkKey (Gdk.Key key, ModifierType modifierType, out string keyEquivalent, out NSEventModifierMask mask)
    {
        keyEquivalent = GetKeyEquivalent (key);
        mask = 0;
    
        if (keyEquivalent.Length == 0)
            return;
    
        if ((modifierType & Gdk.ModifierType.Mod1Mask) != 0) {
            mask |= NSEventModifierMask.AlternateKeyMask;
            modifierType ^= Gdk.ModifierType.Mod1Mask;
        }
        if ((modifierType & Gdk.ModifierType.ShiftMask) != 0) {
            mask |= NSEventModifierMask.ShiftKeyMask;
            modifierType ^= Gdk.ModifierType.ShiftMask;
        }
        if ((modifierType & Gdk.ModifierType.ControlMask) != 0) {
            mask |= NSEventModifierMask.ControlKeyMask;
            modifierType ^= Gdk.ModifierType.ControlMask;
        }
        if ((modifierType & Gdk.ModifierType.MetaMask) != 0) {
            mask |= NSEventModifierMask.CommandKeyMask;
            modifierType ^= Gdk.ModifierType.MetaMask;
        }
    }
    
    static string GetKeyEquivalent (Gdk.Key key)
    {
        char c = (char) Gdk.Keyval.ToUnicode ((uint) key);
        if (c != 0)
            return c.ToString ();
    
        var fk = GetFunctionKey (key);
        if (fk != 0)
            return ((char) fk).ToString ();
    
        return "";
    }
    
    static FunctionKey GetFunctionKey (Gdk.Key key)
    {
        switch (key) {
            case Gdk.Key.Return:
                return (FunctionKey) (uint) '\n';
            case Gdk.Key.BackSpace:
                return (FunctionKey) 0x08;
            // NSBackspaceCharacter
            case Gdk.Key.KP_Delete:
            case Gdk.Key.Delete:
                return (FunctionKey) 0x7F;
            // NSDeleteCharacter
            case Gdk.Key.KP_Up:
            case Gdk.Key.Up:
                return FunctionKey.UpArrow;
            case Gdk.Key.KP_Down:
            case Gdk.Key.Down:
                return FunctionKey.DownArrow;
            case Gdk.Key.KP_Left:
            case Gdk.Key.Left:
                return FunctionKey.LeftArrow;
            case Gdk.Key.KP_Right:
            case Gdk.Key.Right:
                return FunctionKey.RightArrow;
            case Gdk.Key.F1:
                return FunctionKey.F1;
            case Gdk.Key.F2:
                return FunctionKey.F2;
            case Gdk.Key.F3:
                return FunctionKey.F3;
            case Gdk.Key.F4:
                return FunctionKey.F4;
            case Gdk.Key.F5:
                return FunctionKey.F5;
            case Gdk.Key.F6:
                return FunctionKey.F6;
            case Gdk.Key.F7:
                return FunctionKey.F7;
            case Gdk.Key.F8:
                return FunctionKey.F8;
            case Gdk.Key.F9:
                return FunctionKey.F9;
            case Gdk.Key.F10:
                return FunctionKey.F10;
            case Gdk.Key.F11:
                return FunctionKey.F11;
            case Gdk.Key.F12:
                return FunctionKey.F12;
            case Gdk.Key.F13:
                return FunctionKey.F13;
            case Gdk.Key.F14:
                return FunctionKey.F14;
            case Gdk.Key.F15:
                return FunctionKey.F15;
            case Gdk.Key.F16:
                return FunctionKey.F16;
            case Gdk.Key.F17:
                return FunctionKey.F17;
            case Gdk.Key.F18:
                return FunctionKey.F18;
            case Gdk.Key.F19:
                return FunctionKey.F19;
            case Gdk.Key.F20:
                return FunctionKey.F20;
            case Gdk.Key.F21:
                return FunctionKey.F21;
            case Gdk.Key.F22:
                return FunctionKey.F22;
            case Gdk.Key.F23:
                return FunctionKey.F23;
            case Gdk.Key.F24:
                return FunctionKey.F24;
            case Gdk.Key.F25:
                return FunctionKey.F25;
            case Gdk.Key.F26:
                return FunctionKey.F26;
            case Gdk.Key.F27:
                return FunctionKey.F27;
            case Gdk.Key.F28:
                return FunctionKey.F28;
            case Gdk.Key.F29:
                return FunctionKey.F29;
            case Gdk.Key.F30:
                return FunctionKey.F30;
            case Gdk.Key.F31:
                return FunctionKey.F31;
            case Gdk.Key.F32:
                return FunctionKey.F32;
            case Gdk.Key.F33:
                return FunctionKey.F33;
            case Gdk.Key.F34:
                return FunctionKey.F34;
            case Gdk.Key.F35:
                return FunctionKey.F35;
            case Gdk.Key.KP_Insert:
            case Gdk.Key.Insert:
                return FunctionKey.Insert;
            case Gdk.Key.KP_Home:
            case Gdk.Key.Home:
                return FunctionKey.Home;
            case Gdk.Key.Begin:
                return FunctionKey.Begin;
            case Gdk.Key.KP_End:
            case Gdk.Key.End:
                return FunctionKey.End;
            case Gdk.Key.KP_Page_Up:
            case Gdk.Key.Page_Up:
                return FunctionKey.PageUp;
            case Gdk.Key.KP_Page_Down:
            case Gdk.Key.Page_Down:
                return FunctionKey.PageDown;
            case Gdk.Key.Key_3270_PrintScreen:
                return FunctionKey.PrintScreen;
            case Gdk.Key.Scroll_Lock:
                return FunctionKey.ScrollLock;
            case Gdk.Key.Pause:
                return FunctionKey.Pause;
            case Gdk.Key.Sys_Req:
                return FunctionKey.SysReq;
            case Gdk.Key.Break:
                return FunctionKey.Break;
            case Gdk.Key.Key_3270_Reset:
                return FunctionKey.Reset;
            case Gdk.Key.Menu:
                return FunctionKey.Menu;
            case Gdk.Key.Print:
                return FunctionKey.Print;
            case Gdk.Key.Help:
                return FunctionKey.Help;
            case Gdk.Key.Find:
                return FunctionKey.Find;
            case Gdk.Key.Undo:
                return FunctionKey.Undo;
            case Gdk.Key.Redo:
                return FunctionKey.Redo;
            case Gdk.Key.Execute:
                return FunctionKey.Execute;
            /*
            return FunctionKey.Stop;
            return FunctionKey.User;
            return FunctionKey.System;
            return FunctionKey.ClearLine;
            return FunctionKey.ClearDisplay;
            return FunctionKey.InsertLine;
            return FunctionKey.DeleteLine;
            return FunctionKey.InsertChar;
            return FunctionKey.DeleteChar;
            return FunctionKey.Next;
            return FunctionKey.Prev;
            return FunctionKey.Select;
            return FunctionKey.ModeSwitch;
            */
        }
    
        return 0;
    }
    
    // "Function-Key Unicodes" from NSEvent reference
    enum FunctionKey : ushort
    {
        UpArrow = 0xF700,
        DownArrow = 0xF701,
        LeftArrow = 0xF702,
        RightArrow = 0xF703,
        F1 = 0xF704,
        F2 = 0xF705,
        F3 = 0xF706,
        F4 = 0xF707,
        F5 = 0xF708,
        F6 = 0xF709,
        F7 = 0xF70A,
        F8 = 0xF70B,
        F9 = 0xF70C,
        F10 = 0xF70D,
        F11 = 0xF70E,
        F12 = 0xF70F,
        F13 = 0xF710,
        F14 = 0xF711,
        F15 = 0xF712,
        F16 = 0xF713,
        F17 = 0xF714,
        F18 = 0xF715,
        F19 = 0xF716,
        F20 = 0xF717,
        F21 = 0xF718,
        F22 = 0xF719,
        F23 = 0xF71A,
        F24 = 0xF71B,
        F25 = 0xF71C,
        F26 = 0xF71D,
        F27 = 0xF71E,
        F28 = 0xF71F,
        F29 = 0xF720,
        F30 = 0xF721,
        F31 = 0xF722,
        F32 = 0xF723,
        F33 = 0xF724,
        F34 = 0xF725,
        F35 = 0xF726,
        Insert = 0xF727,
        Delete = 0xF728,
        Home = 0xF729,
        Begin = 0xF72A,
        End = 0xF72B,
        PageUp = 0xF72C,
        PageDown = 0xF72D,
        PrintScreen = 0xF72E,
        ScrollLock = 0xF72F,
        Pause = 0xF730,
        SysReq = 0xF731,
        Break = 0xF732,
        Reset = 0xF733,
        Stop = 0xF734,
        Menu = 0xF735,
        User = 0xF736,
        System = 0xF737,
        Print = 0xF738,
        ClearLine = 0xF739,
        ClearDisplay = 0xF73A,
        InsertLine = 0xF73B,
        DeleteLine = 0xF73C,
        InsertChar = 0xF73D,
        DeleteChar = 0xF73E,
        Prev = 0xF73F,
        Next = 0xF740,
        Select = 0xF741,
        Execute = 0xF742,
        Undo = 0xF743,
        Redo = 0xF744,
        Find = 0xF745,
        Help = 0xF746,
        ModeSwitch = 0xF747
    }