ADDED src/hid/OFEvdevGameController.h Index: src/hid/OFEvdevGameController.h ================================================================== --- /dev/null +++ src/hid/OFEvdevGameController.h @@ -0,0 +1,48 @@ +/* + * 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 "OFGameController.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFEvdevGameController: OFGameController +{ + OFString *_path; + int _fd; + uint16_t _vendorID, _productID; + OFString *_name; + OFMutableSet *_buttons, *_pressedButtons; + bool _hasLeftAnalogStick, _hasRightAnalogStick; + bool _hasLeftTriggerPressure, _hasRightTriggerPressure; + unsigned int _leftTriggerPressureBit, _rightTriggerPressureBit; + OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; + float _leftTriggerPressure, _rightTriggerPressure; + int32_t _leftAnalogStickMinX, _leftAnalogStickMaxX; + int32_t _leftAnalogStickMinY, _leftAnalogStickMaxY; + unsigned int _rightAnalogStickXBit, _rightAnalogStickYBit; + int32_t _rightAnalogStickMinX, _rightAnalogStickMaxX; + int32_t _rightAnalogStickMinY, _rightAnalogStickMaxY; + int32_t _leftTriggerMinPressure, _leftTriggerMaxPressure; + int32_t _rightTriggerMinPressure, _rightTriggerMaxPressure; +} + +- (instancetype)of_initWithPath: (OFString *)path OF_METHOD_FAMILY(init); +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFEvdevGameController.m Index: src/hid/OFEvdevGameController.m ================================================================== --- /dev/null +++ src/hid/OFEvdevGameController.m @@ -0,0 +1,610 @@ +/* + * 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" + +#include +#include +#include + +#import "OFEvdevGameController.h" +#import "OFArray.h" +#import "OFFileManager.h" +#import "OFLocale.h" +#import "OFNumber.h" +#import "OFSet.h" + +#include +#include + +#import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFOpenItemFailedException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" + +/* + * Controllers with tested correct mapping: + * + * Microsoft X-Box 360 pad [045E:028E] + * Joy-Con (L) [057E:2006] + * Joy-Con (R) [057E:2007] + * N64 Controller [057E:2019] + * Sony Interactive Entertainment DualSense Wireless Controller [054C:0CE6] + * 8BitDo Pro 2 Wired Controller [2DC8:3106] + * Stadia2SZY-0d6c [18D1:9400] + * Wireless Controller [054C:09CC] + */ + +static const uint16_t vendorIDMicrosoft = 0x045E; +static const uint16_t vendorIDNintendo = 0x057E; +static const uint16_t vendorIDSony = 0x054C; +static const uint16_t vendorIDGoogle = 0x18D1; + +/* Microsoft controllers */ +static const uint16_t productIDXbox360 = 0x028E; + +/* Nintendo controllers */ +static const uint16_t productIDLeftJoycon = 0x2006; +static const uint16_t productIDRightJoycon = 0x2007; +static const uint16_t productIDN64Controller = 0x2019; + +/* Sony controllers */ +static const uint16_t productIDDualSense = 0x0CE6; +static const uint16_t productIDDualShock4 = 0x09CC; + +/* Google controllers */ +static const uint16_t productIDStadia = 0x9400; + +static const uint16_t buttons[] = { + BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2, + BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_THUMBL, BTN_THUMBR, + BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, + BTN_TRIGGER_HAPPY1, BTN_TRIGGER_HAPPY2 +}; + +static OFGameControllerButton +buttonToName(uint16_t button, uint16_t vendorID, uint16_t productID) +{ + if (vendorID == vendorIDNintendo && + productID == productIDLeftJoycon) { + switch (button) { + case BTN_SELECT: + return OFGameControllerMinusButton; + case BTN_Z: + return OFGameControllerCaptureButton; + case BTN_TR: + return OFGameControllerSLButton; + case BTN_TR2: + return OFGameControllerSRButton; + } + } else if (vendorID == vendorIDNintendo && + productID == productIDRightJoycon) { + switch (button) { + case BTN_NORTH: + return OFGameControllerNorthButton; + case BTN_WEST: + return OFGameControllerWestButton; + case BTN_START: + return OFGameControllerPlusButton; + case BTN_TL: + return OFGameControllerSLButton; + case BTN_TL2: + return OFGameControllerSRButton; + } + } else if (vendorID == vendorIDNintendo && + productID == productIDN64Controller) { + switch (button) { + case BTN_A: + return OFGameControllerAButton; + case BTN_B: + return OFGameControllerBButton; + case BTN_SELECT: + return OFGameControllerCPadUpButton; + case BTN_X: + return OFGameControllerCPadDownButton; + case BTN_Y: + return OFGameControllerCPadLeftButton; + case BTN_C: + return OFGameControllerCPadRightButton; + case BTN_Z: + return OFGameControllerCaptureButton; + } + } else if (vendorID == vendorIDSony && + (productID == productIDDualSense || + productID == productIDDualShock4)) { + switch (button) { + case BTN_NORTH: + return OFGameControllerNorthButton; + case BTN_WEST: + return OFGameControllerWestButton; + } + } else if (vendorID == vendorIDGoogle && productID == productIDStadia) { + switch (button) { + case BTN_TRIGGER_HAPPY1: + return OFGameControllerAssistantButton; + case BTN_TRIGGER_HAPPY2: + return OFGameControllerCaptureButton; + } + } + + switch (button) { + case BTN_Y: + return OFGameControllerNorthButton; + case BTN_A: + return OFGameControllerSouthButton; + case BTN_X: + return OFGameControllerWestButton; + case BTN_B: + return OFGameControllerEastButton; + case BTN_TL2: + return OFGameControllerLeftTriggerButton; + case BTN_TR2: + return OFGameControllerRightTriggerButton; + case BTN_TL: + return OFGameControllerLeftShoulderButton; + case BTN_TR: + return OFGameControllerRightShoulderButton; + case BTN_THUMBL: + return OFGameControllerLeftStickButton; + case BTN_THUMBR: + return OFGameControllerRightStickButton; + case BTN_DPAD_UP: + return OFGameControllerDPadUpButton; + case BTN_DPAD_DOWN: + return OFGameControllerDPadDownButton; + case BTN_DPAD_LEFT: + return OFGameControllerDPadLeftButton; + case BTN_DPAD_RIGHT: + return OFGameControllerDPadRightButton; + case BTN_START: + return OFGameControllerStartButton; + case BTN_SELECT: + return OFGameControllerSelectButton; + case BTN_MODE: + return OFGameControllerHomeButton; + case BTN_C: + return OFGameControllerCButton; + case BTN_Z: + return OFGameControllerZButton; + } + + return nil; +} + +static float +scale(float value, float min, float max) +{ + if (value < min) + value = min; + if (value > max) + value = max; + + return ((value - min) / (max - min) * 2) - 1; +} + +@implementation OFEvdevGameController +@synthesize name = _name, buttons = _buttons; +@synthesize hasLeftAnalogStick = _hasLeftAnalogStick; +@synthesize hasRightAnalogStick = _hasRightAnalogStick; +@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; +@synthesize rightAnalogStickPosition = _rightAnalogStickPosition; + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + OFMutableArray *controllers = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + + for (OFString *device in [[OFFileManager defaultManager] + contentsOfDirectoryAtPath: @"/dev/input"]) { + OFString *path; + OFGameController *controller; + + if (![device hasPrefix: @"event"]) + continue; + + path = [@"/dev/input" stringByAppendingPathComponent: device]; + + @try { + controller = [[[OFEvdevGameController alloc] + of_initWithPath: path] autorelease]; + } @catch (OFOpenItemFailedException *e) { + if (e.errNo == EACCES) + continue; + + @throw e; + } @catch (OFInvalidArgumentException *e) { + /* Not a game controller. */ + continue; + } + + [controllers addObject: controller]; + } + + [controllers sort]; + [controllers makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return controllers; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)of_initWithPath: (OFString *)path +{ + self = [super init]; + + @try { + OFStringEncoding encoding = [OFLocale encoding]; + unsigned long evBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, + EV_MAX) / OF_ULONG_BIT] = { 0 }; + unsigned long keyBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, + KEY_MAX) / OF_ULONG_BIT] = { 0 }; + unsigned long absBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, + ABS_MAX) / OF_ULONG_BIT] = { 0 }; + struct input_id inputID; + char name[128]; + + _path = [path copy]; + + if ((_fd = open([_path cStringWithEncoding: encoding], + O_RDONLY | O_NONBLOCK)) == -1) + @throw [OFOpenItemFailedException + exceptionWithPath: _path + mode: @"r" + errNo: errno]; + + if (ioctl(_fd, EVIOCGBIT(0, sizeof(evBits)), evBits) == -1) + @throw [OFInitializationFailedException exception]; + + if (!OFBitSetIsSet(evBits, EV_KEY)) + @throw [OFInvalidArgumentException exception]; + + if (ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) == + -1) + @throw [OFInitializationFailedException exception]; + + if (!OFBitSetIsSet(keyBits, BTN_GAMEPAD) && + !OFBitSetIsSet(keyBits, BTN_DPAD_UP)) + @throw [OFInvalidArgumentException exception]; + + if (ioctl(_fd, EVIOCGID, &inputID) == -1) + @throw [OFInvalidArgumentException exception]; + + _vendorID = inputID.vendor; + _productID = inputID.product; + + if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) == -1) + @throw [OFInitializationFailedException exception]; + + _name = [[OFString alloc] initWithCString: name + encoding: encoding]; + + _buttons = [[OFMutableSet alloc] init]; + for (size_t i = 0; i < sizeof(buttons) / sizeof(*buttons); + i++) { + if (OFBitSetIsSet(keyBits, buttons[i])) { + OFGameControllerButton button = buttonToName( + buttons[i], _vendorID, _productID); + + if (button != nil) + [_buttons addObject: button]; + } + } + + _pressedButtons = [[OFMutableSet alloc] init]; + + if (OFBitSetIsSet(evBits, EV_ABS)) { + if (ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), + absBits) == -1) + @throw [OFInitializationFailedException + exception]; + + if (_vendorID == vendorIDGoogle && + _productID == productIDStadia) { + /* + * It's unclear how this can be screwed up + * *this* bad. + */ + _rightAnalogStickXBit = ABS_Z; + _rightAnalogStickYBit = ABS_RZ; + _leftTriggerPressureBit = ABS_BRAKE; + _rightTriggerPressureBit = ABS_GAS; + } else { + _rightAnalogStickXBit = ABS_RX; + _rightAnalogStickYBit = ABS_RY; + _leftTriggerPressureBit = ABS_Z; + _rightTriggerPressureBit = ABS_RZ; + } + + if (OFBitSetIsSet(absBits, ABS_X) && + OFBitSetIsSet(absBits, ABS_Y)) { + struct input_absinfo infoX, infoY; + + _hasLeftAnalogStick = true; + + if (ioctl(_fd, EVIOCGABS(ABS_X), &infoX) == -1) + @throw [OFInitializationFailedException + exception]; + + if (ioctl(_fd, EVIOCGABS(ABS_Y), &infoY) == -1) + @throw [OFInitializationFailedException + exception]; + + _leftAnalogStickMinX = infoX.minimum; + _leftAnalogStickMaxX = infoX.maximum; + _leftAnalogStickMinY = infoY.minimum; + _leftAnalogStickMaxY = infoY.maximum; + } + + if (OFBitSetIsSet(absBits, _rightAnalogStickXBit) && + OFBitSetIsSet(absBits, _rightAnalogStickYBit)) { + struct input_absinfo infoX, infoY; + + _hasRightAnalogStick = true; + + if (ioctl(_fd, EVIOCGABS(_rightAnalogStickXBit), + &infoX) == -1) + @throw [OFInitializationFailedException + exception]; + + if (ioctl(_fd, EVIOCGABS(_rightAnalogStickYBit), + &infoY) == -1) + @throw [OFInitializationFailedException + exception]; + + _rightAnalogStickMinX = infoX.minimum; + _rightAnalogStickMaxX = infoX.maximum; + _rightAnalogStickMinY = infoY.minimum; + _rightAnalogStickMaxY = infoY.maximum; + } + + if (OFBitSetIsSet(absBits, ABS_HAT0X) && + OFBitSetIsSet(absBits, ABS_HAT0Y)) { + [_buttons addObject: + OFGameControllerDPadLeftButton]; + [_buttons addObject: + OFGameControllerDPadRightButton]; + [_buttons addObject: + OFGameControllerDPadUpButton]; + [_buttons addObject: + OFGameControllerDPadDownButton]; + } + + if (OFBitSetIsSet(absBits, _leftTriggerPressureBit)) { + struct input_absinfo info; + + _hasLeftTriggerPressure = true; + + if (ioctl(_fd, EVIOCGABS( + _leftTriggerPressureBit), &info) == -1) + @throw [OFInitializationFailedException + exception]; + + _leftTriggerMinPressure = info.minimum; + _leftTriggerMaxPressure = info.maximum; + + [_buttons addObject: + OFGameControllerLeftTriggerButton]; + } + + if (OFBitSetIsSet(absBits, _rightTriggerPressureBit)) { + struct input_absinfo info; + + _hasRightTriggerPressure = true; + + if (ioctl(_fd, EVIOCGABS( + _rightTriggerPressureBit), &info) == -1) + @throw [OFInitializationFailedException + exception]; + + _rightTriggerMinPressure = info.minimum; + _rightTriggerMaxPressure = info.maximum; + + [_buttons addObject: + OFGameControllerRightTriggerButton]; + } + } + + [_buttons makeImmutable]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_path release]; + + if (_fd != -1) + close(_fd); + + [_name release]; + [_buttons release]; + [_pressedButtons release]; + + [super dealloc]; +} + +- (OFNumber *)vendorID +{ + return [OFNumber numberWithUnsignedShort: _vendorID]; +} + +- (OFNumber *)productID +{ + return [OFNumber numberWithUnsignedShort: _productID]; +} + +- (void)retrieveState +{ + struct input_event event; + + for (;;) { + OFGameControllerButton button; + + errno = 0; + + if (read(_fd, &event, sizeof(event)) < (int)sizeof(event)) { + if (errno == EWOULDBLOCK) + return; + + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(event) + errNo: errno]; + } + + switch (event.type) { + case EV_KEY: + if ((button = buttonToName(event.code, _vendorID, + _productID)) != nil) { + if (event.value) + [_pressedButtons addObject: button]; + else + [_pressedButtons removeObject: button]; + } + break; + case EV_ABS: + if (event.code == ABS_X) + _leftAnalogStickPosition.x = scale(event.value, + _leftAnalogStickMinX, _leftAnalogStickMaxX); + else if (event.code == ABS_Y) + _leftAnalogStickPosition.y = scale(event.value, + _leftAnalogStickMinY, _leftAnalogStickMaxY); + else if (event.code == _rightAnalogStickXBit) + _rightAnalogStickPosition.x = scale(event.value, + _rightAnalogStickMinX, + _rightAnalogStickMaxX); + else if (event.code == _rightAnalogStickYBit) + _rightAnalogStickPosition.y = scale(event.value, + _rightAnalogStickMinY, + _rightAnalogStickMaxY); + else if (event.code == ABS_HAT0X) { + if (event.value < 0) { + [_pressedButtons addObject: + OFGameControllerDPadLeftButton]; + [_pressedButtons removeObject: + OFGameControllerDPadRightButton]; + } else if (event.value > 0) { + [_pressedButtons addObject: + OFGameControllerDPadRightButton]; + [_pressedButtons removeObject: + OFGameControllerDPadLeftButton]; + } else { + [_pressedButtons removeObject: + OFGameControllerDPadLeftButton]; + [_pressedButtons removeObject: + OFGameControllerDPadRightButton]; + } + } else if (event.code == ABS_HAT0Y) { + if (event.value < 0) { + [_pressedButtons addObject: + OFGameControllerDPadUpButton]; + [_pressedButtons removeObject: + OFGameControllerDPadDownButton]; + } else if (event.value > 0) { + [_pressedButtons addObject: + OFGameControllerDPadDownButton]; + [_pressedButtons removeObject: + OFGameControllerDPadUpButton]; + } else { + [_pressedButtons removeObject: + OFGameControllerDPadUpButton]; + [_pressedButtons removeObject: + OFGameControllerDPadDownButton]; + } + } else if (event.code == _leftTriggerPressureBit) { + _leftTriggerPressure = scale(event.value, + _leftTriggerMinPressure, + _leftTriggerMaxPressure); + + if (_leftTriggerPressure > 0) + [_pressedButtons addObject: + OFGameControllerLeftTriggerButton]; + else + [_pressedButtons removeObject: + OFGameControllerLeftTriggerButton]; + } else if (event.code == _rightTriggerPressureBit) { + _rightTriggerPressure = scale(event.value, + _rightTriggerMinPressure, + _rightTriggerMaxPressure); + + if (_rightTriggerPressure > 0) + [_pressedButtons addObject: + OFGameControllerRightTriggerButton]; + else + [_pressedButtons removeObject: + OFGameControllerRightTriggerButton]; + } + break; + } + } +} + +- (OFComparisonResult)compare: (OFEvdevGameController *)otherController +{ + unsigned long long selfIndex, otherIndex; + + if (![otherController isKindOfClass: [OFEvdevGameController class]]) + @throw [OFInvalidArgumentException exception]; + + selfIndex = [_path substringFromIndex: 16].unsignedLongLongValue; + otherIndex = [otherController->_path substringFromIndex: 16] + .unsignedLongLongValue; + + if (selfIndex > otherIndex) + return OFOrderedDescending; + if (selfIndex < otherIndex) + return OFOrderedAscending; + + return OFOrderedSame; +} + +- (OFSet *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + if (button == OFGameControllerLeftTriggerButton && + _hasLeftTriggerPressure) + return _leftTriggerPressure; + if (button == OFGameControllerRightTriggerButton && + _hasRightTriggerPressure) + return _rightTriggerPressure; + + return ([self.pressedButtons containsObject: button] ? 1 : 0); +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end Index: src/hid/OFGameController.h ================================================================== --- src/hid/OFGameController.h +++ src/hid/OFGameController.h @@ -257,45 +257,11 @@ #endif /** * @brief A class for reading state from a game controller. */ -OF_SUBCLASSING_RESTRICTED @interface OFGameController: OFObject -{ -#if defined(OF_LINUX) - OFString *_path; - int _fd; - uint16_t _vendorID, _productID; - OFString *_name; - OFMutableSet *_buttons, *_pressedButtons; - bool _hasLeftAnalogStick, _hasRightAnalogStick; - bool _hasLeftTriggerPressure, _hasRightTriggerPressure; - unsigned int _leftTriggerPressureBit, _rightTriggerPressureBit; - OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; - float _leftTriggerPressure, _rightTriggerPressure; - int32_t _leftAnalogStickMinX, _leftAnalogStickMaxX; - int32_t _leftAnalogStickMinY, _leftAnalogStickMaxY; - unsigned int _rightAnalogStickXBit, _rightAnalogStickYBit; - int32_t _rightAnalogStickMinX, _rightAnalogStickMaxX; - int32_t _rightAnalogStickMinY, _rightAnalogStickMaxY; - int32_t _leftTriggerMinPressure, _leftTriggerMaxPressure; - int32_t _rightTriggerMinPressure, _rightTriggerMaxPressure; -#elif defined(OF_NINTENDO_DS) - OFMutableSet *_pressedButtons; -#elif defined(OF_NINTENDO_3DS) - OFMutableSet *_pressedButtons; - OFPoint _leftAnalogStickPosition; -#elif defined(OF_WINDOWS) - DWORD _index; - OFNumber *_Nullable _vendorID, *_Nullable productID; - OFMutableSet *_pressedButtons; - OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; - float _leftTriggerPressure, _rightTriggerPressure; -#endif -} - #ifdef OF_HAVE_CLASS_PROPERTIES @property (class, readonly, nonatomic) OFArray *controllers; #endif @@ -355,12 +321,10 @@ * * @return The available controllers */ + (OFArray OF_GENERIC(OFGameController *) *)controllers; -- (instancetype)init OF_UNAVAILABLE; - /** * @brief Retrieve the current state from the game controller. * * The state returned by @ref OFGameController's messages does not change until * this method is called. Index: src/hid/OFGameController.m ================================================================== --- src/hid/OFGameController.m +++ src/hid/OFGameController.m @@ -19,10 +19,23 @@ #include "config.h" #import "OFGameController.h" #import "OFArray.h" + +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) +# include "OFEvdevGameController.h" +#endif +#ifdef OF_WINDOWS +# include "OFXInputGameController.h" +#endif +#ifdef OF_NINTENDO_DS +# include "OFNintendoDSGameController.h" +#endif +#ifdef OF_NINTENDO_3DS +# include "OFNintendo3DSGameController.h" +#endif const OFGameControllerButton OFGameControllerNorthButton = @"North"; const OFGameControllerButton OFGameControllerSouthButton = @"South"; const OFGameControllerButton OFGameControllerWestButton = @"West"; const OFGameControllerButton OFGameControllerEastButton = @"East"; @@ -59,31 +72,43 @@ const OFGameControllerButton OFGameControllerSLButton = @"SL"; const OFGameControllerButton OFGameControllerSRButton = @"SR"; const OFGameControllerButton OFGameControllerModeButton = @"Mode"; const OFGameControllerButton OFGameControllerAssistantButton = @"Assistant"; -#if defined(OF_LINUX) && defined(OF_HAVE_FILES) -# include "platform/Linux/OFGameController.m" -#elif defined(OF_WINDOWS) -# include "platform/Windows/OFGameController.m" -#elif defined(OF_NINTENDO_DS) -# include "platform/NintendoDS/OFGameController.m" -#elif defined(OF_NINTENDO_3DS) -# include "platform/Nintendo3DS/OFGameController.m" -#else @implementation OFGameController @dynamic name, buttons, pressedButtons, hasLeftAnalogStick; @dynamic leftAnalogStickPosition, hasRightAnalogStick, rightAnalogStickPosition; + (OFArray OF_GENERIC(OFGameController *) *)controllers { +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) + return [OFEvdevGameController controllers]; +#elif defined(OF_WINDOWS) + return [OFXInputGameController controllers]; +#elif defined(OF_NINTENDO_DS) + return [OFNintendoDSGameController controllers]; +#elif defined(OF_NINTENDO_3DS) + return [OFNintendo3DSGameController controllers]; +#else return [OFArray array]; +#endif } - (instancetype)init { - OF_INVALID_INIT_METHOD + if ([self isMemberOfClass: [OFGameController class]]) { + @try { + [self doesNotRecognizeSelector: _cmd]; + } @catch (id e) { + [self release]; + @throw e; + } + + abort(); + } + + return [super init]; } - (OFNumber *)vendorID { return nil; @@ -94,13 +119,26 @@ return nil; } - (void)retrieveState { + OF_UNRECOGNIZED_SELECTOR } - (float)pressureForButton: (OFGameControllerButton)button { - return 0; + OF_UNRECOGNIZED_SELECTOR } @end + +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) +# include "OFEvdevGameController.m" +#endif +#ifdef OF_WINDOWS +# include "OFXInputGameController.m" +#endif +#ifdef OF_NINTENDO_DS +# include "OFNintendoDSGameController.m" +#endif +#ifdef OF_NINTENDO_3DS +# include "OFNintendo3DSGameController.m" #endif ADDED src/hid/OFNintendo3DSGameController.h Index: src/hid/OFNintendo3DSGameController.h ================================================================== --- /dev/null +++ src/hid/OFNintendo3DSGameController.h @@ -0,0 +1,31 @@ +/* + * 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 "OFGameController.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFNintendo3DSGameController: OFGameController +{ + OFMutableSet *_pressedButtons; + OFPoint _leftAnalogStickPosition; +} +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFNintendo3DSGameController.m Index: src/hid/OFNintendo3DSGameController.m ================================================================== --- /dev/null +++ src/hid/OFNintendo3DSGameController.m @@ -0,0 +1,187 @@ +/* + * 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 "OFNintendo3DSGameController.h" +#import "OFArray.h" +#import "OFSet.h" + +#import "OFOutOfRangeException.h" + +#define id id_3ds +#include <3ds.h> +#undef id + +static OFArray OF_GENERIC(OFGameController *) *controllers; + +static void +initControllers(void) +{ + void *pool = objc_autoreleasePoolPush(); + + controllers = [[OFArray alloc] initWithObject: + [[[OFNintendo3DSGameController alloc] init] autorelease]]; + + objc_autoreleasePoolPop(pool); +} + +@implementation OFNintendo3DSGameController +@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + + OFOnce(&onceControl, initControllers); + + return [[controllers retain] autorelease]; +} + +- (instancetype)init +{ + self = [super init]; + + @try { + _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 18]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_pressedButtons release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + u32 keys; + circlePosition pos; + + hidScanInput(); + + keys = hidKeysHeld(); + hidCircleRead(&pos); + + [_pressedButtons removeAllObjects]; + + if (keys & KEY_X) + [_pressedButtons addObject: OFGameControllerNorthButton]; + if (keys & KEY_B) + [_pressedButtons addObject: OFGameControllerSouthButton]; + if (keys & KEY_Y) + [_pressedButtons addObject: OFGameControllerWestButton]; + if (keys & KEY_A) + [_pressedButtons addObject: OFGameControllerEastButton]; + if (keys & KEY_ZL) + [_pressedButtons addObject: OFGameControllerLeftTriggerButton]; + if (keys & KEY_ZR) + [_pressedButtons addObject: OFGameControllerRightTriggerButton]; + if (keys & KEY_L) + [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; + if (keys & KEY_R) + [_pressedButtons addObject: + OFGameControllerRightShoulderButton]; + if (keys & KEY_DUP) + [_pressedButtons addObject: OFGameControllerDPadUpButton]; + if (keys & KEY_DDOWN) + [_pressedButtons addObject: OFGameControllerDPadDownButton]; + if (keys & KEY_DLEFT) + [_pressedButtons addObject: OFGameControllerDPadLeftButton]; + if (keys & KEY_DRIGHT) + [_pressedButtons addObject: OFGameControllerDPadRightButton]; + if (keys & KEY_START) + [_pressedButtons addObject: OFGameControllerStartButton]; + if (keys & KEY_SELECT) + [_pressedButtons addObject: OFGameControllerSelectButton]; + if (keys & KEY_CSTICK_UP) + [_pressedButtons addObject: OFGameControllerCPadUpButton]; + if (keys & KEY_CSTICK_DOWN) + [_pressedButtons addObject: OFGameControllerCPadDownButton]; + if (keys & KEY_CSTICK_LEFT) + [_pressedButtons addObject: OFGameControllerCPadLeftButton]; + if (keys & KEY_CSTICK_RIGHT) + [_pressedButtons addObject: OFGameControllerCPadRightButton]; + + _leftAnalogStickPosition = OFMakePoint( + (float)pos.dx / (pos.dx < 0 ? -INT16_MIN : INT16_MAX), + (float)pos.dy / (pos.dy < 0 ? -INT16_MIN : INT16_MAX)); +} + +- (OFString *)name +{ + return @"Nintendo 3DS"; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons +{ + return [OFSet setWithObjects: + OFGameControllerNorthButton, + OFGameControllerSouthButton, + OFGameControllerWestButton, + OFGameControllerEastButton, + OFGameControllerLeftTriggerButton, + OFGameControllerRightTriggerButton, + OFGameControllerRightShoulderButton, + OFGameControllerLeftShoulderButton, + OFGameControllerDPadUpButton, + OFGameControllerDPadDownButton, + OFGameControllerDPadLeftButton, + OFGameControllerDPadRightButton, + OFGameControllerStartButton, + OFGameControllerSelectButton, + OFGameControllerCPadRightButton, + OFGameControllerCPadLeftButton, + OFGameControllerCPadUpButton, + OFGameControllerCPadDownButton, nil]; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (bool)hasLeftAnalogStick +{ + return true; +} + +- (bool)hasRightAnalogStick +{ + return false; +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + return ([self.pressedButtons containsObject: button] ? 1 : 0); +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end ADDED src/hid/OFNintendoDSGameController.h Index: src/hid/OFNintendoDSGameController.h ================================================================== --- /dev/null +++ src/hid/OFNintendoDSGameController.h @@ -0,0 +1,30 @@ +/* + * 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 "OFGameController.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFNintendoDSGameController: OFGameController +{ + OFMutableSet *_pressedButtons; +} +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFNintendoDSGameController.m Index: src/hid/OFNintendoDSGameController.m ================================================================== --- /dev/null +++ src/hid/OFNintendoDSGameController.m @@ -0,0 +1,160 @@ +/* + * 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 "OFNintendoDSGameController.h" +#import "OFArray.h" +#import "OFSet.h" + +#import "OFOutOfRangeException.h" + +#define asm __asm__ +#include +#undef asm + +static OFArray OF_GENERIC(OFGameController *) *controllers; + +static void +initControllers(void) +{ + void *pool = objc_autoreleasePoolPush(); + + controllers = [[OFArray alloc] initWithObject: + [[[OFNintendoDSGameController alloc] init] autorelease]]; + + objc_autoreleasePoolPop(pool); +} + +@implementation OFNintendoDSGameController ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + static OFOnceControl onceControl = OFOnceControlInitValue; + + OFOnce(&onceControl, initControllers); + + return [[controllers retain] autorelease]; +} + +- (instancetype)init +{ + self = [super init]; + + @try { + _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 12]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_pressedButtons release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + uint32 keys; + + scanKeys(); + keys = keysCurrent(); + + [_pressedButtons removeAllObjects]; + + if (keys & KEY_X) + [_pressedButtons addObject: OFGameControllerNorthButton]; + if (keys & KEY_B) + [_pressedButtons addObject: OFGameControllerSouthButton]; + if (keys & KEY_Y) + [_pressedButtons addObject: OFGameControllerWestButton]; + if (keys & KEY_A) + [_pressedButtons addObject: OFGameControllerEastButton]; + if (keys & KEY_L) + [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; + if (keys & KEY_R) + [_pressedButtons addObject: + OFGameControllerRightShoulderButton]; + if (keys & KEY_UP) + [_pressedButtons addObject: OFGameControllerDPadUpButton]; + if (keys & KEY_DOWN) + [_pressedButtons addObject: OFGameControllerDPadDownButton]; + if (keys & KEY_LEFT) + [_pressedButtons addObject: OFGameControllerDPadLeftButton]; + if (keys & KEY_RIGHT) + [_pressedButtons addObject: OFGameControllerDPadRightButton]; + if (keys & KEY_START) + [_pressedButtons addObject: OFGameControllerStartButton]; + if (keys & KEY_SELECT) + [_pressedButtons addObject: OFGameControllerSelectButton]; +} + +- (OFString *)name +{ + return @"Nintendo DS"; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons +{ + return [OFSet setWithObjects: + OFGameControllerNorthButton, + OFGameControllerSouthButton, + OFGameControllerWestButton, + OFGameControllerEastButton, + OFGameControllerLeftShoulderButton, + OFGameControllerRightShoulderButton, + OFGameControllerDPadUpButton, + OFGameControllerDPadDownButton, + OFGameControllerDPadLeftButton, + OFGameControllerDPadRightButton, + OFGameControllerStartButton, + OFGameControllerSelectButton, nil]; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (bool)hasLeftAnalogStick +{ + return false; +} + +- (bool)hasRightAnalogStick +{ + return false; +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + return ([self.pressedButtons containsObject: button] ? 1 : 0); +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end ADDED src/hid/OFXInputGameController.h Index: src/hid/OFXInputGameController.h ================================================================== --- /dev/null +++ src/hid/OFXInputGameController.h @@ -0,0 +1,36 @@ +/* + * 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 "OFGameController.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFXInputGameController: OFGameController +{ + DWORD _index; + OFNumber *_Nullable _vendorID, *_Nullable productID; + OFMutableSet *_pressedButtons; + OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; + float _leftTriggerPressure, _rightTriggerPressure; +} + +- (instancetype)of_initWithIndex: (DWORD)index OF_METHOD_FAMILY(init); +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFXInputGameController.m Index: src/hid/OFXInputGameController.m ================================================================== --- /dev/null +++ src/hid/OFXInputGameController.m @@ -0,0 +1,277 @@ +/* + * 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 "OFXInputGameController.h" +#import "OFArray.h" +#import "OFNumber.h" +#import "OFSet.h" + +#import "OFInitializationFailedException.h" +#import "OFReadFailedException.h" + +#include + +struct XInputCapabilitiesEx { + XINPUT_CAPABILITIES capabilities; + WORD vendorID; + WORD productID; + WORD versionNumber; + WORD unknown1; + DWORD unknown2; +}; + +static WINAPI DWORD (*XInputGetStateFuncPtr)(DWORD, XINPUT_STATE *); +static WINAPI DWORD (*XInputGetCapabilitiesExFuncPtr)(DWORD, DWORD, DWORD, + struct XInputCapabilitiesEx *); +static const char *XInputVersion; + +@implementation OFXInputGameController +@synthesize vendorID = _vendorID, productID = _productID; +@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; +@synthesize rightAnalogStickPosition = _rightAnalogStickPosition; + ++ (void)initialize +{ + HMODULE module; + + if (self != [OFXInputGameController class]) + return; + + if ((module = LoadLibraryA("xinput1_4.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, "XInputGetState"); + XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD, + DWORD, struct XInputCapabilitiesEx *)) + GetProcAddress(module, "XInputGetCapabilitiesEx"); + XInputVersion = "1.4"; + } else if ((module = LoadLibraryA("xinput1_3.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, "XInputGetState"); + XInputVersion = "1.3"; + } else if ((module = LoadLibraryA("xinput9_1_0.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, "XInputGetState"); + XInputVersion = "9.1.0"; + } +} + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + OFMutableArray *controllers = [OFMutableArray array]; + + if (XInputGetStateFuncPtr != NULL) { + void *pool = objc_autoreleasePoolPush(); + + for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { + OFGameController *controller; + + @try { + controller = [[[OFXInputGameController alloc] + of_initWithIndex: i] autorelease]; + } @catch (OFInitializationFailedException *e) { + /* Controller does not exist. */ + continue; + } + + [controllers addObject: controller]; + } + + objc_autoreleasePoolPop(pool); + } + + [controllers makeImmutable]; + + return controllers; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)of_initWithIndex: (DWORD)index +{ + self = [super init]; + + @try { + XINPUT_STATE state = { 0 }; + + if (XInputGetStateFuncPtr(index, &state) == + ERROR_DEVICE_NOT_CONNECTED) + @throw [OFInitializationFailedException exception]; + + _index = index; + + if (XInputGetCapabilitiesExFuncPtr != NULL) { + struct XInputCapabilitiesEx capabilities; + + if (XInputGetCapabilitiesExFuncPtr(1, _index, + XINPUT_FLAG_GAMEPAD, &capabilities) == + ERROR_SUCCESS) { + _vendorID = [[OFNumber alloc] + initWithUnsignedShort: + capabilities.vendorID]; + _productID = [[OFNumber alloc] + initWithUnsignedShort: + capabilities.productID]; + } + } + + _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 16]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_vendorID release]; + [_productID release]; + [_pressedButtons release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + XINPUT_STATE state = { 0 }; + + if (XInputGetStateFuncPtr(_index, &state) != ERROR_SUCCESS) + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: sizeof(state) + errNo: 0]; + + [_pressedButtons removeAllObjects]; + + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) + [_pressedButtons addObject: OFGameControllerNorthButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) + [_pressedButtons addObject: OFGameControllerSouthButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X) + [_pressedButtons addObject: OFGameControllerWestButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) + [_pressedButtons addObject: OFGameControllerEastButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) + [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) + [_pressedButtons addObject: + OFGameControllerRightShoulderButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) + [_pressedButtons addObject: OFGameControllerLeftStickButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) + [_pressedButtons addObject: OFGameControllerRightStickButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) + [_pressedButtons addObject: OFGameControllerDPadUpButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) + [_pressedButtons addObject: OFGameControllerDPadDownButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) + [_pressedButtons addObject: OFGameControllerDPadLeftButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) + [_pressedButtons addObject: OFGameControllerDPadRightButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) + [_pressedButtons addObject: OFGameControllerStartButton]; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) + [_pressedButtons addObject: OFGameControllerSelectButton]; + + _leftTriggerPressure = (float)state.Gamepad.bLeftTrigger / 255; + _rightTriggerPressure = (float)state.Gamepad.bRightTrigger / 255; + + if (_leftTriggerPressure > 0) + [_pressedButtons addObject: OFGameControllerLeftTriggerButton]; + if (_rightTriggerPressure > 0) + [_pressedButtons addObject: OFGameControllerRightTriggerButton]; + + _leftAnalogStickPosition = OFMakePoint( + (float)state.Gamepad.sThumbLX / + (state.Gamepad.sThumbLX < 0 ? -INT16_MIN : INT16_MAX), + -(float)state.Gamepad.sThumbLY / + (state.Gamepad.sThumbLY < 0 ? -INT16_MIN : INT16_MAX)); + _rightAnalogStickPosition = OFMakePoint( + (float)state.Gamepad.sThumbRX / + (state.Gamepad.sThumbRX < 0 ? -INT16_MIN : INT16_MAX), + -(float)state.Gamepad.sThumbRY / + (state.Gamepad.sThumbRY < 0 ? -INT16_MIN : INT16_MAX)); +} + +- (OFString *)name +{ + return [OFString stringWithFormat: @"XInput %s device", XInputVersion]; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons +{ + return [OFSet setWithObjects: + OFGameControllerNorthButton, + OFGameControllerSouthButton, + OFGameControllerWestButton, + OFGameControllerEastButton, + OFGameControllerLeftTriggerButton, + OFGameControllerRightTriggerButton, + OFGameControllerLeftShoulderButton, + OFGameControllerRightShoulderButton, + OFGameControllerLeftStickButton, + OFGameControllerRightStickButton, + OFGameControllerDPadLeftButton, + OFGameControllerDPadRightButton, + OFGameControllerDPadUpButton, + OFGameControllerDPadDownButton, + OFGameControllerStartButton, + OFGameControllerSelectButton, nil]; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (bool)hasLeftAnalogStick +{ + return true; +} + +- (bool)hasRightAnalogStick +{ + return true; +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + if (button == OFGameControllerLeftTriggerButton) + return _leftTriggerPressure; + if (button == OFGameControllerRightTriggerButton) + return _rightTriggerPressure; + + return ([self.pressedButtons containsObject: button] ? 1 : 0); +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end DELETED src/hid/platform/Linux/OFGameController.m Index: src/hid/platform/Linux/OFGameController.m ================================================================== --- src/hid/platform/Linux/OFGameController.m +++ /dev/null @@ -1,614 +0,0 @@ -/* - * 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" - -#include -#include -#include - -#import "OFGameController.h" -#import "OFArray.h" -#import "OFFileManager.h" -#import "OFLocale.h" -#import "OFNumber.h" -#import "OFSet.h" - -#include -#include - -#import "OFInitializationFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFOpenItemFailedException.h" -#import "OFOutOfRangeException.h" -#import "OFReadFailedException.h" - -/* - * Controllers with tested correct mapping: - * - * Microsoft X-Box 360 pad [045E:028E] - * Joy-Con (L) [057E:2006] - * Joy-Con (R) [057E:2007] - * N64 Controller [057E:2019] - * Sony Interactive Entertainment DualSense Wireless Controller [054C:0CE6] - * 8BitDo Pro 2 Wired Controller [2DC8:3106] - * Stadia2SZY-0d6c [18D1:9400] - * Wireless Controller [054C:09CC] - */ - -static const uint16_t vendorIDMicrosoft = 0x045E; -static const uint16_t vendorIDNintendo = 0x057E; -static const uint16_t vendorIDSony = 0x054C; -static const uint16_t vendorIDGoogle = 0x18D1; - -/* Microsoft controllers */ -static const uint16_t productIDXbox360 = 0x028E; - -/* Nintendo controllers */ -static const uint16_t productIDLeftJoycon = 0x2006; -static const uint16_t productIDRightJoycon = 0x2007; -static const uint16_t productIDN64Controller = 0x2019; - -/* Sony controllers */ -static const uint16_t productIDDualSense = 0x0CE6; -static const uint16_t productIDDualShock4 = 0x09CC; - -/* Google controllers */ -static const uint16_t productIDStadia = 0x9400; - -@interface OFGameController () -- (instancetype)of_initWithPath: (OFString *)path OF_METHOD_FAMILY(init); -@end - -static const uint16_t buttons[] = { - BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2, - BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_THUMBL, BTN_THUMBR, - BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT, - BTN_TRIGGER_HAPPY1, BTN_TRIGGER_HAPPY2 -}; - -static OFGameControllerButton -buttonToName(uint16_t button, uint16_t vendorID, uint16_t productID) -{ - if (vendorID == vendorIDNintendo && - productID == productIDLeftJoycon) { - switch (button) { - case BTN_SELECT: - return OFGameControllerMinusButton; - case BTN_Z: - return OFGameControllerCaptureButton; - case BTN_TR: - return OFGameControllerSLButton; - case BTN_TR2: - return OFGameControllerSRButton; - } - } else if (vendorID == vendorIDNintendo && - productID == productIDRightJoycon) { - switch (button) { - case BTN_NORTH: - return OFGameControllerNorthButton; - case BTN_WEST: - return OFGameControllerWestButton; - case BTN_START: - return OFGameControllerPlusButton; - case BTN_TL: - return OFGameControllerSLButton; - case BTN_TL2: - return OFGameControllerSRButton; - } - } else if (vendorID == vendorIDNintendo && - productID == productIDN64Controller) { - switch (button) { - case BTN_A: - return OFGameControllerAButton; - case BTN_B: - return OFGameControllerBButton; - case BTN_SELECT: - return OFGameControllerCPadUpButton; - case BTN_X: - return OFGameControllerCPadDownButton; - case BTN_Y: - return OFGameControllerCPadLeftButton; - case BTN_C: - return OFGameControllerCPadRightButton; - case BTN_Z: - return OFGameControllerCaptureButton; - } - } else if (vendorID == vendorIDSony && - (productID == productIDDualSense || - productID == productIDDualShock4)) { - switch (button) { - case BTN_NORTH: - return OFGameControllerNorthButton; - case BTN_WEST: - return OFGameControllerWestButton; - } - } else if (vendorID == vendorIDGoogle && productID == productIDStadia) { - switch (button) { - case BTN_TRIGGER_HAPPY1: - return OFGameControllerAssistantButton; - case BTN_TRIGGER_HAPPY2: - return OFGameControllerCaptureButton; - } - } - - switch (button) { - case BTN_Y: - return OFGameControllerNorthButton; - case BTN_A: - return OFGameControllerSouthButton; - case BTN_X: - return OFGameControllerWestButton; - case BTN_B: - return OFGameControllerEastButton; - case BTN_TL2: - return OFGameControllerLeftTriggerButton; - case BTN_TR2: - return OFGameControllerRightTriggerButton; - case BTN_TL: - return OFGameControllerLeftShoulderButton; - case BTN_TR: - return OFGameControllerRightShoulderButton; - case BTN_THUMBL: - return OFGameControllerLeftStickButton; - case BTN_THUMBR: - return OFGameControllerRightStickButton; - case BTN_DPAD_UP: - return OFGameControllerDPadUpButton; - case BTN_DPAD_DOWN: - return OFGameControllerDPadDownButton; - case BTN_DPAD_LEFT: - return OFGameControllerDPadLeftButton; - case BTN_DPAD_RIGHT: - return OFGameControllerDPadRightButton; - case BTN_START: - return OFGameControllerStartButton; - case BTN_SELECT: - return OFGameControllerSelectButton; - case BTN_MODE: - return OFGameControllerHomeButton; - case BTN_C: - return OFGameControllerCButton; - case BTN_Z: - return OFGameControllerZButton; - } - - return nil; -} - -static float -scale(float value, float min, float max) -{ - if (value < min) - value = min; - if (value > max) - value = max; - - return ((value - min) / (max - min) * 2) - 1; -} - -@implementation OFGameController -@synthesize name = _name, buttons = _buttons; -@synthesize hasLeftAnalogStick = _hasLeftAnalogStick; -@synthesize hasRightAnalogStick = _hasRightAnalogStick; -@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; -@synthesize rightAnalogStickPosition = _rightAnalogStickPosition; - -+ (OFArray OF_GENERIC(OFGameController *) *)controllers -{ - OFMutableArray *controllers = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - - for (OFString *device in [[OFFileManager defaultManager] - contentsOfDirectoryAtPath: @"/dev/input"]) { - OFString *path; - OFGameController *controller; - - if (![device hasPrefix: @"event"]) - continue; - - path = [@"/dev/input" stringByAppendingPathComponent: device]; - - @try { - controller = [[[OFGameController alloc] - of_initWithPath: path] autorelease]; - } @catch (OFOpenItemFailedException *e) { - if (e.errNo == EACCES) - continue; - - @throw e; - } @catch (OFInvalidArgumentException *e) { - /* Not a game controller. */ - continue; - } - - [controllers addObject: controller]; - } - - [controllers sort]; - [controllers makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return controllers; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_initWithPath: (OFString *)path -{ - self = [super init]; - - @try { - OFStringEncoding encoding = [OFLocale encoding]; - unsigned long evBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, - EV_MAX) / OF_ULONG_BIT] = { 0 }; - unsigned long keyBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, - KEY_MAX) / OF_ULONG_BIT] = { 0 }; - unsigned long absBits[OFRoundUpToPowerOf2(OF_ULONG_BIT, - ABS_MAX) / OF_ULONG_BIT] = { 0 }; - struct input_id inputID; - char name[128]; - - _path = [path copy]; - - if ((_fd = open([_path cStringWithEncoding: encoding], - O_RDONLY | O_NONBLOCK)) == -1) - @throw [OFOpenItemFailedException - exceptionWithPath: _path - mode: @"r" - errNo: errno]; - - if (ioctl(_fd, EVIOCGBIT(0, sizeof(evBits)), evBits) == -1) - @throw [OFInitializationFailedException exception]; - - if (!OFBitSetIsSet(evBits, EV_KEY)) - @throw [OFInvalidArgumentException exception]; - - if (ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) == - -1) - @throw [OFInitializationFailedException exception]; - - if (!OFBitSetIsSet(keyBits, BTN_GAMEPAD) && - !OFBitSetIsSet(keyBits, BTN_DPAD_UP)) - @throw [OFInvalidArgumentException exception]; - - if (ioctl(_fd, EVIOCGID, &inputID) == -1) - @throw [OFInvalidArgumentException exception]; - - _vendorID = inputID.vendor; - _productID = inputID.product; - - if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) == -1) - @throw [OFInitializationFailedException exception]; - - _name = [[OFString alloc] initWithCString: name - encoding: encoding]; - - _buttons = [[OFMutableSet alloc] init]; - for (size_t i = 0; i < sizeof(buttons) / sizeof(*buttons); - i++) { - if (OFBitSetIsSet(keyBits, buttons[i])) { - OFGameControllerButton button = buttonToName( - buttons[i], _vendorID, _productID); - - if (button != nil) - [_buttons addObject: button]; - } - } - - _pressedButtons = [[OFMutableSet alloc] init]; - - if (OFBitSetIsSet(evBits, EV_ABS)) { - if (ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), - absBits) == -1) - @throw [OFInitializationFailedException - exception]; - - if (_vendorID == vendorIDGoogle && - _productID == productIDStadia) { - /* - * It's unclear how this can be screwed up - * *this* bad. - */ - _rightAnalogStickXBit = ABS_Z; - _rightAnalogStickYBit = ABS_RZ; - _leftTriggerPressureBit = ABS_BRAKE; - _rightTriggerPressureBit = ABS_GAS; - } else { - _rightAnalogStickXBit = ABS_RX; - _rightAnalogStickYBit = ABS_RY; - _leftTriggerPressureBit = ABS_Z; - _rightTriggerPressureBit = ABS_RZ; - } - - if (OFBitSetIsSet(absBits, ABS_X) && - OFBitSetIsSet(absBits, ABS_Y)) { - struct input_absinfo infoX, infoY; - - _hasLeftAnalogStick = true; - - if (ioctl(_fd, EVIOCGABS(ABS_X), &infoX) == -1) - @throw [OFInitializationFailedException - exception]; - - if (ioctl(_fd, EVIOCGABS(ABS_Y), &infoY) == -1) - @throw [OFInitializationFailedException - exception]; - - _leftAnalogStickMinX = infoX.minimum; - _leftAnalogStickMaxX = infoX.maximum; - _leftAnalogStickMinY = infoY.minimum; - _leftAnalogStickMaxY = infoY.maximum; - } - - if (OFBitSetIsSet(absBits, _rightAnalogStickXBit) && - OFBitSetIsSet(absBits, _rightAnalogStickYBit)) { - struct input_absinfo infoX, infoY; - - _hasRightAnalogStick = true; - - if (ioctl(_fd, EVIOCGABS(_rightAnalogStickXBit), - &infoX) == -1) - @throw [OFInitializationFailedException - exception]; - - if (ioctl(_fd, EVIOCGABS(_rightAnalogStickYBit), - &infoY) == -1) - @throw [OFInitializationFailedException - exception]; - - _rightAnalogStickMinX = infoX.minimum; - _rightAnalogStickMaxX = infoX.maximum; - _rightAnalogStickMinY = infoY.minimum; - _rightAnalogStickMaxY = infoY.maximum; - } - - if (OFBitSetIsSet(absBits, ABS_HAT0X) && - OFBitSetIsSet(absBits, ABS_HAT0Y)) { - [_buttons addObject: - OFGameControllerDPadLeftButton]; - [_buttons addObject: - OFGameControllerDPadRightButton]; - [_buttons addObject: - OFGameControllerDPadUpButton]; - [_buttons addObject: - OFGameControllerDPadDownButton]; - } - - if (OFBitSetIsSet(absBits, _leftTriggerPressureBit)) { - struct input_absinfo info; - - _hasLeftTriggerPressure = true; - - if (ioctl(_fd, EVIOCGABS( - _leftTriggerPressureBit), &info) == -1) - @throw [OFInitializationFailedException - exception]; - - _leftTriggerMinPressure = info.minimum; - _leftTriggerMaxPressure = info.maximum; - - [_buttons addObject: - OFGameControllerLeftTriggerButton]; - } - - if (OFBitSetIsSet(absBits, _rightTriggerPressureBit)) { - struct input_absinfo info; - - _hasRightTriggerPressure = true; - - if (ioctl(_fd, EVIOCGABS( - _rightTriggerPressureBit), &info) == -1) - @throw [OFInitializationFailedException - exception]; - - _rightTriggerMinPressure = info.minimum; - _rightTriggerMaxPressure = info.maximum; - - [_buttons addObject: - OFGameControllerRightTriggerButton]; - } - } - - [_buttons makeImmutable]; - - [self retrieveState]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_path release]; - - if (_fd != -1) - close(_fd); - - [_name release]; - [_buttons release]; - [_pressedButtons release]; - - [super dealloc]; -} - -- (OFNumber *)vendorID -{ - return [OFNumber numberWithUnsignedShort: _vendorID]; -} - -- (OFNumber *)productID -{ - return [OFNumber numberWithUnsignedShort: _productID]; -} - -- (void)retrieveState -{ - struct input_event event; - - for (;;) { - OFGameControllerButton button; - - errno = 0; - - if (read(_fd, &event, sizeof(event)) < (int)sizeof(event)) { - if (errno == EWOULDBLOCK) - return; - - @throw [OFReadFailedException - exceptionWithObject: self - requestedLength: sizeof(event) - errNo: errno]; - } - - switch (event.type) { - case EV_KEY: - if ((button = buttonToName(event.code, _vendorID, - _productID)) != nil) { - if (event.value) - [_pressedButtons addObject: button]; - else - [_pressedButtons removeObject: button]; - } - break; - case EV_ABS: - if (event.code == ABS_X) - _leftAnalogStickPosition.x = scale(event.value, - _leftAnalogStickMinX, _leftAnalogStickMaxX); - else if (event.code == ABS_Y) - _leftAnalogStickPosition.y = scale(event.value, - _leftAnalogStickMinY, _leftAnalogStickMaxY); - else if (event.code == _rightAnalogStickXBit) - _rightAnalogStickPosition.x = scale(event.value, - _rightAnalogStickMinX, - _rightAnalogStickMaxX); - else if (event.code == _rightAnalogStickYBit) - _rightAnalogStickPosition.y = scale(event.value, - _rightAnalogStickMinY, - _rightAnalogStickMaxY); - else if (event.code == ABS_HAT0X) { - if (event.value < 0) { - [_pressedButtons addObject: - OFGameControllerDPadLeftButton]; - [_pressedButtons removeObject: - OFGameControllerDPadRightButton]; - } else if (event.value > 0) { - [_pressedButtons addObject: - OFGameControllerDPadRightButton]; - [_pressedButtons removeObject: - OFGameControllerDPadLeftButton]; - } else { - [_pressedButtons removeObject: - OFGameControllerDPadLeftButton]; - [_pressedButtons removeObject: - OFGameControllerDPadRightButton]; - } - } else if (event.code == ABS_HAT0Y) { - if (event.value < 0) { - [_pressedButtons addObject: - OFGameControllerDPadUpButton]; - [_pressedButtons removeObject: - OFGameControllerDPadDownButton]; - } else if (event.value > 0) { - [_pressedButtons addObject: - OFGameControllerDPadDownButton]; - [_pressedButtons removeObject: - OFGameControllerDPadUpButton]; - } else { - [_pressedButtons removeObject: - OFGameControllerDPadUpButton]; - [_pressedButtons removeObject: - OFGameControllerDPadDownButton]; - } - } else if (event.code == _leftTriggerPressureBit) { - _leftTriggerPressure = scale(event.value, - _leftTriggerMinPressure, - _leftTriggerMaxPressure); - - if (_leftTriggerPressure > 0) - [_pressedButtons addObject: - OFGameControllerLeftTriggerButton]; - else - [_pressedButtons removeObject: - OFGameControllerLeftTriggerButton]; - } else if (event.code == _rightTriggerPressureBit) { - _rightTriggerPressure = scale(event.value, - _rightTriggerMinPressure, - _rightTriggerMaxPressure); - - if (_rightTriggerPressure > 0) - [_pressedButtons addObject: - OFGameControllerRightTriggerButton]; - else - [_pressedButtons removeObject: - OFGameControllerRightTriggerButton]; - } - break; - } - } -} - -- (OFComparisonResult)compare: (OFGameController *)otherController -{ - unsigned long long selfIndex, otherIndex; - - if (![otherController isKindOfClass: [OFGameController class]]) - @throw [OFInvalidArgumentException exception]; - - selfIndex = [_path substringFromIndex: 16].unsignedLongLongValue; - otherIndex = [otherController->_path substringFromIndex: 16] - .unsignedLongLongValue; - - if (selfIndex > otherIndex) - return OFOrderedDescending; - if (selfIndex < otherIndex) - return OFOrderedAscending; - - return OFOrderedSame; -} - -- (OFSet *)pressedButtons -{ - return [[_pressedButtons copy] autorelease]; -} - -- (float)pressureForButton: (OFGameControllerButton)button -{ - if (button == OFGameControllerLeftTriggerButton && - _hasLeftTriggerPressure) - return _leftTriggerPressure; - if (button == OFGameControllerRightTriggerButton && - _hasRightTriggerPressure) - return _rightTriggerPressure; - - return ([self.pressedButtons containsObject: button] ? 1 : 0); -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; -} -@end DELETED src/hid/platform/Nintendo3DS/OFGameController.m Index: src/hid/platform/Nintendo3DS/OFGameController.m ================================================================== --- src/hid/platform/Nintendo3DS/OFGameController.m +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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 "OFGameController.h" -#import "OFArray.h" -#import "OFSet.h" - -#import "OFOutOfRangeException.h" - -#define id id_3ds -#include <3ds.h> -#undef id - -@interface OFGameController () -- (instancetype)of_init OF_METHOD_FAMILY(init); -@end - -static OFArray OF_GENERIC(OFGameController *) *controllers; - -static void -initControllers(void) -{ - void *pool = objc_autoreleasePoolPush(); - - controllers = [[OFArray alloc] initWithObject: - [[[OFGameController alloc] of_init] autorelease]]; - - objc_autoreleasePoolPop(pool); -} - -@implementation OFGameController -@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; -@dynamic rightAnalogStickPosition; - -+ (OFArray OF_GENERIC(OFGameController *) *)controllers -{ - static OFOnceControl onceControl = OFOnceControlInitValue; - - OFOnce(&onceControl, initControllers); - - return [[controllers retain] autorelease]; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_init -{ - self = [super init]; - - @try { - _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 18]; - - [self retrieveState]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_pressedButtons release]; - - [super dealloc]; -} - -- (void)retrieveState -{ - u32 keys; - circlePosition pos; - - hidScanInput(); - - keys = hidKeysHeld(); - hidCircleRead(&pos); - - [_pressedButtons removeAllObjects]; - - if (keys & KEY_X) - [_pressedButtons addObject: OFGameControllerNorthButton]; - if (keys & KEY_B) - [_pressedButtons addObject: OFGameControllerSouthButton]; - if (keys & KEY_Y) - [_pressedButtons addObject: OFGameControllerWestButton]; - if (keys & KEY_A) - [_pressedButtons addObject: OFGameControllerEastButton]; - if (keys & KEY_ZL) - [_pressedButtons addObject: OFGameControllerLeftTriggerButton]; - if (keys & KEY_ZR) - [_pressedButtons addObject: OFGameControllerRightTriggerButton]; - if (keys & KEY_L) - [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; - if (keys & KEY_R) - [_pressedButtons addObject: - OFGameControllerRightShoulderButton]; - if (keys & KEY_DUP) - [_pressedButtons addObject: OFGameControllerDPadUpButton]; - if (keys & KEY_DDOWN) - [_pressedButtons addObject: OFGameControllerDPadDownButton]; - if (keys & KEY_DLEFT) - [_pressedButtons addObject: OFGameControllerDPadLeftButton]; - if (keys & KEY_DRIGHT) - [_pressedButtons addObject: OFGameControllerDPadRightButton]; - if (keys & KEY_START) - [_pressedButtons addObject: OFGameControllerStartButton]; - if (keys & KEY_SELECT) - [_pressedButtons addObject: OFGameControllerSelectButton]; - if (keys & KEY_CSTICK_UP) - [_pressedButtons addObject: OFGameControllerCPadUpButton]; - if (keys & KEY_CSTICK_DOWN) - [_pressedButtons addObject: OFGameControllerCPadDownButton]; - if (keys & KEY_CSTICK_LEFT) - [_pressedButtons addObject: OFGameControllerCPadLeftButton]; - if (keys & KEY_CSTICK_RIGHT) - [_pressedButtons addObject: OFGameControllerCPadRightButton]; - - _leftAnalogStickPosition = OFMakePoint( - (float)pos.dx / (pos.dx < 0 ? -INT16_MIN : INT16_MAX), - (float)pos.dy / (pos.dy < 0 ? -INT16_MIN : INT16_MAX)); -} - -- (OFString *)name -{ - return @"Nintendo 3DS"; -} - -- (OFNumber *)vendorID -{ - return nil; -} - -- (OFNumber *)productID -{ - return nil; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons -{ - return [OFSet setWithObjects: - OFGameControllerNorthButton, - OFGameControllerSouthButton, - OFGameControllerWestButton, - OFGameControllerEastButton, - OFGameControllerLeftTriggerButton, - OFGameControllerRightTriggerButton, - OFGameControllerRightShoulderButton, - OFGameControllerLeftShoulderButton, - OFGameControllerDPadUpButton, - OFGameControllerDPadDownButton, - OFGameControllerDPadLeftButton, - OFGameControllerDPadRightButton, - OFGameControllerStartButton, - OFGameControllerSelectButton, - OFGameControllerCPadRightButton, - OFGameControllerCPadLeftButton, - OFGameControllerCPadUpButton, - OFGameControllerCPadDownButton, nil]; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons -{ - return [[_pressedButtons copy] autorelease]; -} - -- (bool)hasLeftAnalogStick -{ - return true; -} - -- (bool)hasRightAnalogStick -{ - return false; -} - -- (float)pressureForButton: (OFGameControllerButton)button -{ - return ([self.pressedButtons containsObject: button] ? 1 : 0); -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; -} -@end DELETED src/hid/platform/NintendoDS/OFGameController.m Index: src/hid/platform/NintendoDS/OFGameController.m ================================================================== --- src/hid/platform/NintendoDS/OFGameController.m +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 "OFGameController.h" -#import "OFArray.h" -#import "OFSet.h" - -#import "OFOutOfRangeException.h" - -#define asm __asm__ -#include -#undef asm - -@interface OFGameController () -- (instancetype)of_init OF_METHOD_FAMILY(init); -@end - -static OFArray OF_GENERIC(OFGameController *) *controllers; - -static void -initControllers(void) -{ - void *pool = objc_autoreleasePoolPush(); - - controllers = [[OFArray alloc] initWithObject: - [[[OFGameController alloc] of_init] autorelease]]; - - objc_autoreleasePoolPop(pool); -} - -@implementation OFGameController -@dynamic leftAnalogStickPosition, rightAnalogStickPosition; - -+ (OFArray OF_GENERIC(OFGameController *) *)controllers -{ - static OFOnceControl onceControl = OFOnceControlInitValue; - - OFOnce(&onceControl, initControllers); - - return [[controllers retain] autorelease]; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_init -{ - self = [super init]; - - @try { - _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 12]; - - [self retrieveState]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_pressedButtons release]; - - [super dealloc]; -} - -- (void)retrieveState -{ - uint32 keys; - - scanKeys(); - keys = keysCurrent(); - - [_pressedButtons removeAllObjects]; - - if (keys & KEY_X) - [_pressedButtons addObject: OFGameControllerNorthButton]; - if (keys & KEY_B) - [_pressedButtons addObject: OFGameControllerSouthButton]; - if (keys & KEY_Y) - [_pressedButtons addObject: OFGameControllerWestButton]; - if (keys & KEY_A) - [_pressedButtons addObject: OFGameControllerEastButton]; - if (keys & KEY_L) - [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; - if (keys & KEY_R) - [_pressedButtons addObject: - OFGameControllerRightShoulderButton]; - if (keys & KEY_UP) - [_pressedButtons addObject: OFGameControllerDPadUpButton]; - if (keys & KEY_DOWN) - [_pressedButtons addObject: OFGameControllerDPadDownButton]; - if (keys & KEY_LEFT) - [_pressedButtons addObject: OFGameControllerDPadLeftButton]; - if (keys & KEY_RIGHT) - [_pressedButtons addObject: OFGameControllerDPadRightButton]; - if (keys & KEY_START) - [_pressedButtons addObject: OFGameControllerStartButton]; - if (keys & KEY_SELECT) - [_pressedButtons addObject: OFGameControllerSelectButton]; -} - -- (OFString *)name -{ - return @"Nintendo DS"; -} - -- (OFNumber *)vendorID -{ - return nil; -} - -- (OFNumber *)productID -{ - return nil; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons -{ - return [OFSet setWithObjects: - OFGameControllerNorthButton, - OFGameControllerSouthButton, - OFGameControllerWestButton, - OFGameControllerEastButton, - OFGameControllerLeftShoulderButton, - OFGameControllerRightShoulderButton, - OFGameControllerDPadUpButton, - OFGameControllerDPadDownButton, - OFGameControllerDPadLeftButton, - OFGameControllerDPadRightButton, - OFGameControllerStartButton, - OFGameControllerSelectButton, nil]; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons -{ - return [[_pressedButtons copy] autorelease]; -} - -- (bool)hasLeftAnalogStick -{ - return false; -} - -- (bool)hasRightAnalogStick -{ - return false; -} - -- (float)pressureForButton: (OFGameControllerButton)button -{ - return ([self.pressedButtons containsObject: button] ? 1 : 0); -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; -} -@end DELETED src/hid/platform/Windows/OFGameController.m Index: src/hid/platform/Windows/OFGameController.m ================================================================== --- src/hid/platform/Windows/OFGameController.m +++ /dev/null @@ -1,281 +0,0 @@ -/* - * 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 "OFGameController.h" -#import "OFArray.h" -#import "OFNumber.h" -#import "OFSet.h" - -#import "OFInitializationFailedException.h" -#import "OFReadFailedException.h" - -#include - -@interface OFGameController () -- (instancetype)of_initWithIndex: (DWORD)index OF_METHOD_FAMILY(init); -@end - -struct XInputCapabilitiesEx { - XINPUT_CAPABILITIES capabilities; - WORD vendorID; - WORD productID; - WORD versionNumber; - WORD unknown1; - DWORD unknown2; -}; - -static WINAPI DWORD (*XInputGetStateFuncPtr)(DWORD, XINPUT_STATE *); -static WINAPI DWORD (*XInputGetCapabilitiesExFuncPtr)(DWORD, DWORD, DWORD, - struct XInputCapabilitiesEx *); -static const char *XInputVersion; - -@implementation OFGameController -@synthesize vendorID = _vendorID, productID = _productID; -@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; -@synthesize rightAnalogStickPosition = _rightAnalogStickPosition; - -+ (void)initialize -{ - HMODULE module; - - if (self != [OFGameController class]) - return; - - if ((module = LoadLibraryA("xinput1_4.dll")) != NULL) { - XInputGetStateFuncPtr = - (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) - GetProcAddress(module, "XInputGetState"); - XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD, - DWORD, struct XInputCapabilitiesEx *)) - GetProcAddress(module, "XInputGetCapabilitiesEx"); - XInputVersion = "1.4"; - } else if ((module = LoadLibraryA("xinput1_3.dll")) != NULL) { - XInputGetStateFuncPtr = - (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) - GetProcAddress(module, "XInputGetState"); - XInputVersion = "1.3"; - } else if ((module = LoadLibraryA("xinput9_1_0.dll")) != NULL) { - XInputGetStateFuncPtr = - (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) - GetProcAddress(module, "XInputGetState"); - XInputVersion = "9.1.0"; - } -} - -+ (OFArray OF_GENERIC(OFGameController *) *)controllers -{ - OFMutableArray *controllers = [OFMutableArray array]; - - if (XInputGetStateFuncPtr != NULL) { - void *pool = objc_autoreleasePoolPush(); - - for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { - OFGameController *controller; - - @try { - controller = [[[OFGameController alloc] - of_initWithIndex: i] autorelease]; - } @catch (OFInitializationFailedException *e) { - /* Controller does not exist. */ - continue; - } - - [controllers addObject: controller]; - } - - objc_autoreleasePoolPop(pool); - } - - [controllers makeImmutable]; - - return controllers; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_initWithIndex: (DWORD)index -{ - self = [super init]; - - @try { - XINPUT_STATE state = { 0 }; - - if (XInputGetStateFuncPtr(index, &state) == - ERROR_DEVICE_NOT_CONNECTED) - @throw [OFInitializationFailedException exception]; - - _index = index; - - if (XInputGetCapabilitiesExFuncPtr != NULL) { - struct XInputCapabilitiesEx capabilities; - - if (XInputGetCapabilitiesExFuncPtr(1, _index, - XINPUT_FLAG_GAMEPAD, &capabilities) == - ERROR_SUCCESS) { - _vendorID = [[OFNumber alloc] - initWithUnsignedShort: - capabilities.vendorID]; - _productID = [[OFNumber alloc] - initWithUnsignedShort: - capabilities.productID]; - } - } - - _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 16]; - - [self retrieveState]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [_vendorID release]; - [_productID release]; - [_pressedButtons release]; - - [super dealloc]; -} - -- (void)retrieveState -{ - XINPUT_STATE state = { 0 }; - - if (XInputGetStateFuncPtr(_index, &state) != ERROR_SUCCESS) - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: sizeof(state) - errNo: 0]; - - [_pressedButtons removeAllObjects]; - - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) - [_pressedButtons addObject: OFGameControllerNorthButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) - [_pressedButtons addObject: OFGameControllerSouthButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X) - [_pressedButtons addObject: OFGameControllerWestButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) - [_pressedButtons addObject: OFGameControllerEastButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) - [_pressedButtons addObject: OFGameControllerLeftShoulderButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) - [_pressedButtons addObject: - OFGameControllerRightShoulderButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) - [_pressedButtons addObject: OFGameControllerLeftStickButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) - [_pressedButtons addObject: OFGameControllerRightStickButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) - [_pressedButtons addObject: OFGameControllerDPadUpButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) - [_pressedButtons addObject: OFGameControllerDPadDownButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) - [_pressedButtons addObject: OFGameControllerDPadLeftButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) - [_pressedButtons addObject: OFGameControllerDPadRightButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) - [_pressedButtons addObject: OFGameControllerStartButton]; - if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) - [_pressedButtons addObject: OFGameControllerSelectButton]; - - _leftTriggerPressure = (float)state.Gamepad.bLeftTrigger / 255; - _rightTriggerPressure = (float)state.Gamepad.bRightTrigger / 255; - - if (_leftTriggerPressure > 0) - [_pressedButtons addObject: OFGameControllerLeftTriggerButton]; - if (_rightTriggerPressure > 0) - [_pressedButtons addObject: OFGameControllerRightTriggerButton]; - - _leftAnalogStickPosition = OFMakePoint( - (float)state.Gamepad.sThumbLX / - (state.Gamepad.sThumbLX < 0 ? -INT16_MIN : INT16_MAX), - -(float)state.Gamepad.sThumbLY / - (state.Gamepad.sThumbLY < 0 ? -INT16_MIN : INT16_MAX)); - _rightAnalogStickPosition = OFMakePoint( - (float)state.Gamepad.sThumbRX / - (state.Gamepad.sThumbRX < 0 ? -INT16_MIN : INT16_MAX), - -(float)state.Gamepad.sThumbRY / - (state.Gamepad.sThumbRY < 0 ? -INT16_MIN : INT16_MAX)); -} - -- (OFString *)name -{ - return [OFString stringWithFormat: @"XInput %s device", XInputVersion]; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons -{ - return [OFSet setWithObjects: - OFGameControllerNorthButton, - OFGameControllerSouthButton, - OFGameControllerWestButton, - OFGameControllerEastButton, - OFGameControllerLeftTriggerButton, - OFGameControllerRightTriggerButton, - OFGameControllerLeftShoulderButton, - OFGameControllerRightShoulderButton, - OFGameControllerLeftStickButton, - OFGameControllerRightStickButton, - OFGameControllerDPadLeftButton, - OFGameControllerDPadRightButton, - OFGameControllerDPadUpButton, - OFGameControllerDPadDownButton, - OFGameControllerStartButton, - OFGameControllerSelectButton, nil]; -} - -- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons -{ - return [[_pressedButtons copy] autorelease]; -} - -- (bool)hasLeftAnalogStick -{ - return true; -} - -- (bool)hasRightAnalogStick -{ - return true; -} - -- (float)pressureForButton: (OFGameControllerButton)button -{ - if (button == OFGameControllerLeftTriggerButton) - return _leftTriggerPressure; - if (button == OFGameControllerRightTriggerButton) - return _rightTriggerPressure; - - return ([self.pressedButtons containsObject: button] ? 1 : 0); -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; -} -@end