MacでHID準拠USBゲームコントローラの値を取得したい

2017-01-01 15:57  -  2 min read

コントローラがどんな種類の入力をもっているかスキャンできるコードを紹介します。 このプログラムの管理下にあるときは任意のタイミングで入力の状態がどうなっているかもチェックできます。 startメソッド の無限ループはチェックのためにその場におきました。よきに取り除いてください。

Criteriaには検出したいデバイスの条件を設定して下さい。

// HIDDeviceManager
#import <Foundation/Foundation.h>
#import <IOKit/hid/IOHIDLib.h>
#import "HIDDeviceManager.h"

static const int numOfDetection = 2;
static const NSDictionary* criteria = @{@kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop),
                                         @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick)};

static void CFSetApplier(const void *value,void *context)
{
  CFArrayAppendValue((CFMutableArrayRef)context,value);
}

@interface HIDDeviceManager (){}
  @property(nonatomic) IOHIDManagerRef manager;
  @property(nonatomic) IOHIDDeviceRef* devices;
  @property(nonatomic) int deviceNum;
@end

@implementation HIDDeviceManager

- (void) start {
  self.manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);

  IOHIDManagerSetDeviceMatching(self.manager, (__bridge CFDictionaryRef)criteria);
  IOHIDManagerOpen(self.manager, kIOHIDOptionsTypeNone);
  
  CFSetRef copyOfDevices = IOHIDManagerCopyDevices(self.manager);
  CFMutableArrayRef hidDevices = CFArrayCreateMutable(kCFAllocatorDefault, 0 ,&kCFTypeArrayCallBacks);
  CFSetApplyFunction(copyOfDevices, CFSetApplier, (void*)hidDevices);
  
  CFRelease(copyOfDevices);

  IOHIDDeviceRef shoudlManage[numOfDetection];
  int deviceCount = (int)CFArrayGetCount(hidDevices);

  for (int i=0; i<deviceCount && i < numOfDetection; i++) {
    IOHIDDeviceRef hidDevice = (IOHIDDeviceRef)CFArrayGetValueAtIndex(hidDevices, i);
    if (hidDevice == NULL) {
      continue;
    }

    shoudlManage[self.deviceNum++] = hidDevice;
  }
  
  self.devices = shoudlManage;
  CFRelease(hidDevices);
  
  while(1) {
    time_t t0;
    t0=time(NULL);
    while(t0==time(NULL))
    {
    }

    for (int i=0; i<self.deviceNum; i++) {
      CFArrayRef elements = IOHIDDeviceCopyMatchingElements(self.devices[i], NULL, 0);
      int elementCount = (int)CFArrayGetCount(elements);
      
      for (int j=0; j<elementCount; j++) {
        IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, j);
        IOHIDElementType elementType = IOHIDElementGetType(element);
        unsigned int usage = IOHIDElementGetUsage(element);
        unsigned int usagePage = IOHIDElementGetUsagePage(element);
        
        printf("element %d: type: %d usage: %d value: ", j, elementType, usagePage);
    
        switch(elementType) {
          case kIOHIDElementTypeInput_ScanCodes:
          case kIOHIDElementTypeInput_Misc:
          case kIOHIDElementTypeInput_Button:
          case kIOHIDElementTypeInput_Axis:
          {
            IOHIDValueRef valueRef;
            IOHIDDeviceGetValue(self.devices[i], element, &valueRef);
            
            int value = (int)IOHIDValueGetIntegerValue(valueRef);
            printf("%d", value);
          }
          default: {
            printf("\n");
            break;
          }
            
        }
      }

      CFRelease(elements);
    }
  }
}

@end

今回はObjective-Cのコードを紹介しますが,Swift3上のコードでも実現できます。やることは各型のRefという接尾辞やCFReleaseを取り除いたり,引数のコールバックをクロージャあるいは関数のUnsafePointerで渡すといった変更を加えていけばObjective-Cレスでスッキリします。別の実装ではSwiftを使っていますが,今回はUnsafePointer <-> Tの相互変換が面倒くさかったので無理にSwiftを使う必要はないなという判断です。C++の関数を呼び出してるので,こっちの方が相性良さそう。

それにしてもI/OKit面倒臭いですね。

値が変更されたタイミングにその値をコールバックで受け取って処理したいならもっとスッキリとした実装があります。 Mac OS Xで動作するドライバの開発