mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Attempts a full implementation of the joystick manager.
So it currently vends a list of existing joysticks plus their states. More work will be required for a UI — e.g. there is no way to identify one joystick from another — but this'll do for now.
This commit is contained in:
parent
8d18808efe
commit
c05b6397b0
@ -8,8 +8,72 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface CSJoystickManager : NSObject
|
/*!
|
||||||
|
Models a single joystick button.
|
||||||
- (instancetype)init;
|
Buttons have an index and are either currently pressed, or not.
|
||||||
|
*/
|
||||||
|
@interface CSJoystickButton: NSObject
|
||||||
|
@property(nonatomic, readonly) NSInteger index;
|
||||||
|
@property(nonatomic, readonly) bool isPressed;
|
||||||
|
@end
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, CSJoystickAxisType) {
|
||||||
|
CSJoystickAxisTypeX,
|
||||||
|
CSJoystickAxisTypeY,
|
||||||
|
CSJoystickAxisTypeZ,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models a joystick axis.
|
||||||
|
Axes have a nominated type and a continuous value between 0 and 1.
|
||||||
|
*/
|
||||||
|
@interface CSJoystickAxis: NSObject
|
||||||
|
@property(nonatomic, readonly) CSJoystickAxisType type;
|
||||||
|
/// The current position of this axis in the range [0, 1].
|
||||||
|
@property(nonatomic, readonly) float position;
|
||||||
|
@end
|
||||||
|
|
||||||
|
typedef NS_OPTIONS(NSInteger, CSJoystickHatDirection) {
|
||||||
|
CSJoystickHatDirectionUp = 1 << 0,
|
||||||
|
CSJoystickHatDirectionDown = 1 << 1,
|
||||||
|
CSJoystickHatDirectionLeft = 1 << 2,
|
||||||
|
CSJoystickHatDirectionRight = 1 << 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models a joystick hat.
|
||||||
|
A hat is a digital directional input, so e.g. this is how thumbpads are represented.
|
||||||
|
*/
|
||||||
|
@interface CSJoystickHat: NSObject
|
||||||
|
@property(nonatomic, readonly) CSJoystickHatDirection direction;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models a joystick.
|
||||||
|
|
||||||
|
A joystick is a collection of buttons, axes and hats, each of which holds a current
|
||||||
|
state. The holder must use @c update to cause this joystick to read a fresh copy
|
||||||
|
of its state.
|
||||||
|
*/
|
||||||
|
@interface CSJoystick: NSObject
|
||||||
|
@property(nonatomic, readonly) NSArray<CSJoystickButton *> *buttons;
|
||||||
|
@property(nonatomic, readonly) NSArray<CSJoystickAxis *> *axes;
|
||||||
|
@property(nonatomic, readonly) NSArray<CSJoystickHat *> *hats;
|
||||||
|
|
||||||
|
- (void)update;
|
||||||
|
@end
|
||||||
|
|
||||||
|
/*!
|
||||||
|
The joystick manager watches for joystick connections and disconnections and
|
||||||
|
offers a list of joysticks currently attached.
|
||||||
|
|
||||||
|
Be warned: this means using Apple's IOKit directly to watch for Bluetooth and
|
||||||
|
USB HID devices. So to use this code, make sure you have USB and Bluetooth
|
||||||
|
enabled for the app's sandbox.
|
||||||
|
*/
|
||||||
|
@interface CSJoystickManager : NSObject
|
||||||
|
@property(nonatomic, readonly) NSArray<CSJoystick *> *joysticks;
|
||||||
|
|
||||||
|
/// Updates all joysticks.
|
||||||
|
- (void)update;
|
||||||
@end
|
@end
|
||||||
|
@ -11,6 +11,194 @@
|
|||||||
@import IOKit;
|
@import IOKit;
|
||||||
#include <IOKit/hid/IOHIDLib.h>
|
#include <IOKit/hid/IOHIDLib.h>
|
||||||
|
|
||||||
|
#pragma mark - CSJoystickButton
|
||||||
|
|
||||||
|
@implementation CSJoystickButton {
|
||||||
|
IOHIDElementRef _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index {
|
||||||
|
self = [super init];
|
||||||
|
if(self) {
|
||||||
|
_index = index;
|
||||||
|
_element = (IOHIDElementRef)CFRetain(element);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
CFRelease(_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)description {
|
||||||
|
return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IOHIDElementRef)element {
|
||||||
|
return _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setIsPressed:(bool)isPressed {
|
||||||
|
_isPressed = isPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark - CSJoystickAxis
|
||||||
|
|
||||||
|
@implementation CSJoystickAxis {
|
||||||
|
IOHIDElementRef _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type {
|
||||||
|
self = [super init];
|
||||||
|
if(self) {
|
||||||
|
_element = (IOHIDElementRef)CFRetain(element);
|
||||||
|
_type = type;
|
||||||
|
_position = 0.5f;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
CFRelease(_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)description {
|
||||||
|
return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IOHIDElementRef)element {
|
||||||
|
return _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPosition:(float)position {
|
||||||
|
_position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark - CSJoystickHat
|
||||||
|
|
||||||
|
@implementation CSJoystickHat {
|
||||||
|
IOHIDElementRef _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithElement:(IOHIDElementRef)element {
|
||||||
|
self = [super init];
|
||||||
|
if(self) {
|
||||||
|
_element = (IOHIDElementRef)CFRetain(element);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
CFRelease(_element);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)description {
|
||||||
|
return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IOHIDElementRef)element {
|
||||||
|
return _element;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDirection:(CSJoystickHatDirection)direction {
|
||||||
|
_direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark - CSJoystick
|
||||||
|
|
||||||
|
@implementation CSJoystick {
|
||||||
|
IOHIDDeviceRef _device;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons
|
||||||
|
axes:(NSArray<CSJoystickAxis *> *)axes
|
||||||
|
hats:(NSArray<CSJoystickHat *> *)hats
|
||||||
|
device:(IOHIDDeviceRef)device {
|
||||||
|
self = [super init];
|
||||||
|
if(self) {
|
||||||
|
// Sort buttons by index.
|
||||||
|
_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]];
|
||||||
|
|
||||||
|
// Sort axes by enum value.
|
||||||
|
_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]];
|
||||||
|
|
||||||
|
// Hats have no guaranteed ordering.
|
||||||
|
_hats = hats;
|
||||||
|
|
||||||
|
// Keep hold of the device.
|
||||||
|
_device = (IOHIDDeviceRef)CFRetain(device);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
CFRelease(_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)description {
|
||||||
|
return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)update {
|
||||||
|
// Update buttons.
|
||||||
|
for(CSJoystickButton *button in _buttons) {
|
||||||
|
IOHIDValueRef value;
|
||||||
|
if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) {
|
||||||
|
// Some pressure-sensitive buttons return values greater than 1 for hard presses,
|
||||||
|
// but this class rationalised everything to Boolean.
|
||||||
|
button.isPressed = !!IOHIDValueGetIntegerValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update hats.
|
||||||
|
for(CSJoystickHat *hat in _hats) {
|
||||||
|
IOHIDValueRef value;
|
||||||
|
if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) {
|
||||||
|
// Hats report a direction, which is either one of eight or one of four.
|
||||||
|
CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(hat.element);
|
||||||
|
const CFIndex range = 1 + IOHIDElementGetLogicalMax(hat.element) - IOHIDElementGetLogicalMin(hat.element);
|
||||||
|
integerValue *= 8 / range;
|
||||||
|
|
||||||
|
// Map from the HID direction to the bit field.
|
||||||
|
switch(integerValue) {
|
||||||
|
default: hat.direction = 0; break;
|
||||||
|
case 0: hat.direction = CSJoystickHatDirectionUp; break;
|
||||||
|
case 1: hat.direction = CSJoystickHatDirectionUp | CSJoystickHatDirectionRight; break;
|
||||||
|
case 2: hat.direction = CSJoystickHatDirectionRight; break;
|
||||||
|
case 3: hat.direction = CSJoystickHatDirectionRight | CSJoystickHatDirectionDown; break;
|
||||||
|
case 4: hat.direction = CSJoystickHatDirectionDown; break;
|
||||||
|
case 5: hat.direction = CSJoystickHatDirectionDown | CSJoystickHatDirectionLeft; break;
|
||||||
|
case 6: hat.direction = CSJoystickHatDirectionLeft; break;
|
||||||
|
case 7: hat.direction = CSJoystickHatDirectionLeft | CSJoystickHatDirectionUp; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update axes.
|
||||||
|
for(CSJoystickAxis *axis in _axes) {
|
||||||
|
IOHIDValueRef value;
|
||||||
|
if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) {
|
||||||
|
const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element);
|
||||||
|
const CFIndex range = 1 + IOHIDElementGetLogicalMax(axis.element) - IOHIDElementGetLogicalMin(axis.element);
|
||||||
|
axis.position = (float)integerValue / (float)range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IOHIDDeviceRef)device {
|
||||||
|
return _device;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark - CSJoystickManager
|
||||||
|
|
||||||
@interface CSJoystickManager ()
|
@interface CSJoystickManager ()
|
||||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||||
@ -26,13 +214,13 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
|||||||
|
|
||||||
@implementation CSJoystickManager {
|
@implementation CSJoystickManager {
|
||||||
IOHIDManagerRef _hidManager;
|
IOHIDManagerRef _hidManager;
|
||||||
NSMutableSet<NSValue *> *_activeDevices;
|
NSMutableArray<CSJoystick *> *_joysticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if(self) {
|
if(self) {
|
||||||
_activeDevices = [[NSMutableSet alloc] init];
|
_joysticks = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||||
if(!_hidManager) return nil;
|
if(!_hidManager) return nil;
|
||||||
@ -65,14 +253,17 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||||
NSValue *const deviceKey = [NSValue valueWithPointer:device];
|
// Double check this joystick isn't already known.
|
||||||
if([_activeDevices containsObject:deviceKey]) {
|
for(CSJoystick *joystick in _joysticks) {
|
||||||
return;
|
if(joystick.device == device) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[_activeDevices addObject:deviceKey];
|
// Prepare to collate a list of buttons, axes and hats for the new device.
|
||||||
NSLog(@"Matched");
|
NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init];
|
||||||
|
NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init];
|
||||||
|
NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
// Inspect all elements for those that are comprehensible to this code.
|
||||||
const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
|
const CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone);
|
||||||
for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) {
|
for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) {
|
||||||
const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index);
|
const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index);
|
||||||
@ -81,40 +272,62 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
|||||||
const uint32_t usagePage = IOHIDElementGetUsagePage(element);
|
const uint32_t usagePage = IOHIDElementGetUsagePage(element);
|
||||||
if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue;
|
if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue;
|
||||||
|
|
||||||
// Then inspect the usage and type.
|
// Then inspect the type.
|
||||||
const IOHIDElementType type = IOHIDElementGetType(element);
|
switch(IOHIDElementGetType(element)) {
|
||||||
|
|
||||||
// IOHIDElementGetCookie
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
default: break;
|
default: break;
|
||||||
case kIOHIDElementTypeInput_Button:
|
|
||||||
// Add a buton
|
case kIOHIDElementTypeInput_Button: {
|
||||||
break;
|
// Add a button; pretty easy stuff. 'Usage' provides a button index.
|
||||||
|
const uint32_t usage = IOHIDElementGetUsage(element);
|
||||||
|
[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]];
|
||||||
|
} break;
|
||||||
|
|
||||||
case kIOHIDElementTypeInput_Misc:
|
case kIOHIDElementTypeInput_Misc:
|
||||||
case kIOHIDElementTypeInput_Axis: {
|
case kIOHIDElementTypeInput_Axis: {
|
||||||
const uint32_t usage = IOHIDElementGetUsage(element);
|
CSJoystickAxisType axisType;
|
||||||
// Add something depending on usage...
|
switch(IOHIDElementGetUsage(element)) {
|
||||||
|
default: continue;
|
||||||
|
|
||||||
|
// Three analogue axes are implemented here; there are another three sets
|
||||||
|
// of these that could be parsed in the future if interesting.
|
||||||
|
case kHIDUsage_GD_X: axisType = CSJoystickAxisTypeX; break;
|
||||||
|
case kHIDUsage_GD_Y: axisType = CSJoystickAxisTypeY; break;
|
||||||
|
case kHIDUsage_GD_Z: axisType = CSJoystickAxisTypeZ; break;
|
||||||
|
|
||||||
|
// A hatswitch is a multi-directional control all of its own.
|
||||||
|
case kHIDUsage_GD_Hatswitch:
|
||||||
|
[hats addObject:[[CSJoystickHat alloc] initWithElement:element]];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the axis; if it was a hat switch or unrecognised then the code doesn't
|
||||||
|
// reach here.
|
||||||
|
[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]];
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case kIOHIDElementTypeCollection:
|
|
||||||
// TODO: recurse.
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CFRelease(elements);
|
CFRelease(elements);
|
||||||
|
|
||||||
|
// Add this joystick to the list.
|
||||||
|
[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||||
NSValue *const deviceKey = [NSValue valueWithPointer:device];
|
// If this joystick was recorded, remove it.
|
||||||
if(![_activeDevices containsObject:deviceKey]) {
|
for(CSJoystick *joystick in [_joysticks copy]) {
|
||||||
|
if(joystick.device == device) {
|
||||||
|
[_joysticks removeObject:joystick];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[_activeDevices removeObject:deviceKey];
|
- (void)update {
|
||||||
NSLog(@"Removed");
|
[self.joysticks makeObjectsPerformSelector:@selector(update)];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<CSJoystick *> *)joysticks {
|
||||||
|
return [_joysticks copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
Loading…
Reference in New Issue
Block a user