Index: src/hid/Makefile ================================================================== --- src/hid/Makefile +++ src/hid/Makefile @@ -7,11 +7,12 @@ FRAMEWORK = ${OBJFWHID_FRAMEWORK} LIB_MAJOR = ${OBJFWHID_LIB_MAJOR} LIB_MINOR = ${OBJFWHID_LIB_MINOR} LIB_PATCH = ${OBJFWHID_LIB_PATCH} -SRCS = OHGameController.m \ +SRCS = OHCombinedJoyCons.m \ + OHGameController.m \ OHGameControllerAxis.m \ OHGameControllerButton.m \ OHGameControllerDirectionalPad.m \ OHGameControllerElement.m \ OHGameControllerProfile.m \ ADDED src/hid/OHCombinedJoyCons.h Index: src/hid/OHCombinedJoyCons.h ================================================================== --- /dev/null +++ src/hid/OHCombinedJoyCons.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OHGamepad.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OHGameController; + +/** + * @brief Combines a left and a right Joy-Con into a gamepad. + */ +@interface OHCombinedJoyCons: OHGamepad +{ + OHGameControllerProfile *_leftJoyCon, *_rightJoyCon; +} + +/** + * @brief Creates a new @ref OHCombinedJoyCons with the specified left and + * right Joy-Con. + * + * @param leftJoyCon The left Joy-Con + * @param rightJoyCon The right Joy-Con + * @return An new @ref OHCombinedJoyCons + */ ++ (instancetype)gamepadWithLeftJoyCon: (OHGameController *)leftJoyCon + rightJoyCon: (OHGameController *)rightJoyCon; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated @ref OHCombinedJoyCons with the + * specified left and right Joy-Con. + * + * @param leftJoyCon The left Joy-Con + * @param rightJoyCon The right Joy-Con + * @return An initialized @ref OHCombinedJoyCons + */ +- (instancetype)initWithLeftJoyCon: (OHGameController *)leftJoyCon + rightJoyCon: (OHGameController *)rightJoyCon; +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OHCombinedJoyCons.m Index: src/hid/OHCombinedJoyCons.m ================================================================== --- /dev/null +++ src/hid/OHCombinedJoyCons.m @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OHCombinedJoyCons.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OHGameController.h" +#import "OHGameControllerDirectionalPad.h" + +#import "OFInvalidArgumentException.h" + +@implementation OHCombinedJoyCons ++ (instancetype)gamepadWithLeftJoyCon: (OHGameController *)leftJoyCon + rightJoyCon: (OHGameController *)rightJoyCon +{ + return [[[self alloc] initWithLeftJoyCon: leftJoyCon + rightJoyCon: rightJoyCon] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithLeftJoyCon: (OHGameController *)leftJoyCon + rightJoyCon: (OHGameController *)rightJoyCon +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + OFDictionary *leftButtons, *rightButtons; + OFMutableDictionary *buttons, *directionalPads; + OHGameControllerDirectionalPad *directionalPad; + + if (leftJoyCon.vendorID.unsignedShortValue != + OHVendorIDNintendo || + rightJoyCon.vendorID.unsignedShortValue != + OHVendorIDNintendo) + @throw [OFInvalidArgumentException exception]; + + if (leftJoyCon.productID.unsignedShortValue != + OHProductIDLeftJoyCon || + rightJoyCon.productID.unsignedShortValue != + OHProductIDRightJoyCon) + @throw [OFInvalidArgumentException exception]; + + _leftJoyCon = [leftJoyCon.rawProfile retain]; + _rightJoyCon = [rightJoyCon.rawProfile retain]; + + leftButtons = _leftJoyCon.buttons; + rightButtons = _rightJoyCon.buttons; + + buttons = [OFMutableDictionary dictionaryWithCapacity: + leftButtons.count + rightButtons.count]; + [buttons addEntriesFromDictionary: leftButtons]; + [buttons addEntriesFromDictionary: rightButtons]; + [buttons removeObjectForKey: @"D-Pad Up"]; + [buttons removeObjectForKey: @"D-Pad Down"]; + [buttons removeObjectForKey: @"D-Pad Left"]; + [buttons removeObjectForKey: @"D-Pad Right"]; + [buttons removeObjectForKey: @"SL"]; + [buttons removeObjectForKey: @"SR"]; + [buttons makeImmutable]; + _buttons = [buttons retain]; + + _axes = [[OFDictionary alloc] init]; + + directionalPads = + [OFMutableDictionary dictionaryWithCapacity: 3]; + + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"Left Thumbstick" + xAxis: [_leftJoyCon.axes objectForKey: @"X"] + yAxis: [_leftJoyCon.axes objectForKey: @"Y"]] + autorelease]; + [directionalPads setObject: directionalPad + forKey: @"Left Thumbstick"]; + + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"Right Thumbstick" + xAxis: [_rightJoyCon.axes objectForKey: @"RX"] + yAxis: [_rightJoyCon.axes objectForKey: @"RY"]] + autorelease]; + [directionalPads setObject: directionalPad + forKey: @"Right Thumbstick"]; + + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"D-Pad" + up: [leftButtons objectForKey: @"D-Pad Up"] + down: [leftButtons objectForKey: @"D-Pad Down"] + left: [leftButtons objectForKey: @"D-Pad Left"] + right: [leftButtons objectForKey: @"D-Pad Right"]] + autorelease]; + [directionalPads setObject: directionalPad forKey: @"D-Pad"]; + + [directionalPads makeImmutable]; + _directionalPads = [directionalPads retain]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_leftJoyCon release]; + [_rightJoyCon release]; + + [super dealloc]; +} + +- (OHGameControllerButton *)northButton +{ + return [_buttons objectForKey: @"X"]; +} + +- (OHGameControllerButton *)southButton +{ + return [_buttons objectForKey: @"B"]; +} + +- (OHGameControllerButton *)westButton +{ + return [_buttons objectForKey: @"Y"]; +} + +- (OHGameControllerButton *)eastButton +{ + return [_buttons objectForKey: @"A"]; +} + +- (OHGameControllerButton *)leftShoulderButton +{ + return [_buttons objectForKey: @"L"]; +} + +- (OHGameControllerButton *)rightShoulderButton +{ + return [_buttons objectForKey: @"R"]; +} + +- (OHGameControllerButton *)leftTriggerButton +{ + return [_buttons objectForKey: @"ZL"]; +} + +- (OHGameControllerButton *)rightTriggerButton +{ + return [_buttons objectForKey: @"ZR"]; +} + +- (OHGameControllerButton *)leftThumbstickButton +{ + return [_buttons objectForKey: @"Left Thumbstick"]; +} + +- (OHGameControllerButton *)rightThumbstickButton +{ + return [_buttons objectForKey: @"Right Thumbstick"]; +} + +- (OHGameControllerButton *)menuButton +{ + return [_buttons objectForKey: @"+"]; +} + +- (OHGameControllerButton *)optionsButton +{ + return [_buttons objectForKey: @"-"]; +} + +- (OHGameControllerButton *)homeButton +{ + return [_buttons objectForKey: @"Home"]; +} + +- (OHGameControllerDirectionalPad *)leftThumbStick +{ + return [_directionalPads objectForKey: @"Left Thumbstick"]; +} + +- (OHGameControllerDirectionalPad *)rightThumbStick +{ + return [_directionalPads objectForKey: @"Right Thumbstick"]; +} + +- (OHGameControllerDirectionalPad *)dPad +{ + return [_directionalPads objectForKey: @"D-Pad"]; +} +@end Index: src/hid/OHEvdevGameController.m ================================================================== --- src/hid/OHEvdevGameController.m +++ src/hid/OHEvdevGameController.m @@ -127,11 +127,11 @@ case BTN_TL: return @"L"; case BTN_TL2: return @"ZL"; case BTN_THUMBL: - return @"Left Stick"; + return @"Left Thumbstick"; case BTN_SELECT: return @"-"; case BTN_Z: return @"Capture"; case BTN_TR: @@ -153,11 +153,11 @@ case BTN_TR: return @"R"; case BTN_TR2: return @"ZR"; case BTN_THUMBR: - return @"Right Stick"; + return @"Right Thumbstick"; case BTN_START: return @"+"; case BTN_MODE: return @"Home"; case BTN_TL: Index: tests/gamecontroller/GameControllerTests.m ================================================================== --- tests/gamecontroller/GameControllerTests.m +++ tests/gamecontroller/GameControllerTests.m @@ -26,10 +26,11 @@ #import "OFDictionary.h" #import "OFNumber.h" #import "OFStdIOStream.h" #import "OFThread.h" +#import "OHCombinedJoyCons.h" #import "OHGameController.h" #import "OHGameControllerAxis.h" #import "OHGameControllerButton.h" #import "OHGameControllerDirectionalPad.h" #import "OHGameControllerProfile.h" @@ -51,16 +52,107 @@ # define gray silver #endif @interface GameControllerTests: OFObject { - OFMutableArray OF_GENERIC(OHGameController *) *_controllers; + OFArray OF_GENERIC(OHGameController *) *_controllers; OFDate *_lastControllersUpdate; } @end OF_APPLICATION_DELEGATE(GameControllerTests) + +static void printProfile(OHGameControllerProfile *profile) +{ + OFArray OF_GENERIC(OFString *) *buttons = + profile.buttons.allKeys.sortedArray; + OFArray OF_GENERIC(OFString *) *axes = profile.axes.allKeys.sortedArray; + OFArray OF_GENERIC(OFString *) *directionalPads = + profile.directionalPads.allKeys.sortedArray; + size_t i; + + i = 0; + for (OFString *name in buttons) { + OHGameControllerButton *button = + [profile.buttons objectForKey: name]; + + if (i++ == buttonsPerLine) { + [OFStdOut writeString: @"\n"]; + i = 0; + } + + if (button.value == 1) + [OFStdOut setForegroundColor: [OFColor red]]; + else if (button.value > 0.5) + [OFStdOut setForegroundColor: [OFColor yellow]]; + else if (button.value > 0) + [OFStdOut setForegroundColor: [OFColor green]]; + else + [OFStdOut setForegroundColor: [OFColor gray]]; + + [OFStdOut writeFormat: @"[%@] ", name]; + } + [OFStdOut setForegroundColor: [OFColor gray]]; + [OFStdOut writeString: @"\n"]; + + i = 0; + for (OFString *name in axes) { + OHGameControllerAxis *axis = [profile.axes objectForKey: name]; + + if (i++ == buttonsPerLine) { + [OFStdOut writeString: @"\n"]; + i = 0; + } + + [OFStdOut writeFormat: @"%@: %5.2f ", name, axis.value]; + } + if (axes.count > 0) + [OFStdOut writeString: @"\n"]; + + i = 0; + for (OFString *name in directionalPads) { + OHGameControllerDirectionalPad *directionalPad = + [profile.directionalPads objectForKey: name]; + + if (i++ == 2) { + [OFStdOut writeString: @"\n"]; + i = 0; + } + + [OFStdOut writeFormat: + @"%@: (%5.2f, %5.2f) ", + name, + directionalPad.xAxis.value, directionalPad.yAxis.value]; + } + if (directionalPads.count > 0) + [OFStdOut writeString: @"\n"]; + + if ([profile isKindOfClass: [OHGamepad class]]) { + OHGamepad *gamepad = (OHGamepad *)profile; + + [OFStdOut writeFormat: + @"[Map] North: %@ South: %@ West: %@ East: %@\n", + gamepad.northButton.name, gamepad.southButton.name, + gamepad.westButton.name, gamepad.eastButton.name]; + [OFStdOut writeFormat: + @"[Map] Left Shoulder: %@ Right Shoulder: %@\n", + gamepad.leftShoulderButton.name, + gamepad.rightShoulderButton.name]; + [OFStdOut writeFormat: + @"[Map] Left Trigger: %@ Right Trigger: %@\n", + gamepad.leftTriggerButton.name, + gamepad.rightTriggerButton.name]; + [OFStdOut writeFormat: + @"[Map] Left Thumbstick: %@ Right Thumbstick: %@\n", + gamepad.leftThumbstickButton.name, + gamepad.rightThumbstickButton.name]; + [OFStdOut writeFormat: + @"[Map] Menu: %@ Options: %@ Home: %@\n", + gamepad.menuButton.name, gamepad.optionsButton.name, + gamepad.homeButton.name]; + } +} @implementation GameControllerTests - (void)applicationDidFinishLaunching: (OFNotification *)notification { #if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) @@ -67,18 +159,18 @@ [OFStdIOStream setUpConsole]; #endif for (;;) { void *pool = objc_autoreleasePoolPush(); + OHGameController *leftJoyCon = nil, *rightJoyCon = nil; if (_lastControllersUpdate == nil || -[_lastControllersUpdate timeIntervalSinceNow] > 1) { [_controllers release]; [_lastControllersUpdate release]; - _controllers = [[OHGameController controllers] - mutableCopy]; + _controllers = [[OHGameController controllers] retain]; _lastControllersUpdate = [[OFDate alloc] init]; [OFStdOut clear]; } @@ -86,17 +178,10 @@ for (OHGameController *controller in _controllers) { OHGameControllerProfile *profile = (controller.gamepad != nil ? controller.gamepad : controller.rawProfile); - OFArray OF_GENERIC(OFString *) *buttons = - profile.buttons.allKeys.sortedArray; - OFArray OF_GENERIC(OFString *) *axes = - profile.axes.allKeys.sortedArray; - OFArray OF_GENERIC(OFString *) *directionalPads = - profile.directionalPads.allKeys.sortedArray; - size_t i; [OFStdOut setForegroundColor: [OFColor green]]; [OFStdOut writeLine: controller.description]; @try { @@ -105,104 +190,32 @@ [OFStdOut setForegroundColor: [OFColor red]]; [OFStdOut writeString: e.description]; continue; } - i = 0; - for (OFString *name in buttons) { - OHGameControllerButton *button = - [profile.buttons objectForKey: name]; - - if (i++ == buttonsPerLine) { - [OFStdOut writeString: @"\n"]; - i = 0; - } - - if (button.value == 1) - [OFStdOut setForegroundColor: - [OFColor red]]; - else if (button.value > 0.5) - [OFStdOut setForegroundColor: - [OFColor yellow]]; - else if (button.value > 0) - [OFStdOut setForegroundColor: - [OFColor green]]; - else - [OFStdOut setForegroundColor: - [OFColor gray]]; - - [OFStdOut writeFormat: @"[%@] ", name]; - } - [OFStdOut setForegroundColor: [OFColor gray]]; - [OFStdOut writeString: @"\n"]; - - i = 0; - for (OFString *name in axes) { - OHGameControllerAxis *axis = - [profile.axes objectForKey: name]; - - if (i++ == buttonsPerLine) { - [OFStdOut writeString: @"\n"]; - i = 0; - } - - [OFStdOut writeFormat: @"%@: %5.2f ", - name, axis.value]; - } - if (axes.count > 0) - [OFStdOut writeString: @"\n"]; - - i = 0; - for (OFString *name in directionalPads) { - OHGameControllerDirectionalPad *directionalPad = - [profile.directionalPads - objectForKey: name]; - - if (i++ == 2) { - [OFStdOut writeString: @"\n"]; - i = 0; - } - - [OFStdOut writeFormat: - @"%@: (%5.2f, %5.2f) ", - name, directionalPad.xAxis.value, - directionalPad.yAxis.value]; - } - if (directionalPads.count > 0) - [OFStdOut writeString: @"\n"]; - - if ([profile isKindOfClass: [OHGamepad class]]) { - OHGamepad *gamepad = (OHGamepad *)profile; - - [OFStdOut writeFormat: - @"[Map] North: %@ South: %@" - @" West: %@ East: %@\n", - gamepad.northButton.name, - gamepad.southButton.name, - gamepad.westButton.name, - gamepad.eastButton.name]; - [OFStdOut writeFormat: - @"[Map] Left Shoulder: %@" - @" Right Shoulder: %@\n", - gamepad.leftShoulderButton.name, - gamepad.rightShoulderButton.name]; - [OFStdOut writeFormat: - @"[Map] Left Trigger: %@" - @" Right Trigger: %@\n", - gamepad.leftTriggerButton.name, - gamepad.rightTriggerButton.name]; - [OFStdOut writeFormat: - @"[Map] Left Thumbstick: %@" - @" Right Thumbstick: %@\n", - gamepad.leftThumbstickButton.name, - gamepad.rightThumbstickButton.name]; - [OFStdOut writeFormat: - @"[Map] Menu: %@ Options: %@ Home: %@\n", - gamepad.menuButton.name, - gamepad.optionsButton.name, - gamepad.homeButton.name]; - } + printProfile(profile); + + if (controller.vendorID.unsignedShortValue == + OHVendorIDNintendo) { + if (controller.productID.unsignedShortValue == + OHProductIDLeftJoyCon) + leftJoyCon = controller; + if (controller.productID.unsignedShortValue == + OHProductIDRightJoyCon) + rightJoyCon = controller; + } + } + + if (leftJoyCon != nil && rightJoyCon != nil) { + OHCombinedJoyCons *combinedJoyCons = [OHCombinedJoyCons + gamepadWithLeftJoyCon: leftJoyCon + rightJoyCon: rightJoyCon]; + + [OFStdOut setForegroundColor: [OFColor green]]; + [OFStdOut writeLine: @"Combined Joy-Cons"]; + + printProfile(combinedJoyCons); } #if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) [OFThread waitForVerticalBlank]; #else