From a38d964f62efce8cb5e6d5f09a7d8e55b43c9f86 Mon Sep 17 00:00:00 2001 From: "C.W. Betts" Date: Sun, 13 Dec 2020 11:23:33 -0700 Subject: [PATCH 1/5] Initial GameController joystick support. --- .../Joystick Manager/CSJoystickManager.m | 265 ++++++++++++++++-- 1 file changed, 248 insertions(+), 17 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m index 843edf9a3..3c39b1364 100644 --- a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m +++ b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m @@ -10,17 +10,35 @@ @import IOKit; #include +@import GameController; #pragma mark - CSJoystickButton @implementation CSJoystickButton { + @package + bool _isPressed; +} + +- (instancetype)initWithIndex:(NSInteger)index { + if (self = [super init]) { + _index = index; + } + return self; +} + +@end + +@interface CSIOJoystickButton: CSJoystickButton + +@end + +@implementation CSIOJoystickButton { IOHIDElementRef _element; } - (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index { - self = [super init]; + self = [super initWithIndex:index]; if(self) { - _index = index; _element = (IOHIDElementRef)CFRetain(element); } return self; @@ -31,7 +49,7 @@ } - (NSString *)description { - return [NSString stringWithFormat:@"; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; + return [NSString stringWithFormat:@"; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; } - (IOHIDElementRef)element { @@ -47,14 +65,33 @@ #pragma mark - CSJoystickAxis @implementation CSJoystickAxis { + @package + float _position; +} + +- (instancetype)initWithType:(CSJoystickAxisType)type +{ + if (self = [super init]) { + _type = type; + } + return self; +} + +@end + +@interface CSIOJoystickAxis: CSJoystickAxis + + +@end + +@implementation CSIOJoystickAxis { IOHIDElementRef _element; } - (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type { - self = [super init]; + self = [super initWithType:type]; if(self) { _element = (IOHIDElementRef)CFRetain(element); - _type = type; _position = 0.5f; } return self; @@ -65,7 +102,7 @@ } - (NSString *)description { - return [NSString stringWithFormat:@"; type %d, value %0.2f", self, (int)self.type, self.position]; + return [NSString stringWithFormat:@"; type %d, value %0.2f", self, (int)self.type, self.position]; } - (IOHIDElementRef)element { @@ -81,6 +118,18 @@ #pragma mark - CSJoystickHat @implementation CSJoystickHat { + @package + CSJoystickHatDirection _direction; +} + +@end + +@interface CSIOJoystickHat: CSJoystickHat + +@end + + +@implementation CSIOJoystickHat { IOHIDElementRef _element; } @@ -97,7 +146,7 @@ } - (NSString *)description { - return [NSString stringWithFormat:@"; direction %ld", self, (long)self.direction]; + return [NSString stringWithFormat:@"; direction %ld", self, (long)self.direction]; } - (IOHIDElementRef)element { @@ -113,6 +162,24 @@ #pragma mark - CSJoystick @implementation CSJoystick { + @package + NSArray *_buttons; + NSArray *_axes; + NSArray *_hats; +} + +- (void)update +{ + //subclass! +} + +@end + +@interface CSIOJoystick: CSJoystick + +@end + +@implementation CSIOJoystick { IOHIDDeviceRef _device; } @@ -142,12 +209,12 @@ } - (NSString *)description { - return [NSString stringWithFormat:@"; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats]; + return [NSString stringWithFormat:@"; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats]; } - (void)update { // Update buttons. - for(CSJoystickButton *button in _buttons) { + for(CSIOJoystickButton *button in _buttons) { IOHIDValueRef value; if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) { // Some pressure-sensitive buttons return values greater than 1 for hard presses, @@ -157,7 +224,7 @@ } // Update hats. - for(CSJoystickHat *hat in _hats) { + for(CSIOJoystickHat *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. @@ -181,7 +248,7 @@ } // Update axes. - for(CSJoystickAxis *axis in _axes) { + for(CSIOJoystickAxis *axis in _axes) { IOHIDValueRef value; if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) { const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element); @@ -197,11 +264,120 @@ @end +#pragma mark - GameController subclasses + +API_AVAILABLE(macos(11.0)) +@interface CSGCJoystickHat: CSJoystickHat +@property (readonly, strong) GCDeviceDirectionPad *directionPad; +@end + +API_AVAILABLE(macos(11.0)) +@interface CSGCJoystickAxis: CSJoystickAxis +@property (readonly, strong) GCDeviceAxisInput *axis; +@end + +API_AVAILABLE(macos(11.0)) +@interface CSGCJoystickButton: CSJoystickButton +@property (readonly, strong) GCDeviceButtonInput *button; +@end + +API_AVAILABLE(macos(11.0)) +@interface CSGCJoystick: CSJoystick +@property (readonly, strong) GCController *device; +@end + +@implementation CSGCJoystickHat + +- (instancetype)initWithDirectionPad:(GCDeviceDirectionPad*)dPad { + if (self = [super init]) { + _directionPad = dPad; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"; direction %ld", self, (long)self.direction]; +} + +- (void)setDirection:(CSJoystickHatDirection)direction { + _direction = direction; +} + +@end + +@implementation CSGCJoystickAxis + +- (instancetype)initWithAxis:(GCDeviceAxisInput*)element type:(CSJoystickAxisType)type { + self = [super initWithType:type]; + if(self) { + _axis = element; + _position = 0.5f; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"; type %d, value %0.2f", self, (int)self.type, self.position]; +} + +@end + +@implementation CSGCJoystickButton + +- (instancetype)initWithButton:(GCDeviceButtonInput*)element index:(NSInteger)index { + self = [super initWithIndex:index]; + if(self) { + _button = element; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; +} + +@end + +@implementation CSGCJoystick + +- (instancetype)initWithButtons:(NSArray *)buttons + axes:(NSArray *)axes + hats:(NSArray *)hats + device:(GCController*)device { + if (self = [super init]) { + // 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 = device; + } + return self; +} + + +-(void)update { + // TODO: implement +} + +- (NSString *)description { + return [NSString stringWithFormat:@"; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats]; +} + +@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; +- (void)controllerDidConnect:(NSNotification *)note API_AVAILABLE(macos(11.0)); +- (void)controllerDidDisconnect:(NSNotification *)note API_AVAILABLE(macos(11.0)); @end static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) { @@ -235,6 +411,10 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self); IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self); IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + if (@available(macOS 11.0, *)) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil]; + } if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { NSLog(@"Failed to open HID manager"); @@ -246,15 +426,63 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev return self; } +- (void)controllerDidConnect:(NSNotification *)note { + GCController *controller = note.object; + + // Double check this joystick isn't already known. + for(CSGCJoystick *joystick in _joysticks) { + if (![joystick isKindOfClass:[CSGCJoystick class]]) { + continue; + } + if(joystick.device == controller) return; + } + + // Prepare to collate a list of buttons, axes and hats for the new device. + NSMutableArray *buttons = [[NSMutableArray alloc] init]; + NSMutableArray *axes = [[NSMutableArray alloc] init]; + NSMutableArray *hats = [[NSMutableArray alloc] init]; + + //TODO: write! + + // Add this joystick to the list. + [_joysticks addObject:[[CSGCJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:controller]]; +} + +- (void)controllerDidDisconnect:(NSNotification *)note { + GCController *controller = note.object; + + // If this joystick was recorded, remove it. + for(CSGCJoystick *joystick in [_joysticks copy]) { + if (![joystick isKindOfClass:[CSGCJoystick class]]) { + continue; + } + if(joystick.device == controller) { + [_joysticks removeObject:joystick]; + return; + } + } +} + - (void)dealloc { IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); CFRelease(_hidManager); + if (@available(macOS 11.0, *)) { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + } } - (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender { + if (@available(macOS 11.0, *)) { + if ([GCController supportsHIDDevice:device]) { + return; + } + } // Double check this joystick isn't already known. - for(CSJoystick *joystick in _joysticks) { + for(CSIOJoystick *joystick in _joysticks) { + if (![joystick isKindOfClass:[CSIOJoystick class]]) { + continue; + } if(joystick.device == device) return; } @@ -279,7 +507,7 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev 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]]; + [buttons addObject:[[CSIOJoystickButton alloc] initWithElement:element index:usage]]; } break; case kIOHIDElementTypeInput_Misc: @@ -296,25 +524,28 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev // A hatswitch is a multi-directional control all of its own. case kHIDUsage_GD_Hatswitch: - [hats addObject:[[CSJoystickHat alloc] initWithElement:element]]; + [hats addObject:[[CSIOJoystickHat 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]]; + [axes addObject:[[CSIOJoystickAxis 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]]; + [_joysticks addObject:[[CSIOJoystick 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]) { + for(CSIOJoystick *joystick in [_joysticks copy]) { + if (![joystick isKindOfClass:[CSIOJoystick class]]) { + continue; + } if(joystick.device == device) { [_joysticks removeObject:joystick]; return; From 86283b18153b4cb2d1c0b8232e1eb2694dedb189 Mon Sep 17 00:00:00 2001 From: "C.W. Betts" Date: Mon, 14 Dec 2020 01:14:40 -0700 Subject: [PATCH 2/5] Actually write the setup code. --- .../Joystick Manager/CSJoystickManager.m | 59 ++++++++++++++++++- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m index 3c39b1364..f863feac6 100644 --- a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m +++ b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m @@ -320,6 +320,10 @@ API_AVAILABLE(macos(11.0)) return [NSString stringWithFormat:@"; type %d, value %0.2f", self, (int)self.type, self.position]; } +- (void)setPosition:(float)position { + _position = position; +} + @end @implementation CSGCJoystickButton @@ -336,6 +340,10 @@ API_AVAILABLE(macos(11.0)) return [NSString stringWithFormat:@"; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"]; } +- (void)setIsPressed:(bool)isPressed { + _isPressed = isPressed; +} + @end @implementation CSGCJoystick @@ -360,9 +368,38 @@ API_AVAILABLE(macos(11.0)) return self; } - -(void)update { - // TODO: implement + // Update buttons. + for(CSGCJoystickButton *button in _buttons) { + // This assumes that the values provided by GCDeviceButtonInput are + // digital. This might not always be the case. + button.isPressed = button.button.pressed; + } + for(CSGCJoystickAxis *axis in _axes) { + float val = axis.axis.value; + val += 1; + val /= 2; + axis.position = val; + } + for(CSGCJoystickHat *hat in _hats) { + // This assumes that the values provided by GCDeviceDirectionPad are + // digital. this might not always be the case. + CSJoystickHatDirection hatDir = 0; + if (hat.directionPad.down.pressed) { + hatDir |= CSJoystickHatDirectionDown; + } + if (hat.directionPad.up.pressed) { + hatDir |= CSJoystickHatDirectionUp; + } + if (hat.directionPad.left.pressed) { + hatDir |= CSJoystickHatDirectionLeft; + } + if (hat.directionPad.right.pressed) { + hatDir |= CSJoystickHatDirectionRight; + } + // There shouldn't be any conflicting directions. + hat.direction = hatDir; + } } - (NSString *)description { @@ -442,7 +479,23 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev NSMutableArray *axes = [[NSMutableArray alloc] init]; NSMutableArray *hats = [[NSMutableArray alloc] init]; - //TODO: write! + if (controller.extendedGamepad) { + GCExtendedGamepad *gp = controller.extendedGamepad; + // Let's go a b x y + // 1 2 3 4 + [buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonA index:1]]; + [buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonB index:2]]; + [buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonX index:3]]; + [buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonY index:4]]; + + [hats addObject:[[CSGCJoystickHat alloc] initWithDirectionPad:gp.dpad]]; + + [axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.xAxis type:CSJoystickAxisTypeX]]; + [axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.yAxis type:CSJoystickAxisTypeY]]; + [axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.rightThumbstick.xAxis type:CSJoystickAxisTypeZ]]; + } else { + return; + } // Add this joystick to the list. [_joysticks addObject:[[CSGCJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:controller]]; From 5a48e50355a7c381b524e9f4cbe5a36a9c568ae6 Mon Sep 17 00:00:00 2001 From: "C.W. Betts" Date: Mon, 14 Dec 2020 15:41:11 -0700 Subject: [PATCH 3/5] Use isEqual: to compare GCController when connecting/disconnecting. Only remove observers for GCController notifications. --- .../Mac/Clock Signal/Joystick Manager/CSJoystickManager.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m index f863feac6..307dbe3a8 100644 --- a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m +++ b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m @@ -471,7 +471,7 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev if (![joystick isKindOfClass:[CSGCJoystick class]]) { continue; } - if(joystick.device == controller) return; + if([joystick.device isEqual:controller]) return; } // Prepare to collate a list of buttons, axes and hats for the new device. @@ -509,7 +509,7 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev if (![joystick isKindOfClass:[CSGCJoystick class]]) { continue; } - if(joystick.device == controller) { + if([joystick.device isEqual:controller]) { [_joysticks removeObject:joystick]; return; } @@ -521,7 +521,8 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); CFRelease(_hidManager); if (@available(macOS 11.0, *)) { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidConnectNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidDisconnectNotification object:nil]; } } From c5c56f9d05ae32007d04433961372fa04f4ee0d8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 23 Dec 2020 11:15:57 -0400 Subject: [PATCH 4/5] Mention my manual list sorting. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6324f1c7..0ceafada9 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,4 @@ This emulator attempts cycle-accurate emulation of all supported machines. In so I've been asked several times whether it is possible to sponsor this project; I think that's a poor fit for this emulator's highly-malleable scope, and it makes me uncomfortable because as the author I primarily see only its defects. -An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Per the optimism of some book sellers, please don't purchase anything that is currnetly listed only at an absurd price. +An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Per the optimism of some book sellers, please don't purchase anything that is currnetly listed only at an absurd price; they were sorted by secondhand price when added to the list, with cheapest being $5. From dfe4e49110b02de1f475a0bb8da0b1a2f44b402f Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Tue, 29 Dec 2020 22:08:48 -0500 Subject: [PATCH 5/5] Ensure proper in-memory ordering of the b72a2c70 ROM. --- Components/DiskII/DiskII.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Components/DiskII/DiskII.cpp b/Components/DiskII/DiskII.cpp index dc8e6029e..c6174c47d 100644 --- a/Components/DiskII/DiskII.cpp +++ b/Components/DiskII/DiskII.cpp @@ -180,29 +180,40 @@ void DiskII::set_state_machine(const std::vector &state_machine) { if(state_machine[0] != 0x18) { for(size_t source_address = 0; source_address < 256; ++source_address) { // Remap into Beneath Apple Pro-DOS address form. - size_t destination_address = - ((source_address&0x80) ? 0x10 : 0x00) | - ((source_address&0x01) ? 0x20 : 0x00) | - ((source_address&0x40) ? 0x40 : 0x00) | + const size_t destination_address = ((source_address&0x20) ? 0x80 : 0x00) | - ((source_address&0x10) ? 0x01 : 0x00) | + ((source_address&0x40) ? 0x40 : 0x00) | + ((source_address&0x01) ? 0x20 : 0x00) | + ((source_address&0x80) ? 0x10 : 0x00) | ((source_address&0x08) ? 0x08 : 0x00) | ((source_address&0x04) ? 0x04 : 0x00) | - ((source_address&0x02) ? 0x02 : 0x00); - uint8_t source_value = state_machine[source_address]; + ((source_address&0x02) ? 0x02 : 0x00) | + ((source_address&0x10) ? 0x01 : 0x00); - source_value = + // Store. + const uint8_t source_value = state_machine[source_address]; + state_machine_[destination_address] = ((source_value & 0x80) ? 0x10 : 0x0) | ((source_value & 0x40) ? 0x20 : 0x0) | ((source_value & 0x20) ? 0x40 : 0x0) | ((source_value & 0x10) ? 0x80 : 0x0) | (source_value & 0x0f); - - // Store. - state_machine_[destination_address] = source_value; } } else { - memcpy(&state_machine_[0], &state_machine[0], 128); + for(size_t source_address = 0; source_address < 256; ++source_address) { + // Reshuffle ordering of bytes only, to retain indexing by the high nibble. + const size_t destination_address = + ((source_address&0x80) ? 0x80 : 0x00) | + ((source_address&0x40) ? 0x40 : 0x00) | + ((source_address&0x01) ? 0x20 : 0x00) | + ((source_address&0x20) ? 0x10 : 0x00) | + ((source_address&0x08) ? 0x08 : 0x00) | + ((source_address&0x04) ? 0x04 : 0x00) | + ((source_address&0x02) ? 0x02 : 0x00) | + ((source_address&0x10) ? 0x01 : 0x00); + + state_machine_[destination_address] = state_machine[source_address]; + } } }