mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-13 22:32:03 +00:00
Merge pull request #500 from TomHarte/MacJoysticks
Implements initial joystick support for the Mac
This commit is contained in:
commit
6a73fe7d65
@ -128,6 +128,24 @@ class Joystick {
|
||||
set_input(input, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the number of input fire buttons.
|
||||
|
||||
This is cached by default, but it's virtual so overridable.
|
||||
*/
|
||||
virtual int get_number_of_fire_buttons() {
|
||||
if(number_of_buttons_ >= 0) return number_of_buttons_;
|
||||
|
||||
number_of_buttons_ = 0;
|
||||
for(const auto &input: get_inputs()) {
|
||||
if(input.type == Input::Type::Fire) ++number_of_buttons_;
|
||||
}
|
||||
return number_of_buttons_;
|
||||
}
|
||||
|
||||
private:
|
||||
int number_of_buttons_ = -1;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -605,6 +605,7 @@
|
||||
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
|
||||
4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
|
||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
|
||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||
4BC39568208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
4BC39569208EE6CF0044766B /* DiskIICard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC39566208EE6CF0044766B /* DiskIICard.cpp */; };
|
||||
@ -1345,6 +1346,8 @@
|
||||
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTTypes.hpp; sourceTree = "<group>"; };
|
||||
4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ZX8081.cpp; path = Parsers/ZX8081.cpp; sourceTree = "<group>"; };
|
||||
4BBFBB6B1EE8401E00C01E7A /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ZX8081.hpp; path = Parsers/ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSJoystickManager.m; sourceTree = "<group>"; };
|
||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
|
||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
||||
4BC39565208EDFCE0044766B /* Card.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Card.hpp; sourceTree = "<group>"; };
|
||||
4BC39566208EE6CF0044766B /* DiskIICard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DiskIICard.cpp; sourceTree = "<group>"; };
|
||||
@ -2760,6 +2763,7 @@
|
||||
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
@ -2916,6 +2920,15 @@
|
||||
path = Internals;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */,
|
||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */,
|
||||
);
|
||||
path = "Joystick Manager";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BC3B74C1CD194CC00F86E85 /* Shaders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -3824,6 +3837,7 @@
|
||||
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
|
||||
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */,
|
||||
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */,
|
||||
4B89451C201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4B302184208A550100773308 /* DiskII.cpp in Sources */,
|
||||
|
@ -4,6 +4,10 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSAudioQueue.h"
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
#include "KeyCodes.h"
|
||||
|
@ -9,4 +9,5 @@
|
||||
import Cocoa
|
||||
|
||||
class DocumentController: NSDocumentController {
|
||||
let joystickManager = CSJoystickManager()
|
||||
}
|
||||
|
@ -229,6 +229,13 @@ class MachineDocument:
|
||||
func windowDidResignKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.clearAllKeys()
|
||||
machine.joystickManager = nil
|
||||
}
|
||||
}
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) {
|
||||
if let machine = self.machine {
|
||||
machine.joystickManager = (DocumentController.shared as! DocumentController).joystickManager
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,80 @@
|
||||
//
|
||||
// CSJoystickManager.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/07/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/*!
|
||||
Models a single joystick button.
|
||||
Buttons have an index and are either currently pressed, or not.
|
||||
*/
|
||||
@interface CSJoystickButton: NSObject
|
||||
/// The button index. By convention the USB spec defines the first button as number 1.
|
||||
@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
|
333
OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
Normal file
333
OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m
Normal file
@ -0,0 +1,333 @@
|
||||
//
|
||||
// CSJoystickManager.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/07/2018.
|
||||
// Copyright © 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
@import IOKit;
|
||||
#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 ()
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
@end
|
||||
|
||||
static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
|
||||
[(__bridge CSJoystickManager *)context deviceMatched:device result:result sender:sender];
|
||||
}
|
||||
|
||||
static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
|
||||
[(__bridge CSJoystickManager *)context deviceRemoved:device result:result sender:sender];
|
||||
}
|
||||
|
||||
@implementation CSJoystickManager {
|
||||
IOHIDManagerRef _hidManager;
|
||||
NSMutableArray<CSJoystick *> *_joysticks;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_joysticks = [[NSMutableArray alloc] init];
|
||||
|
||||
_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||
if(!_hidManager) return nil;
|
||||
|
||||
NSArray<NSDictionary<NSString *, NSNumber *> *> *const multiple = @[
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_Joystick) },
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_GamePad) },
|
||||
@{ @kIOHIDDeviceUsagePageKey: @(kHIDPage_GenericDesktop), @kIOHIDDeviceUsageKey: @(kHIDUsage_GD_MultiAxisController) },
|
||||
];
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)multiple);
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self);
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self);
|
||||
IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
|
||||
if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||
NSLog(@"Failed to open HID manager");
|
||||
// something
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone);
|
||||
CFRelease(_hidManager);
|
||||
}
|
||||
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||
// Double check this joystick isn't already known.
|
||||
for(CSJoystick *joystick in _joysticks) {
|
||||
if(joystick.device == device) return;
|
||||
}
|
||||
|
||||
// Prepare to collate a list of buttons, axes and hats for the new device.
|
||||
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);
|
||||
for(CFIndex index = 0; index < CFArrayGetCount(elements); ++index) {
|
||||
const IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, index);
|
||||
|
||||
// Check that this element is either on the generic desktop page or else is a button.
|
||||
const uint32_t usagePage = IOHIDElementGetUsagePage(element);
|
||||
if(usagePage != kHIDPage_GenericDesktop && usagePage != kHIDPage_Button) continue;
|
||||
|
||||
// Then inspect the type.
|
||||
switch(IOHIDElementGetType(element)) {
|
||||
default: break;
|
||||
|
||||
case kIOHIDElementTypeInput_Button: {
|
||||
// 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_Axis: {
|
||||
CSJoystickAxisType axisType;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// If this joystick was recorded, remove it.
|
||||
for(CSJoystick *joystick in [_joysticks copy]) {
|
||||
if(joystick.device == device) {
|
||||
[_joysticks removeObject:joystick];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
[self.joysticks makeObjectsPerformSelector:@selector(update)];
|
||||
}
|
||||
|
||||
- (NSArray<CSJoystick *> *)joysticks {
|
||||
return [_joysticks copy];
|
||||
}
|
||||
|
||||
@end
|
@ -12,6 +12,7 @@
|
||||
#import "CSFastLoading.h"
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSStaticAnalyser.h"
|
||||
#import "CSJoystickManager.h"
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
@ -74,6 +75,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@property (nonatomic, readonly) BOOL hasKeyboard;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||
@property (nonatomic, nullable) CSJoystickManager *joystickManager;
|
||||
|
||||
// LED list.
|
||||
@property (nonatomic, readonly, nonnull) NSArray<NSString *> *leds;
|
||||
|
@ -75,7 +75,9 @@ struct ActivityObserver: public Activity::Observer {
|
||||
|
||||
CSStaticAnalyser *_analyser;
|
||||
std::unique_ptr<Machine::DynamicMachine> _machine;
|
||||
JoystickMachine::Machine *_joystickMachine;
|
||||
|
||||
CSJoystickManager *_joystickManager;
|
||||
std::bitset<65536> _depressedKeys;
|
||||
NSMutableArray<NSString *> *_leds;
|
||||
}
|
||||
@ -102,6 +104,8 @@ struct ActivityObserver: public Activity::Observer {
|
||||
|
||||
_speakerDelegate.machine = self;
|
||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||
|
||||
_joystickMachine = _machine->joystick_machine();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -162,6 +166,54 @@ struct ActivityObserver: public Activity::Observer {
|
||||
|
||||
- (void)runForInterval:(NSTimeInterval)interval {
|
||||
@synchronized(self) {
|
||||
if(_joystickMachine && _joystickManager) {
|
||||
[_joystickManager update];
|
||||
|
||||
// TODO: configurable mapping from physical joypad inputs to machine inputs.
|
||||
// Until then, apply a default mapping.
|
||||
|
||||
size_t c = 0;
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
|
||||
for(CSJoystick *joystick in _joystickManager.joysticks) {
|
||||
size_t target = c % machine_joysticks.size();
|
||||
++++c;
|
||||
|
||||
// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
|
||||
// unless the user seems to be using a hat.
|
||||
// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
|
||||
if(!joystick.hats.count || !joystick.hats[0].direction) {
|
||||
if(joystick.axes.count > 0) {
|
||||
const float x_axis = joystick.axes[0].position;
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
|
||||
}
|
||||
if(joystick.axes.count > 1) {
|
||||
const float y_axis = joystick.axes[1].position;
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
|
||||
}
|
||||
}
|
||||
|
||||
// Forward hats as directions; hats always override analogue inputs.
|
||||
for(CSJoystickHat *hat in joystick.hats) {
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
|
||||
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
|
||||
}
|
||||
|
||||
// Forward all fire buttons, mapping as a function of index.
|
||||
if(machine_joysticks[target]->get_number_of_fire_buttons()) {
|
||||
std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
|
||||
for(CSJoystickButton *button in joystick.buttons) {
|
||||
if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
|
||||
}
|
||||
for(size_t index = 0; index < button_states.size(); ++index) {
|
||||
machine_joysticks[target]->set_input(
|
||||
Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
|
||||
button_states[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_machine->crt_machine()->run_for(interval);
|
||||
}
|
||||
}
|
||||
@ -198,6 +250,18 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setJoystickManager:(CSJoystickManager *)joystickManager {
|
||||
@synchronized(self) {
|
||||
_joystickManager = joystickManager;
|
||||
if(_joystickMachine) {
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> &machine_joysticks = _joystickMachine->get_joysticks();
|
||||
for(const auto &joystick: machine_joysticks) {
|
||||
joystick->reset_all_inputs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
||||
auto keyboard_machine = _machine->keyboard_machine();
|
||||
if(self.inputMode == CSMachineKeyboardInputModeKeyboard && keyboard_machine) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user