Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -40,10 +40,11 @@ tests/DerivedData tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/big_dictionary_msgpack_gz.m +tests/gamecontroller/gamecontroller_tests tests/iOS.xcodeproj/*.pbxuser tests/iOS.xcodeproj/project.xcworkspace tests/iOS.xcodeproj/xcuserdata tests/objc_sync/objc_sync tests/plugin/Info.plist Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -23,10 +23,11 @@ OFData+MessagePackParsing.m \ OFDate.m \ OFDictionary.m \ OFEnumerator.m \ OFFileManager.m \ + OFGameController.m \ OFGZIPStream.m \ OFHMAC.m \ OFINICategory.m \ OFINIFile.m \ OFIRI.m \ ADDED src/OFGameController.h Index: src/OFGameController.h ================================================================== --- /dev/null +++ src/OFGameController.h @@ -0,0 +1,107 @@ +/* + * 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 "OFObject.h" +#import "OFString.h" + +OF_ASSUME_NONNULL_BEGIN + +/** @file */ + +@class OFArray OF_GENERIC(ObjectType); +@class OFMutableSet OF_GENERIC(ObjectType); +@class OFSet OF_GENERIC(ObjectType); + +/** + * @brief A class for reading state from a game controller. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFGameController: OFObject +{ +#ifdef OF_LINUX + OFString *_path; + int _fd; + uint16_t _vendorID, _productID; + OFString *_name; + OFMutableSet *_buttons, *_pressedButtons; + bool _hasLeftAnalogStick, _hasRightAnalogStick; + OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; + int32_t _leftAnalogStickMinX, _leftAnalogStickMaxX; + int32_t _leftAnalogStickMinY, _leftAnalogStickMaxY; + int32_t _rightAnalogStickMinX, _rightAnalogStickMaxX; + int32_t _rightAnalogStickMinY, _rightAnalogStickMaxY; +#endif +} + +#ifdef OF_HAVE_CLASS_PROPERTIES +@property (class, readonly, nonatomic) + OFArray *controllers; +#endif + +/** + * @brief The name of the controller. + */ +@property (readonly, nonatomic, copy) OFString *name; + +/** + * @brief The buttons the controller has. + */ +@property (readonly, nonatomic, copy) OFSet OF_GENERIC(OFString *) *buttons; + +/** + * @brief The currently pressed buttons on the controller. + */ +@property (readonly, nonatomic, copy) + OFSet OF_GENERIC(OFString *) *pressedButtons; + +/** + * @brief Whether the controller has a left analog stick. + */ +@property (readonly, nonatomic) bool hasLeftAnalogStick; + +/** + * @brief The position of the left analog stick. + * + * The range is from (-1, -1) to (1, 1). + */ +@property (readonly, nonatomic) OFPoint leftAnalogStickPosition; + +/** + * @brief Whether the controller has a right analog stick. + */ +@property (readonly, nonatomic) bool hasRightAnalogStick; + +/** + * @brief The position of the right analog stick. + * + * The range is from (-1, -1) to (1, 1). + */ +@property (readonly, nonatomic) OFPoint rightAnalogStickPosition; + +/** + * @brief Returns the available controllers. + * + * @return The available controllers + */ ++ (OFArray OF_GENERIC(OFGameController *) *)controllers; + +- (instancetype)init OF_UNAVAILABLE; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFGameController.m Index: src/OFGameController.m ================================================================== --- /dev/null +++ src/OFGameController.m @@ -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 + * . + */ + +#include "config.h" + +#import "OFGameController.h" +#import "OFArray.h" + +#import "OFOutOfRangeException.h" + +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) +# include "platform/Linux/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 +{ + return [OFArray array]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} +@end +#endif Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -146,10 +146,11 @@ #import "OFOptionsParser.h" #import "OFTimer.h" #import "OFRunLoop.h" #import "OFMatrix4x4.h" +#import "OFGameController.h" #ifdef OF_WINDOWS # import "OFWindowsRegistryKey.h" #endif ADDED src/platform/Linux/OFGameController.m Index: src/platform/Linux/OFGameController.m ================================================================== --- /dev/null +++ src/platform/Linux/OFGameController.m @@ -0,0 +1,474 @@ +/* + * 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 "OFSet.h" + +#include +#include + +#import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFOpenItemFailedException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" + +static const uint16_t vendorIDNintendo = 0x057E; +static const uint16_t productIDN64Controller = 0x2019; + +@interface OFGameController () +- (instancetype)of_initWithPath: (OFString *)path OF_METHOD_FAMILY(init); +- (void)of_processEvents; +@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 +}; + +static OFString * +buttonToName(uint16_t button, uint16_t vendorID, uint16_t productID) +{ + if (vendorID == vendorIDNintendo && + productID == productIDN64Controller) { + switch (button) { + case BTN_TL2: + return @"Z"; + case BTN_Y: + return @"C-Stick Left"; + case BTN_C: + return @"C-Stick Right"; + case BTN_SELECT: + return @"C-Stick Up"; + case BTN_X: + return @"C-Stick Down"; + case BTN_MODE: + return @"Home"; + case BTN_Z: + return @"Capture"; + case BTN_THUMBL: + case BTN_THUMBR: + return nil; + } + } + + switch (button) { + case BTN_A: + return @"A"; + case BTN_B: + return @"B"; + case BTN_C: + return @"C"; + case BTN_X: + return @"X"; + case BTN_Y: + return @"Y"; + case BTN_Z: + return @"Z"; + case BTN_TL: + return @"L"; + case BTN_TR: + return @"R"; + case BTN_TL2: + return @"ZL"; + case BTN_TR2: + return @"ZR"; + case BTN_SELECT: + return @"Select"; + case BTN_START: + return @"Start"; + case BTN_MODE: + return @"Mode"; + case BTN_THUMBL: + return @"Thumb L"; + case BTN_THUMBR: + return @"Thumb R"; + case BTN_DPAD_UP: + return @"D-Pad Up"; + case BTN_DPAD_DOWN: + return @"D-Pad Down"; + case BTN_DPAD_LEFT: + return @"D-Pad Left"; + case BTN_DPAD_RIGHT: + return @"D-Pad Right"; + } + + 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; + ++ (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)) + @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++) { + OFString *buttonName = + buttonToName(buttons[i], _vendorID, _productID); + + if (buttonName != nil) + [_buttons addObject: buttonName]; + } + + _pressedButtons = [[OFMutableSet alloc] init]; + + if (OFBitSetIsSet(evBits, EV_ABS)) { + if (ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(absBits)), + absBits) == -1) + @throw [OFInitializationFailedException + exception]; + + 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, ABS_RX) && + OFBitSetIsSet(absBits, ABS_RY)) { + struct input_absinfo infoX, infoY; + + _hasRightAnalogStick = true; + + if (ioctl(_fd, EVIOCGABS(ABS_RX), &infoX) == -1) + @throw [OFInitializationFailedException + exception]; + + if (ioctl(_fd, EVIOCGABS(ABS_RY), &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: @"D-Pad Left"]; + [_buttons addObject: @"D-Pad Right"]; + [_buttons addObject: @"D-Pad Up"]; + [_buttons addObject: @"D-Pad Down"]; + } + + if (OFBitSetIsSet(absBits, ABS_Z)) + [_buttons addObject: @"ZL"]; + if (OFBitSetIsSet(absBits, ABS_RZ)) + [_buttons addObject: @"ZR"]; + } + + [_buttons makeImmutable]; + } @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]; +} + +- (void)of_processEvents +{ + struct input_event event; + + for (;;) { + OFString *name; + + 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 ((name = buttonToName(event.code, _vendorID, + _productID)) != nil) { + if (event.value) + [_pressedButtons addObject: name]; + else + [_pressedButtons removeObject: name]; + } + break; + case EV_ABS: + switch (event.code) { + case ABS_X: + _leftAnalogStickPosition.x = scale(event.value, + _leftAnalogStickMinX, _leftAnalogStickMaxX); + break; + case ABS_Y: + _leftAnalogStickPosition.y = scale(event.value, + _leftAnalogStickMinY, _leftAnalogStickMaxY); + break; + case ABS_RX: + _rightAnalogStickPosition.x = scale(event.value, + _rightAnalogStickMinX, + _rightAnalogStickMaxX); + break; + case ABS_RY: + _rightAnalogStickPosition.y = scale(event.value, + _rightAnalogStickMinY, + _rightAnalogStickMaxY); + break; + case ABS_HAT0X: + if (event.value < 0) { + [_pressedButtons addObject: + @"D-Pad Left"]; + [_pressedButtons removeObject: + @"D-Pad Right"]; + } else if (event.value > 0) { + [_pressedButtons addObject: + @"D-Pad Right"]; + [_pressedButtons removeObject: + @"D-Pad Left"]; + } else { + [_pressedButtons removeObject: + @"D-Pad Left"]; + [_pressedButtons removeObject: + @"D-Pad Right"]; + } + break; + case ABS_HAT0Y: + if (event.value < 0) { + [_pressedButtons addObject: + @"D-Pad Up"]; + [_pressedButtons removeObject: + @"D-Pad Down"]; + } else if (event.value > 0) { + [_pressedButtons addObject: + @"D-Pad Down"]; + [_pressedButtons removeObject: + @"D-Pad Up"]; + } else { + [_pressedButtons removeObject: + @"D-Pad Up"]; + [_pressedButtons removeObject: + @"D-Pad Down"]; + } + break; + case ABS_Z: + if (event.value > 0) + [_pressedButtons addObject: @"ZL"]; + else + [_pressedButtons removeObject: @"ZL"]; + break; + case ABS_RZ: + if (event.value > 0) + [_pressedButtons addObject: @"ZR"]; + else + [_pressedButtons removeObject: @"ZR"]; + break; + } + + 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 +{ + [self of_processEvents]; + return [[_pressedButtons copy] autorelease]; +} + +- (OFPoint)leftAnalogStickPosition +{ + [self of_processEvents]; + return _leftAnalogStickPosition; +} + +- (OFPoint)rightAnalogStickPosition +{ + [self of_processEvents]; + return _rightAnalogStickPosition; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end ADDED src/platform/Nintendo3DS/OFGameController.m Index: src/platform/Nintendo3DS/OFGameController.m ================================================================== --- /dev/null +++ src/platform/Nintendo3DS/OFGameController.m @@ -0,0 +1,158 @@ +/* + * 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 +@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 +{ + return [super init]; +} + +- (OFString *)name +{ + return @"Nintendo 3DS"; +} + +- (OFSet *)buttons +{ + return [OFSet setWithObjects: @"A", @"B", @"Select", @"Start", + @"D-Pad Right", @"D-Pad Left", @"D-Pad Up", @"D-Pad Down", @"R", + @"L", @"X", @"Y", @"ZL", @"ZR", @"C-Stick Right", @"C-Stick Left", + @"C-Stick Up", @"C-Stick Down", nil]; +} + +- (OFSet *)pressedButtons +{ + OFMutableSet *pressedButtons = [OFMutableSet setWithCapacity: 18]; + u32 keys; + + hidScanInput(); + keys = hidKeysHeld(); + + if (keys & KEY_A) + [pressedButtons addObject: @"A"]; + if (keys & KEY_B) + [pressedButtons addObject: @"A"]; + if (keys & KEY_SELECT) + [pressedButtons addObject: @"Select"]; + if (keys & KEY_START) + [pressedButtons addObject: @"Start"]; + if (keys & KEY_DRIGHT) + [pressedButtons addObject: @"D-Pad Right"]; + if (keys & KEY_DLEFT) + [pressedButtons addObject: @"D-Pad Left"]; + if (keys & KEY_DUP) + [pressedButtons addObject: @"D-Pad Up"]; + if (keys & KEY_DDOWN) + [pressedButtons addObject: @"D-Pad Down"]; + if (keys & KEY_R) + [pressedButtons addObject: @"R"]; + if (keys & KEY_L) + [pressedButtons addObject: @"L"]; + if (keys & KEY_X) + [pressedButtons addObject: @"X"]; + if (keys & KEY_Y) + [pressedButtons addObject: @"Y"]; + if (keys & KEY_ZL) + [pressedButtons addObject: @"ZL"]; + if (keys & KEY_ZR) + [pressedButtons addObject: @"ZR"]; + if (keys & KEY_CSTICK_RIGHT) + [pressedButtons addObject: @"C-Stick Right"]; + if (keys & KEY_CSTICK_LEFT) + [pressedButtons addObject: @"C-Stick Left"]; + if (keys & KEY_CSTICK_UP) + [pressedButtons addObject: @"C-Stick Up"]; + if (keys & KEY_CSTICK_DOWN) + [pressedButtons addObject: @"C-Stick Down"]; + + [pressedButtons makeImmutable]; + + return pressedButtons; +} + +- (bool)hasLeftAnalogStick +{ + return true; +} + +- (bool)hasRightAnalogStick +{ + return false; +} + +- (OFPoint)leftAnalogStickPosition +{ + circlePosition pos; + hidCircleRead(&pos); + + return OFMakePoint( + (float)pos.dx / (pos.dx < 0 ? -INT16_MIN : INT16_MAX), + (float)pos.dy / (pos.dy < 0 ? -INT16_MIN : INT16_MAX)); +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end ADDED src/platform/NintendoDS/OFGameController.m Index: src/platform/NintendoDS/OFGameController.m ================================================================== --- /dev/null +++ src/platform/NintendoDS/OFGameController.m @@ -0,0 +1,135 @@ +/* + * 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 +{ + return [super init]; +} + +- (OFString *)name +{ + return @"Nintendo DS"; +} + +- (OFSet *)buttons +{ + return [OFSet setWithObjects: @"A", @"B", @"Select", @"Start", + @"D-Pad Right", @"D-Pad Left", @"D-Pad Up", @"D-Pad Down", @"R", + @"L", @"X", @"Y", nil]; +} + +- (OFSet *)pressedButtons +{ + OFMutableSet *pressedButtons = [OFMutableSet setWithCapacity: 12]; + uint32 keys; + + scanKeys(); + keys = keysCurrent(); + + if (keys & KEY_A) + [pressedButtons addObject: @"A"]; + if (keys & KEY_B) + [pressedButtons addObject: @"A"]; + if (keys & KEY_SELECT) + [pressedButtons addObject: @"Select"]; + if (keys & KEY_START) + [pressedButtons addObject: @"Start"]; + if (keys & KEY_RIGHT) + [pressedButtons addObject: @"D-Pad Right"]; + if (keys & KEY_LEFT) + [pressedButtons addObject: @"D-Pad Left"]; + if (keys & KEY_UP) + [pressedButtons addObject: @"D-Pad Up"]; + if (keys & KEY_DOWN) + [pressedButtons addObject: @"D-Pad Down"]; + if (keys & KEY_R) + [pressedButtons addObject: @"R"]; + if (keys & KEY_L) + [pressedButtons addObject: @"L"]; + if (keys & KEY_X) + [pressedButtons addObject: @"X"]; + if (keys & KEY_Y) + [pressedButtons addObject: @"Y"]; + + [pressedButtons makeImmutable]; + + return pressedButtons; +} + +- (bool)hasLeftAnalogStick +{ + return false; +} + +- (bool)hasRightAnalogStick +{ + return false; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name]; +} +@end Index: src/test/OTAppDelegate.m ================================================================== --- src/test/OTAppDelegate.m +++ src/test/OTAppDelegate.m @@ -321,32 +321,28 @@ if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) break; VIDEO_WaitVSync(); } -#elif defined(OF_NINTENDO_DS) +#elif defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) [OFStdOut setForegroundColor: [OFColor silver]]; [OFStdOut writeLine: @"Press A to continue"]; for (;;) { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_A) - break; - } -#elif defined(OF_NINTENDO_3DS) - [OFStdOut setForegroundColor: [OFColor silver]]; - [OFStdOut writeLine: @"Press A to continue"]; - - for (;;) { - hidScanInput(); - - if (hidKeysDown() & KEY_A) - break; - - gspWaitForVBlank(); + void *pool = objc_autoreleasePoolPush(); + OFGameController *controller = + [[OFGameController controllers] objectAtIndex: 0]; + + if ([controller.pressedButtons containsObject: @"A"]) + break; + +# if defined(OF_NINTENDO_DS) + swiWaitForVBlank(); +# elif defined(OF_NINTENDO_3DS) + gspWaitForVBlank(); +# endif + objc_autoreleasePoolPop(pool); } #elif defined(OF_NINTENDO_SWITCH) [OFStdOut setForegroundColor: [OFColor silver]]; [OFStdOut writeLine: @"Press A to continue"]; ADDED tests/gamecontroller/GameControllerTests.m Index: tests/gamecontroller/GameControllerTests.m ================================================================== --- /dev/null +++ tests/gamecontroller/GameControllerTests.m @@ -0,0 +1,89 @@ +/* + * 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 "OFApplication.h" +#import "OFArray.h" +#import "OFColor.h" +#import "OFGameController.h" +#import "OFSet.h" +#import "OFStdIOStream.h" +#import "OFThread.h" + +@interface GameControllerTests: OFObject +@end + +OF_APPLICATION_DELEGATE(GameControllerTests) + +@implementation GameControllerTests +- (void)applicationDidFinishLaunching: (OFNotification *)notification +{ + OFArray *controllers = [OFGameController controllers]; + + [OFStdOut clear]; + + for (;;) { + [OFStdOut setCursorPosition: OFMakePoint(0, 0)]; + + for (OFGameController *controller in controllers) { + OFArray *buttons = + controller.buttons.allObjects.sortedArray; + size_t i = 0; + + [OFStdOut setForegroundColor: [OFColor green]]; + [OFStdOut writeLine: controller.name]; + + for (OFString *button in buttons) { + bool pressed = [controller.pressedButtons + containsObject: button]; + + [OFStdOut setForegroundColor: (pressed + ? [OFColor yellow] : [OFColor gray])]; + + [OFStdOut writeFormat: @"[%@]", button]; + + if (++i == 5) { + [OFStdOut writeString: @"\n"]; + i = 0; + } else + [OFStdOut writeString: @" "]; + } + [OFStdOut setForegroundColor: [OFColor gray]]; + [OFStdOut writeString: @"\n"]; + + if (controller.hasLeftAnalogStick) { + OFPoint position = + controller.leftAnalogStickPosition; + [OFStdOut writeFormat: @"(%5.2f, %5.2f) ", + position.x, position.y]; + } + if (controller.hasRightAnalogStick) { + OFPoint position = + controller.rightAnalogStickPosition; + [OFStdOut writeFormat: @"(%5.2f, %5.2f)", + position.x, position.y]; + } + [OFStdOut writeString: @"\n"]; + } + + [OFThread sleepForTimeInterval: 1.f / 60.f]; + } +} +@end ADDED tests/gamecontroller/Makefile Index: tests/gamecontroller/Makefile ================================================================== --- /dev/null +++ tests/gamecontroller/Makefile @@ -0,0 +1,65 @@ +include ../../extra.mk + +PROG_NOINST = gamecontroller_tests${PROG_SUFFIX} +SRCS = GameControllerTests.m + +include ../../buildsys.mk + +.PHONY: run +run: + rm -f libobjfw.so.${OBJFW_LIB_MAJOR} + rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} + rm -f objfw${OBJFW_LIB_MAJOR}.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR} + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} + rm -f objfwrt${OBJFWRT_LIB_MAJOR}.dll + rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + if test -f ../../src/libobjfw.so; then \ + ${LN_S} ../../src/libobjfw.so libobjfw.so.${OBJFW_LIB_MAJOR}; \ + ${LN_S} ../../src/libobjfw.so \ + libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ + elif test -f ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; then \ + ${LN_S} ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} \ + libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ + fi + if test -f ../../src/objfw${OBJFW_LIB_MAJOR}.dll; then \ + ${LN_S} ../../src/objfw${OBJFW_LIB_MAJOR}.dll \ + objfw${OBJFW_LIB_MAJOR}.dll; \ + fi + if test -f ../../src/libobjfw.dylib; then \ + ${LN_S} ../../src/libobjfw.dylib \ + libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ + fi + if test -f ../../src/runtime/libobjfwrt.so; then \ + ${LN_S} ../../src/runtime/libobjfwrt.so \ + libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ + ${LN_S} ../../src/runtime/libobjfwrt.so \ + libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ + elif test -f ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; then \ + ${LN_S} ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ + fi + if test -f ../../src/runtime/objfwrt${OBJFWRT_LIB_MAJOR}.dll; then \ + ${LN_S} ../../src/runtime/objfwrt${OBJFWRT_LIB_MAJOR}.dll \ + objfwrt${OBJFWRT_LIB_MAJOR}.dll; \ + fi + if test -f ../../src/runtime/libobjfwrt.dylib; then \ + ${LN_S} ../../src/runtime/libobjfwrt.dylib \ + libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ + fi + LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ + DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ + LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ + ${WRAPPER} ./${PROG_NOINST}; EXIT=$$?; \ + rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ + rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ + rm -f objfw${OBJFW_LIB_MAJOR}.dll; \ + rm -f libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ + rm -f objfwrt${OBJFWRT_LIB_MAJOR}.dll; \ + rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ + exit $$EXIT + +CPPFLAGS += -I../../src -I../../src/exceptions -I../../src/runtime -I../.. +LIBS := -L../../src -lobjfw -L../../src/runtime ${RUNTIME_LIBS} ${LIBS} +LD = ${OBJC}