Mac Catalyst cannot capture .command key modifier with pressesBegan override

540 views Asked by At

I'm developing a remote desktop control application for iOS (and MacOS through Mac Catalyst) that must be able to capture all keyboard input on the device including the Cmd key (equivalent to Super / Start key on non Mac keyboards) when the app is in the foreground in order to send them to the remote desktop.

I have not yet tried to see if an iOS device with an external keyboard sees the .command key modifier, but when I enabled Mac Catalyst support and installed the app on my Mac and added the following methods to AppDelegate:

    override func pressesBegan(_ presses: Set<UIPress>,
                           with event: UIPressesEvent?) {
    super.pressesBegan(presses, with: event)
    print(presses.first?.key, presses.first?.key?.modifierFlags)
}
override func pressesEnded(_ presses: Set<UIPress>,
                           with event: UIPressesEvent?) {
    super.pressesEnded(presses, with: event)
    print(presses.first?.key, presses.first?.key?.modifierFlags)
}
override func pressesCancelled(_ presses: Set<UIPress>,
                               with event: UIPressesEvent?) {
    super.pressesCancelled(presses, with: event)
    print(presses.first?.key, presses.first?.key?.modifierFlags)
}

I was able to capture pretty much any key combination I try except when the Cmd/Start/Super key is also in the key-combination. When the Cmd key is in the key combination or pressed alone, there is absolutely nothing sent to the app. The event appears to be reserved and consumed by Mac OS X completely.

For completeness to this post, I'd like to add that I tried removing all the menus from the app as well just in case the menu was to blame for consuming the Cmd key events, but nothing changed:

    override func buildMenu(with builder: UIMenuBuilder) {
    if builder.system == .main {
        builder.remove(menu: .edit)
        builder.remove(menu: .format)
        builder.remove(menu: .help)
        builder.remove(menu: .file)
        builder.remove(menu: .window)
        builder.remove(menu: .view)
        let dummyCommand = UICommand(title: "Dummy",
                  action: #selector(dummy),
                  discoverabilityTitle: "dummy")
        let mainMenu = UIMenu(title: "Dummy", image: nil, identifier: UIMenu.Identifier("dummy"), options: .displayInline, children: [dummyCommand])
        builder.replace(menu: .application, with: mainMenu)
    }
}

I've also tried putting the app into fullscreen mode to no avail.

Any other suggestions on how I can capture the .command modifier?

Next I'm going to try capturing input through the AppKit bundle, but that's not ideal.

1

There are 1 answers

1
Disco On

Hopefully you have an answer already, but in case not: You will want to inspect the press with code like below

override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {

    var didHandleEvent : Bool = true
    print("presses began \(presses.count)")
    for press in presses {
        guard let key = press.key else { continue }

        if key.modifierFlags.contains(.command) {
            commandDown = true
            print("commandDown")
        }
     ...

You should also only call super.pressesBegan if you want the system to handle the keypress in addition to your code. In your case, I suspect that you don't want the system to do this, so track what presses you have handled, and only if not handled would you call super. You will also likely want to set your view controller to .becomeFirstResponder() (assuming you are using a UIVIewController )