Cocoa 如何在OS X应用程序中使用Swift收听全球热键?

Cocoa 如何在OS X应用程序中使用Swift收听全球热键?,cocoa,swift,Cocoa,Swift,我试图在我的MacOSX应用程序中使用Swift编写一个全局(系统范围)热键组合的处理程序,但我就是找不到合适的文档。我已经读到,我将不得不在一些遗留的碳API中乱搞,没有更好的办法吗?你能给我看一些概念验证Swift代码吗?提前谢谢 我不相信你今天能百分之百地做到这一点。您需要调用InstallEventHandler()或CGEventTapCreate(),这两者都需要CFunctionPointer,而不能在Swift中创建。您的最佳计划是使用已建立的ObjC解决方案,如和Swift桥接

我试图在我的MacOSX应用程序中使用Swift编写一个全局(系统范围)热键组合的处理程序,但我就是找不到合适的文档。我已经读到,我将不得不在一些遗留的碳API中乱搞,没有更好的办法吗?你能给我看一些概念验证Swift代码吗?提前谢谢

我不相信你今天能百分之百地做到这一点。您需要调用
InstallEventHandler()
CGEventTapCreate()
,这两者都需要
CFunctionPointer
,而不能在Swift中创建。您的最佳计划是使用已建立的ObjC解决方案,如和Swift桥接

您可以尝试使用
NSEvent.addGlobalMonitorForEventsMatchingMask(处理程序:)
,但这只能复制事件。你不能吃掉它们。这意味着热键也将传递给当前活动的应用程序,这可能会导致问题。这里有一个例子,但我推荐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)
    }

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

自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)

设置的快速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)

看看热键库。您可以简单地使用迦太基将其实现到您自己的应用程序中。

如果你的应用程序有一个
菜单,有一个非常简单的解决方法:

  • 添加一个新的
    MenuItem
    (可以称之为“热键的虚拟”)
  • 在属性检查器中,方便地在
    等效键
    字段中输入热键
  • 将隐藏时允许的
    、启用的
    和隐藏时的
    设置为true
  • 将其与IBAction链接,以执行热键应该执行的任何操作

完成了

以下代码适用于我的Swift 5.0.1。该解决方案结合了Charlie Monroe接受的答案和Rob Napier使用DDHotKey的建议

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

eventSpec.eventKind = kEventHotKeyReleased;

如果要同时处理已按下和已释放的事件,只需添加第二个
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)
  }
}
进口碳
扩展字符串{
///这将字符串转换为UInt作为fourCharCode
公共变量fourCharCodeValue:Int{
变量结果:Int=0
如果let data=self.data(使用:String.Encoding.macOSRoman){
data.withUnsafeBytes({(rawBytes)在
let bytes=rawBytes.bindMemory(to:UInt8.self)
对于0中的i..0){
newFlags |=控制键
}
if((flags&NSEvent.ModifierFlags.command.rawValue)>0){
newFlags |=cmdKey
}
if((flags&NSEvent.ModifierFlags.shift.rawValue)>0){
newFlags |=移位键;
}
if((flags&NSEvent.ModifierFlags.option.rawValue)>0){
newFlags |=选项键
}
if((flags&NSEvent.ModifierFlags.capsLock.rawValue)>0){
newFlags |=字母锁
}
返回UInt32(新标志);
}
静态func寄存器(){
var hotKeyRef:EventHotKeyRef?
让修饰符标记:UInt32=
GetCarbonFlagsFromCoaFlags(cocoaFlags:NSEvent.ModifierFlags.command)
设keyCode=kVK\u ANSI\u R
var gMyHotKeyID=EventHotKeyID()
gMyHotKeyID.id=UInt32(keyCode)
//不知道“swat”和“htk1”做什么。
gMyHotKeyID.signature=OSType(“swat”.fourCharCodeValue)
//gMyHotKeyID.signature=OSType(“htk1”.fourCharCodeValue)
var eventType=EventTypeSpec()
eventType.eventClass=OSType(kEventClassKeyboard)
eventType.eventKind=OSType(kEventHotKeyReleased)
//安装处理程序。
InstallEventHandler(GetApplicationEventTarget(){
(下一个文件夹、事件、用户数据)->OSStatus in
//var hkCom=EventHotKeyID()
//GetEventParameter(事件,
//EventParamName(kEventParamDirectObject),
//EventParamType(类型EventHotKeyId),
//零,
//MemoryLayout.size,
//零,
//(香港电讯)
NSLog(“命令+R已发布!”)
返回noErr
///检查hkCom是否确实存在您的热键ID并处理它。
},1,&事件类型,无,无)
//注册热键。
let status=注册EventThotKey(UInt32(keyCode),
修饰符标记,
gMyHotKeyID,
GetApplicationEventTarget(),
0,
&hotKeyRef)
断言(状态==noErr)
}
}

您到底试过什么?热键是什么意思?您试过NSEvent global monitor或CGEventTap吗注:碳基热键API不需要访问权限(不能在Mac App Store上),虽然这确实更容易实现。有一些应用程序,如CopyLess 2,可以监听按键,不需要辅助功能,你知道吗?也许商店里的应用程序不需要它?在
Swift 4
中不起作用,如果没有更详细的解释,就不可能实现。在Swift 4中效果很好,我知道转换我的项目