How do I get keyboard events in the topmost NSPanel?

WaveRiderWaveRider Member ✭✭

Good day

I have created an app using Xamarin and objective-c to help watching movies online. It shows the subtitles on top of all other windows. This has been done using the NSPanel as it was the only way to make it work on MacOS Mojave. The app works great and made me really happy. Now I want to improve the app a bit and make NSPanel to respond to the keyboard events, so I can control the app using the keyboard to pause, play go backward or forward.

So, How do I get keyboard events in the topmost NSPanel? I tried to use this code:

NSEvent.AddLocalMonitorForEventsMatchingMask(NSEventMask.KeyDown, KeyboardEventHandler);

private static NSEvent KeyboardEventHandler(NSEvent keyEvent)
{
// handle key down events here

return (keyEvent);
}

and it only works when the app is not in the full-screen mode.

The full SubtitlesViewer-MACOS project can be found on GitHub.

Here is the part of the code that creates the panel:
public override void ViewWillAppear()
{
base.ViewWillAppear();
SetupView();
}

private void SetupView()
{ 
    var screenRes = screenResolution();
    int PANEL_HEIGHT = 200;
    subtitlesPanel = new NSPanel
    (
        new CoreGraphics.CGRect(40, 50, screenRes.Width - 80, PANEL_HEIGHT),
        NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.DocModal,
        NSBackingStore.Buffered, true
    )
    {
        BackgroundColor = NSColor.FromCalibratedRgba(0, 0, 0, 0.0f),
        ReleasedWhenClosed = true,
        HidesOnDeactivate = false,
        FloatingPanel = true,
        StyleMask = NSWindowStyle.NonactivatingPanel,
        Level = NSWindowLevel.MainMenu - 1,
        IsMovable = true,
        CollectionBehavior = NSWindowCollectionBehavior.CanJoinAllSpaces |
        NSWindowCollectionBehavior.FullScreenAuxiliary
    };

    subtitlesPanel.OrderFront(null);

    subtitleTextButton = new NSButton(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
    {
        Title = "",
        WantsLayer = true
    };

    subtitleTextButton.Layer.BackgroundColor = NSColor.Clear.CGColor;

    subtitleTextField = new NSTextField(new CoreGraphics.CGRect(40, 0, screenRes.Width - 120, PANEL_HEIGHT-30))
    {
        Alignment = NSTextAlignment.Center
    };
    subtitleTextField.Cell.Alignment = NSTextAlignment.Center;

    forwardButton = new NSButton(new CoreGraphics.CGRect(0, 0, 40, 30));
    forwardButton.Title = ">>";
    forwardButton.Activated += (object sender, EventArgs e) => {
        subtitlesProvider.Forward();
    };

    backButton = new NSButton(new CoreGraphics.CGRect(0, 30, 40, 30));
    backButton.Title = "<<";
    backButton.Activated += (object sender, EventArgs e) => {
        subtitlesProvider.Back();
    };

    startStopButton = new NSButton(new CoreGraphics.CGRect(0, 60, 40, 30));
    startStopButton.Title = "Play";
    startStopButton.Activated += (object sender, EventArgs e) => {
        subtitlesProvider.StartStop(subtitlesProvider.Playing);
    };

    subtitlesPanel.ContentView.AddSubview(subtitleTextButton, NSWindowOrderingMode.Below, null);
    subtitlesPanel.ContentView.AddSubview(subtitleTextField, NSWindowOrderingMode.Below, null);

    subtitlesPanel.ContentView.AddSubview(forwardButton, NSWindowOrderingMode.Below, null);
    subtitlesPanel.ContentView.AddSubview(backButton, NSWindowOrderingMode.Below, null);
    subtitlesPanel.ContentView.AddSubview(startStopButton, NSWindowOrderingMode.Below, null);

    SetupSubtitlesProvider();
}

Please kindly advice what else I should try to make it work.

Thank you

Wave Rider

Best Answer

Answers

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai
    Accepted Answer

    So I don't have an answer, but often you have to subclass to make things like this work

    https://stackoverflow.com/questions/11622255/keydown-not-being-called

    Consider creating a custom NSPanel subclass and see if it has an override that fits your use case.

  • WaveRiderWaveRider Member ✭✭

    Thank you @ChrisHamons I have subclassed the NSPanel and also added the code to keep the NSPanel always focused.

  • WaveRiderWaveRider Member ✭✭

    I achieved my goal, I now use the left arrow key to go back, the right arrow key to go forward and a space key to start/stop.

        namespace SubtitlesViewer
        {
            public class NSPanelExt : NSPanel
            {
                public KeyPressedHandler KeyPressed;
                public delegate void KeyPressedHandler(KeyCodeEventArgs e);
    
                public NSPanelExt(CGRect contentRect, NSWindowStyle aStyle, NSBackingStore bufferingType, bool deferCreation) : base(contentRect, aStyle, bufferingType, deferCreation)
                {
                }
    
                public override bool CanBecomeMainWindow => true;
    
                public override bool CanBecomeKeyWindow => true;
    
                public override bool AcceptsFirstResponder()
                {
                    return true;
                }
    
    
                public override void KeyDown(NSEvent theEvent)
                {
                    // this function is never called
                    KeyPressed?.Invoke(new KeyCodeEventArgs {  Key = GetKeyCode(theEvent.KeyCode) });
    
                }
    
                private KeyCode GetKeyCode(ushort keyCode)
                {
                    KeyCode result = KeyCode.Unknown;
                    switch (keyCode)
                    {
                        case 123:
                            result = KeyCode.Left;
                            break;
                        case 49:
                            result = KeyCode.Space;
                            break;
                        case 124:
                            result = KeyCode.Right;
                            break;
                        case 53:
                            result = KeyCode.Esc;
                            break;
                    }
    
                    return result;
                }
            }
        }
    
Sign In or Register to comment.