在 GUI 应用程序中使用 runloop 调度 IOHIDManager

Scheduling IOHIDManager with runloop in a GUI App

我正在编写一个应用程序,用于监控 swift 中游戏手柄的输入。 我设法构建了一个具有预期行为的命令行应用程序:

import Foundation
import IOKit.hid

var valueCallback : IOHIDValueCallback = {
    (context, result, sender, value) in
    let element = IOHIDValueGetElement(value)
    print(IOHIDElementGetUsage(element), IOHIDValueGetIntegerValue(value))
}

var attachCallback : IOHIDDeviceCallback = {
    (context, result, sender, device) in 
    IOHIDDeviceOpen(device, IOOptionBits(kIOHIDOptionsTypeSeizeDevice))
    IOHIDDeviceRegisterInputValueCallback(device, valueCallback, context)
    print("Controller attached")
}

var detachCallback : IOHIDDeviceCallback = {
    (context, result, sender, device) in
    print("Controller detached")
}

class HID {
    let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
    let devices = [kIOHIDTransportKey: "Bluetooth"] as CFDictionary
    
    init() { 
        IOHIDManagerSetDeviceMatching(self.manager, self.devices)
        IOHIDManagerRegisterDeviceMatchingCallback(self.manager, attachCallback, nil)
        IOHIDManagerRegisterDeviceRemovalCallback(self.manager, detachCallback, nil)

        IOHIDManagerScheduleWithRunLoop(self.manager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
        IOHIDManagerOpen(self.manager, IOOptionBits(kIOHIDOptionsTypeNone))
    }
}

var hidTest = HID()
CFRunLoopRun()

然后我想在实际的应用程序中使用它,但它无法按预期工作。我对 class 和回调使用了相同的代码,并在 AppDelegate 中尝试了这个:

import Cocoa
import SwiftUI
import AppKit
import IOKit.hid
import Foundation

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let contentView = ContentView()
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.isReleasedWhenClosed = false
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)
        
        let hidTest = HID()
        CFRunLoopRun()
    }
}

编译一切正常,但我无法从游戏手柄获得任何值。我认为问题在于:IOHIDManagerScheduleWithRunLoop(self.manager, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue) 因为管理器可能没有附加到应用程序的 RunLoop。

我需要做些什么才能完成这项工作吗? 提前致谢!

我不是 Swift 专家,但我知道 IOKit 和 运行 循环,所以我会尝试回答。

我认为对 CFRunLoopRun() 的调用可能存在问题。 Cocoa 个应用程序已经 运行 在 运行 循环中,该循环由 NSApplication 隐式创建并 运行。不要显式 运行 运行 循环,只允许 applicationDidFinishLaunching 到 return.

执行此操作时,您可能应该使 HID 对象成为应用程序对象或类似对象的成员,否则它将被销毁。我不确定 IOKit 的 Swift 绑定是否执行任何自动资源销毁;如果是这样,这可能会破坏 HID 管理器对象,您可能希望它在应用程序的生命周期内保持活动状态。