如何在OS X应用程序中使用Swift监听全局热键?

Csa*_*ona 41 cocoa swift

我正在尝试在我的Mac OS X应用程序中使用Swift编写一个处理程序,用于全局(系统范围)热键组合,但我找不到适当的文档.我已经读过,我必须在一些传统的Carbon API中乱用它,有没有更好的方法?你能告诉我一些Swift代码的概念证明吗?提前致谢!

Cha*_*roe 12

从Swift 2.0开始,您现在可以将函数指针传递给C API.

    var gMyHotKeyID = EventHotKeyID()
    gMyHotKeyID.signature = OSType("swat".fourCharCodeValue)
    gMyHotKeyID.id = UInt32(keyCode)

    var eventType = EventTypeSpec()
    eventType.eventClass = OSType(kEventClassKeyboard)
    eventType.eventKind = OSType(kEventHotKeyPressed)

    // Install handler.
    InstallEventHandler(GetApplicationEventTarget(), {(nextHanlder, theEvent, userData) -> OSStatus in
            var hkCom = EventHotKeyID()
            GetEventParameter(theEvent, EventParamName(kEventParamDirectObject), EventParamType(typeEventHotKeyID), nil, sizeof(EventHotKeyID), nil, &hkCom)

            /// Check that hkCom in indeed your hotkey ID and handle it.
        }, 1, &eventType, nil, nil)

    // Register hotkey.
    let status = RegisterEventHotKey(UInt32(keyCode), UInt32(modifierKeys), gMyHotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef)
Run Code Online (Sandbox Code Playgroud)

  • 在"Swift 4"中不起作用,如果没有更详细的解释,就无法实现. (3认同)
  • 只有良好的共鸣,查理!如果您也可以分享您的 Swift 4 解决方案,那就太好了。您发布的解决方案不完整,这使初学者难以适应。有必要说的一件事是导入 `Carbon` 是必要的。我面临的下一个问题是 `String` 没有 `.fourCharCodeValue` 属性。如果你能提供一个关于 `keyCode` 和 `modifierKeys` 的例子,那就太好了。 (3认同)
  • @ixany - 抱歉,如果这听起来充满敌意 - 根本不是故意的。`fourCharCodeValue` 将字符串转换为 `Int` - 它在今天被大量使用 - 4 个 ASCII 字符转换为 32 位整数 - 它比随机整数更容易在转储中识别。您可以在这里找到我的实现:https://github.com/charlieMonroe/XUCore/blob/master/XUCore/additions/StringExtensions.swift - 我目前正在从“SRRecorderControl”检索“keyCode”:https://gist。 github.com/charlieMonroe/0923985c704695b252ed9bb879e7f99d (3认同)
  • 在Swift 4中效果很好,我已经将项目转换为Swift 4,并且效果很好。作为开发人员,您应该知道“无效”是对问题的最坏描述-“无效”是什么意思?它不编译吗?您遇到什么编译错误?确实没有太多要解释的内容-每个使用的功能都由Apple记录。 (2认同)

Rob*_*ier 10

我不相信你今天可以在100%Swift中做到这一点.你需要调用InstallEventHandler()或者CGEventTapCreate(),这两个都需要一个CFunctionPointer,不能在Swift中创建.您最好的计划是使用已建立的ObjC解决方案,如DDHotKey和桥接Swift.

您可以尝试使用NSEvent.addGlobalMonitorForEventsMatchingMask(handler:),但这只会复制事件.你不能消耗它们.这意味着热键也将传递给当前活动的应用程序,这可能会导致问题.这是一个例子,但我推荐ObjC方法; 它几乎肯定会更好地工作.

let keycode = UInt16(kVK_ANSI_X)
let keymask: NSEventModifierFlags = .CommandKeyMask | .AlternateKeyMask | .ControlKeyMask

func handler(event: NSEvent!) {
    if event.keyCode == self.keycode &&
        event.modifierFlags & self.keymask == self.keymask {
            println("PRESSED")
    }
}

// ... to set it up ...
    let options = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionaryRef
    let trusted = AXIsProcessTrustedWithOptions(options)
    if (trusted) {
        NSEvent.addGlobalMonitorForEventsMatchingMask(.KeyDownMask, handler: self.handler)
    }
Run Code Online (Sandbox Code Playgroud)

这还要求为此应用程序批准可访问性服务.它也不会捕获发送到您自己的应用程序的事件,因此您必须使用响应程序链捕获它们,我们使用它addLocalMointorForEventsMatchingMask(handler:)来添加本地处理程序.


Teo*_*ori 8

设置的快速Swift 3更新:

    let opts = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionary

    guard AXIsProcessTrustedWithOptions(opts) == true else { return }

    NSEvent.addGlobalMonitorForEvents(matching: .keyDown, handler: self.handler)
Run Code Online (Sandbox Code Playgroud)


Sin*_*hus 6

我维护这个 Swift 包,它可以轻松地将全局键盘快捷键添加到您的应用程序中,也可以让用户设置自己的键盘快捷键。

