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]; + } } } diff --git a/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m b/OSBindings/Mac/Clock Signal/Joystick Manager/CSJoystickManager.m index 843edf9a3..307dbe3a8 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,157 @@ @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]; +} + +- (void)setPosition:(float)position { + _position = 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"]; +} + +- (void)setIsPressed:(bool)isPressed { + _isPressed = isPressed; +} + +@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 { + // 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 { + 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 +448,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 +463,80 @@ 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 isEqual: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]; + + 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]]; +} + +- (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 isEqual: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 name:GCControllerDidConnectNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidDisconnectNotification object:nil]; + } } - (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 +561,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 +578,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; 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.