Подтвердить что ты не робот

Как слушать глобальные горячие клавиши с Swift в приложении OS X?

Я пытаюсь иметь обработчик в своем приложении Mac OS X, написанном в Swift, для глобальной (общесистемной) комбинации горячих клавиш, но я просто не могу найти для этого подходящую документацию. Я читал, что мне нужно будет возиться в каком-то унаследованном Carbon API, нет ли лучшего способа? Можете ли вы показать мне некоторые доказательства концепции Swift-кода? Спасибо заранее!

4b9b3361

Ответ 1

Начиная с 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)

Ответ 2

Я не верю, что вы можете сделать это сегодня в 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)
    }

Это также требует, чтобы службы доступности были одобрены для этого приложения. Он также не фиксирует события, отправленные в ваше собственное приложение, поэтому вам нужно либо захватить их с цепочкой ответчиков, либо использовать addLocalMointorForEventsMatchingMask(handler:), чтобы добавить локальный обработчик.

Ответ 3

Быстрое обновление 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)

Ответ 4

Взгляните на библиотеку HotKey. Вы можете просто использовать Карфаген, чтобы внедрить его в свое приложение. Библиотека HotKey

Ответ 5

есть довольно хакерский, но довольно простой обходной путь, если у вашего приложения есть Menu:

  • добавить новый MenuItem (возможно, назвать его что-то вроде "Dummy for Hotkey")
  • в инспекторе атрибутов введите удобную горячую клавишу в поле " Key Equivalent
  • установите для параметра Allowed when Hidden, Enabled и Hidden значение true
  • свяжите его с IBAction, чтобы сделать то, что должна делать ваша горячая клавиша

сделанный!

Ответ 6

Следующий код работает у меня для Swift 5.0.1. Это решение представляет собой сочетание решения из принятого ответа Чарли Монро и рекомендации Роба Нейпира использовать DDHotKey.

DDHotKey, кажется, работает из коробки, но у него было одно ограничение, которое мне пришлось изменение: eventKind жестко запрограммирован на kEventHotKeyReleased, в то время как мне были нужны оба типа событий: kEventHotKeyPressed и kEventHotKeyReleased.

eventSpec.eventKind = kEventHotKeyReleased;

Если вы хотите обрабатывать события Pressed и Released, просто добавьте второй вызов InstallEventHandler, который регистрирует другой тип события.

Это полный пример кода, который регистрирует клавишу "Command + R" для типа kEventHotKeyReleased.

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