\n
import SwiftUI\nimport KeyboardShortcuts\n\n// Declare the shortcut for strongly-typed access.\nextension KeyboardShortcuts.Name {\n    static let toggleUnicornMode = Self("toggleUnicornMode")\n}\n\n@main\nstruct YourApp: App {\n    @StateObject private var appState = AppState()\n\n    var body: some Scene {\n        WindowGroup {\n            // \xe2\x80\xa6\n        }\n        Settings {\n            SettingsScreen()\n        }\n    }\n}\n\n@MainActor\nfinal class AppState: ObservableObject {\n    init() {\n        // Register the listener.\n        KeyboardShortcuts.onKeyUp(for: .toggleUnicornMode) { [self] in\n            isUnicornMode.toggle()\n        }\n    }\n}\n\n// Present a view where the user can set the shortcut they want.\nstruct SettingsScreen: View {\n    var body: some View {\n        Form {\n            HStack(alignment: .firstTextBaseline) {\n                Text("Toggle Unicorn Mode:")\n                KeyboardShortcuts.Recorder(for: .toggleUnicornMode)\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

本例中使用了 SwiftUI,但它也支持 Cocoa。

\n


Sta*_*ich 5

以下代码适用于 Swift 5.0.1。此解决方案是 Charlie Monroe 接受的答案中的解决方案和 Rob Napier 建议使用 DDHotKey 的组合。

DDHotKey 似乎开箱即用,但它有一个我必须更改的限制:eventKind 是硬编码的,kEventHotKeyReleased而我同时需要kEventHotKeyPressedkEventHotKeyReleased事件类型。

eventSpec.eventKind = kEventHotKeyReleased;
Run Code Online (Sandbox Code Playgroud)

如果您想同时处理 Pressed 和 Released 事件,只需添加第二个InstallEventHandler调用来注册其他事件类型。

这是为该kEventHotKeyReleased类型注册“Command + R”键的代码的完整示例。

import Carbon

extension String {
  /// This converts string to UInt as a fourCharCode
  public var fourCharCodeValue: Int {
    var result: Int = 0
    if let data = self.data(using: String.Encoding.macOSRoman) {
      data.withUnsafeBytes({ (rawBytes) in
        let bytes = rawBytes.bindMemory(to: UInt8.self)
        for i in 0 ..< data.count {
          result = result << 8 + Int(bytes[i])
        }
      })
    }
    return result
  }
}

class HotkeySolution {
  static
  func getCarbonFlagsFromCocoaFlags(cocoaFlags: NSEvent.ModifierFlags) -> UInt32 {
    let flags = cocoaFlags.rawValue
    var newFlags: Int = 0

    if ((flags & NSEvent.ModifierFlags.control.rawValue) > 0) {
      newFlags |= controlKey
    }

    if ((flags & NSEvent.ModifierFlags.command.rawValue) > 0) {
      newFlags |= cmdKey
    }

    if ((flags & NSEvent.ModifierFlags.shift.rawValue) > 0) {
      newFlags |= shiftKey;
    }

    if ((flags & NSEvent.ModifierFlags.option.rawValue) > 0) {
      newFlags |= optionKey
    }

    if ((flags & NSEvent.ModifierFlags.capsLock.rawValue) > 0) {
      newFlags |= alphaLock
    }

    return UInt32(newFlags);
  }

  static func register() {
    var hotKeyRef: EventHotKeyRef?
    let modifierFlags: UInt32 =
      getCarbonFlagsFromCocoaFlags(cocoaFlags: NSEvent.ModifierFlags.command)

    let keyCode = kVK_ANSI_R
    var gMyHotKeyID = EventHotKeyID()

    gMyHotKeyID.id = UInt32(keyCode)

    // Not sure what "swat" vs "htk1" do.
    gMyHotKeyID.signature = OSType("swat".fourCharCodeValue)
    // gMyHotKeyID.signature = OSType("htk1".fourCharCodeValue)

    var eventType = EventTypeSpec()
    eventType.eventClass = OSType(kEventClassKeyboard)
    eventType.eventKind = OSType(kEventHotKeyReleased)

    // Install handler.
    InstallEventHandler(GetApplicationEventTarget(), {
      (nextHanlder, theEvent, userData) -> OSStatus in
      // var hkCom = EventHotKeyID()

      // GetEventParameter(theEvent,
      //                   EventParamName(kEventParamDirectObject),
      //                   EventParamType(typeEventHotKeyID),
      //                   nil,
      //                   MemoryLayout<EventHotKeyID>.size,
      //                   nil,
      //                   &hkCom)

      NSLog("Command + R Released!")

      return noErr
      /// Check that hkCom in indeed your hotkey ID and handle it.
    }, 1, &eventType, nil, nil)

    // Register hotkey.
    let status = RegisterEventHotKey(UInt32(keyCode),
                                     modifierFlags,
                                     gMyHotKeyID,
                                     GetApplicationEventTarget(),
                                     0,
                                     &hotKeyRef)
    assert(status == noErr)
  }
}
Run Code Online (Sandbox Code Playgroud)