Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -15,11 +15,10 @@ */.deps .deps .git aclocal.m4 autom4te.cache -boot.dol buildsys.mk config.h config.h.in config.log config.status @@ -28,10 +27,12 @@ extra.mk generators/library/gen_libraries generators/unicode/gen_tables src/Info.plist src/bridge/Info.plist +src/hid/Info.plist +src/hid/ObjFWHID.oc src/libobjfw.* src/objfw-defs.h src/runtime/Info.plist src/runtime/libobjfwrt.* src/test/libobjfwtest.a @@ -40,10 +41,16 @@ tests/DerivedData tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/big_dictionary_msgpack_gz.m +tests/boot.dol +tests/gamecontroller/boot.dol +tests/gamecontroller/gamecontroller_tests +tests/gamecontroller/gamecontroller_tests.3dsx +tests/gamecontroller/gamecontroller_tests.arm9 +tests/gamecontroller/gamecontroller_tests.nds tests/iOS.xcodeproj/*.pbxuser tests/iOS.xcodeproj/project.xcworkspace tests/iOS.xcodeproj/xcuserdata tests/objc_sync/objc_sync tests/plugin/Info.plist Index: Doxyfile ================================================================== --- Doxyfile +++ Doxyfile @@ -1,8 +1,8 @@ PROJECT_NAME = "ObjFW" OUTPUT_DIRECTORY = docs/ -INPUT = src src/exceptions src/runtime src/test +INPUT = src src/exceptions src/runtime src/test src/hid FILE_PATTERNS = *.h *.m HTML_OUTPUT = . HAVE_DOT = NO GENERATE_LATEX = NO HIDE_UNDOC_CLASSES = YES Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -170,11 +170,11 @@ OBJFW_OBJCFLAGS="$OBJFW_OBJCFLAGS $flags" CPPFLAGS="$CPPFLAGS -DGEKKO -I$DEVKITPRO/libogc/include" OBJFW_CPPFLAGS="$OBJFW_CPPFLAGS -DGEKKO -I\$DEVKITPRO/libogc/include" LDFLAGS="$LDFLAGS -mrvl -mcpu=750 -meabi -mhard-float" LIBS="$LIBS -L$DEVKITPRO/libogc/lib/wii -lfat -logc" - TESTS_LIBS="$TESTS_LIBS -lwiiuse -lbte" + HID_LIBS="$HID_LIBS -lwiiuse -lbte" enable_shared="no" enable_threads="no" # TODO with_tls="no" AC_DEFINE(OF_WII, 1, [Whether we are compiling for Wii]) @@ -411,12 +411,15 @@ AC_SUBST(OBJFW_SHARED_LIB, '${LIB_PREFIX}objfw${LIB_SUFFIX}') AC_SUBST(EXCEPTIONS_LIB_A, "exceptions.lib.a") AC_SUBST(FORWARDING_LIB_A, "forwarding.lib.a") AC_SUBST(LOOKUP_ASM_LIB_A, "lookup-asm.lib.a") + AC_SUBST(OBJFWHID_SHARED_LIB, '${LIB_PREFIX}objfwhid${LIB_SUFFIX}') + BUILDSYS_FRAMEWORK([ AC_SUBST(OBJFW_FRAMEWORK, "ObjFW.framework") + AC_SUBST(OBJFWHID_FRAMEWORK, "ObjFWHID.framework") build_framework="yes" ]) BUILDSYS_BUNDLE([ AC_SUBST(TESTPLUGIN_BUNDLE, "TestPlugin.bundle") @@ -423,19 +426,22 @@ ]) ], [ AC_DEFINE(OF_NO_SHARED, 1, [Whether no shared library was built]) AC_SUBST(LIBOBJFW_DEP, "../src/libobjfw.a") AC_SUBST(LIBOBJFW_DEP_LVL2, "../../src/libobjfw.a") + AC_SUBST(LIBOBJFWHID_DEP, "../src/hid/libobjfwhid.a") + AC_SUBST(LIBOBJFWHID_DEP_LVL2, "../../src/hid/libobjfwhid.a") ]) AS_IF([test x"$build_framework" = x"yes"], [ TESTS_LIBS="-framework ObjFW \${RUNTIME_FRAMEWORK_LIBS} $TESTS_LIBS" - TESTS_LIBS="-F../src -F../src/runtime $TESTS_LIBS" + TESTS_LIBS="-framework ObjFWHID $TESTS_LIBS" + TESTS_LIBS="-F../src -F../src/runtime -F../src/hid $TESTS_LIBS" ], [ - TESTS_LIBS="\${RUNTIME_LIBS} $TESTS_LIBS" - TESTS_LIBS="-L../src/runtime $TESTS_LIBS" + TESTS_LIBS="-L../src/runtime \${RUNTIME_LIBS} $TESTS_LIBS" TESTS_LIBS="-L../src -lobjfw $TESTS_LIBS" + TESTS_LIBS="-L../src/hid -lobjfwhid $HID_LIBS $TESTS_LIBS" ]) AC_ARG_ENABLE(static, AS_HELP_STRING([--enable-static], [build static library])) AS_IF([test x"$enable_shared" = x"no"], [ enable_static="yes" @@ -443,10 +449,12 @@ AS_IF([test x"$enable_static" = x"yes"], [ AC_SUBST(OBJFW_STATIC_LIB, "libobjfw.a") AC_SUBST(EXCEPTIONS_A, "exceptions.a") AC_SUBST(FORWARDING_A, "forwarding.a") AC_SUBST(LOOKUP_ASM_A, "lookup-asm.a") + + AC_SUBST(OBJFWHID_STATIC_LIB, "libobjfwhid.a") ]) AC_DEFINE_UNQUOTED(PLUGIN_SUFFIX, "$PLUGIN_SUFFIX", [Suffix for plugins]) AS_IF([test x"$enable_files" != x"no" -a x"$PLUGIN_SUFFIX" != x""], [ AC_SUBST(USE_SRCS_PLUGINS, '${SRCS_PLUGINS}') @@ -2375,16 +2383,19 @@ AC_SUBST(DEP_ASFLAGS, '${DEP_OBJCFLAGS}') AC_SUBST(OBJFW_CPPFLAGS) AC_SUBST(OBJFW_OBJCFLAGS) +AC_SUBST(HID_LIBS) AC_SUBST(TESTS_LIBS) AC_CONFIG_FILES([ buildsys.mk extra.mk src/Info.plist + src/hid/Info.plist + src/hid/ObjFWHID.oc tests/Info.plist utils/objfw-config ]) AC_CONFIG_HEADERS([config.h src/objfw-defs.h]) AC_OUTPUT Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -25,10 +25,17 @@ OBJFWTLS_STATIC_LIB = @OBJFWTLS_STATIC_LIB@ OBJFWTLS_FRAMEWORK = @OBJFWTLS_FRAMEWORK@ OBJFWTLS_LIB_MAJOR = 1 OBJFWTLS_LIB_MINOR = 0 OBJFWTLS_LIB_PATCH = 2 + +OBJFWHID_SHARED_LIB = @OBJFWHID_SHARED_LIB@ +OBJFWHID_STATIC_LIB = @OBJFWHID_STATIC_LIB@ +OBJFWHID_FRAMEWORK = @OBJFWHID_FRAMEWORK@ +OBJFWHID_LIB_MAJOR = 1 +OBJFWHID_LIB_MINOR = 0 +OBJFWHID_LIB_PATCH = 0 BIN_PREFIX = @BIN_PREFIX@ BRIDGE = @BRIDGE@ CVINCLUDE_INLINE_H = @CVINCLUDE_INLINE_H@ ENCODINGS_A = @ENCODINGS_A@ @@ -36,11 +43,14 @@ ENCODINGS_SRCS = @ENCODINGS_SRCS@ EXCEPTIONS_A = @EXCEPTIONS_A@ EXCEPTIONS_LIB_A = @EXCEPTIONS_LIB_A@ FORWARDING_A = @FORWARDING_A@ FORWARDING_LIB_A = @FORWARDING_LIB_A@ +HID_LIBS = @HID_LIBS@ LIBBASES_M = @LIBBASES_M@ +LIBOBJFWHID_DEP = @LIBOBJFWHID_DEP@ +LIBOBJFWHID_DEP_LVL2 = @LIBOBJFWHID_DEP_LVL2@ LIBOBJFWRT_DEP = @LIBOBJFWRT_DEP@ LIBOBJFWRT_DEP_LVL2 = @LIBOBJFWRT_DEP_LVL2@ LIBOBJFW_DEP = @LIBOBJFW_DEP@ LIBOBJFW_DEP_LVL2 = @LIBOBJFW_DEP_LVL2@ LOOKUP_ASM_A = @LOOKUP_ASM_A@ Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -1,9 +1,9 @@ include ../extra.mk SUBDIRS = ${RUNTIME} exceptions encodings forwarding -SUBDIRS_AFTER = ${BRIDGE} ${TLS} test +SUBDIRS_AFTER = ${BRIDGE} ${TLS} hid test DISTCLEAN = Info.plist objfw-defs.h SHARED_LIB = ${OBJFW_SHARED_LIB} STATIC_LIB = ${OBJFW_STATIC_LIB} FRAMEWORK = ${OBJFW_FRAMEWORK} Index: src/OFStdIOStream.h ================================================================== --- src/OFStdIOStream.h +++ src/OFStdIOStream.h @@ -69,10 +69,21 @@ /** * @brief The number of rows, or -1 if there is no underlying terminal or the * number of rows could not be queried. */ @property (readonly, nonatomic) int rows; + +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) || \ + defined(DOXYGEN) +/** + * @brief Sets up a console for @ref OFStdOut / @ref OFStdErr output on systems + * that don't have a console by default. + * + * @note This method is only available on Wii, Nintendo DS and Nintendo 3DS. + */ ++ (void)setUpConsole; +#endif - (instancetype)init OF_UNAVAILABLE; /** * @brief Sets the foreground color on the underlying terminal. Does nothing if Index: src/OFStdIOStream.m ================================================================== --- src/OFStdIOStream.m +++ src/OFStdIOStream.m @@ -59,16 +59,35 @@ #endif #ifdef OF_MSDOS # include #endif + +#ifdef OF_WII +# define asm __asm__ +# include +# undef asm +#endif #ifdef OF_WII_U # define BOOL WUT_BOOL # include # undef BOOL #endif + +#ifdef OF_NINTENDO_DS +# define asm __asm__ +# include +# undef asm +#endif + +#ifdef OF_NINTENDO_3DS +/* Newer versions of libctru started using id as a parameter name. */ +# define id id_3ds +# include <3ds.h> +# undef id +#endif /* References for static linking */ #ifdef OF_WINDOWS void _reference_to_OFWin32ConsoleStdIOStream(void) @@ -259,10 +278,47 @@ OFStdErr = [[OFStdIOStream alloc] of_initWithFileDescriptor: fd]; # endif } #endif + +#if defined(OF_WII) ++ (void)setUpConsole +{ + GXRModeObj *mode; + void *nextFB; + + VIDEO_Init(); + + mode = VIDEO_GetPreferredMode(NULL); + nextFB = MEM_K0_TO_K1(SYS_AllocateFramebuffer(mode)); + VIDEO_Configure(mode); + VIDEO_SetNextFramebuffer(nextFB); + VIDEO_SetBlack(FALSE); + VIDEO_Flush(); + + VIDEO_WaitVSync(); + if (mode->viTVMode & VI_NON_INTERLACE) + VIDEO_WaitVSync(); + + CON_InitEx(mode, 2, 2, mode->fbWidth - 4, mode->xfbHeight - 4); + VIDEO_ClearFrameBuffer(mode, nextFB, COLOR_BLACK); +} +#elif defined(OF_NINTENDO_DS) ++ (void)setUpConsole +{ + consoleDemoInit(); +} +#elif defined(OF_NINTENDO_3DS) ++ (void)setUpConsole +{ + gfxInitDefault(); + atexit(gfxExit); + + consoleInit(GFX_BOTTOM, NULL); +} +#endif - (instancetype)init { OF_INVALID_INIT_METHOD } Index: src/OFThread.h ================================================================== --- src/OFThread.h +++ src/OFThread.h @@ -244,10 +244,20 @@ /** * @brief Yields a processor voluntarily and moves the thread to the end of the * queue for its priority. */ + (void)yield; + +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) || \ + defined(DOXYGEN) +/** + * @brief Waits for the vertical blank. + * + * @note This method is only available on Wii, Nintendo DS and Nintendo 3DS. + */ ++ (void)waitForVerticalBlank; +#endif #ifdef OF_HAVE_THREADS /** * @brief Terminates the current thread, letting it return `nil`. */ Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -40,17 +40,29 @@ # include # undef Class #endif #ifdef OF_WII +# define asm __asm__ # define nanosleep ogc_nanosleep +# include # include # undef nanosleep +# undef asm +#endif + +#ifdef OF_NINTENDO_DS +# define asm __asm__ +# include +# undef asm #endif #ifdef OF_NINTENDO_3DS -# include <3ds/svc.h> +/* Newer versions of libctru started using id as a parameter name. */ +# define id id_3ds +# include <3ds.h> +# undef id #endif #import "OFThread.h" #import "OFThread+Private.h" #ifdef OF_HAVE_ATOMIC_OPS @@ -310,10 +322,27 @@ sched_yield(); #else [self sleepForTimeInterval: 0]; #endif } + +#if defined(OF_WII) ++ (void)waitForVerticalBlank +{ + VIDEO_WaitVSync(); +} +#elif defined(OF_NINTENDO_DS) ++ (void)waitForVerticalBlank +{ + swiWaitForVBlank(); +} +#elif defined(OF_NINTENDO_3DS) ++ (void)waitForVerticalBlank +{ + gspWaitForVBlank(); +} +#endif #ifdef OF_HAVE_THREADS + (void)terminate { [self terminateWithObject: nil]; ADDED src/hid/Info.plist.in Index: src/hid/Info.plist.in ================================================================== --- /dev/null +++ src/hid/Info.plist.in @@ -0,0 +1,22 @@ + + + + + CFBundleExecutable + ObjFWHID + CFBundleName + ObjFWHID + CFBundleIdentifier + im.nil.objfw.hid + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleVersion + @BUNDLE_VERSION@ + CFBundleShortVersionString + @BUNDLE_SHORT_VERSION@ + MinimumOSVersion + 9.0 + + ADDED src/hid/Makefile Index: src/hid/Makefile ================================================================== --- /dev/null +++ src/hid/Makefile @@ -0,0 +1,50 @@ +include ../../extra.mk + +DISTCLEAN = Info.plist + +SHARED_LIB = ${OBJFWHID_SHARED_LIB} +STATIC_LIB = ${OBJFWHID_STATIC_LIB} +FRAMEWORK = ${OBJFWHID_FRAMEWORK} +LIB_MAJOR = ${OBJFWHID_LIB_MAJOR} +LIB_MINOR = ${OBJFWHID_LIB_MINOR} +LIB_PATCH = ${OBJFWHID_LIB_PATCH} + +SRCS = OFGameController.m + +INCLUDES := ${SRCS:.m=.h} \ + ObjFWHID.h + +includesubdir = ObjFWHID + +include ../../buildsys.mk + +install-extra: + i=ObjFWHID.oc; \ + ${INSTALL_STATUS}; \ + if ${MKDIR_P} ${DESTDIR}${libdir}/objfw-config && \ + ${INSTALL} -m 644 $$i ${DESTDIR}${libdir}/objfw-config/$$i; then \ + ${INSTALL_OK}; \ + else \ + ${INSTALL_FAILED}; \ + fi + +uninstall-extra: + i=ObjFWHID.oc; \ + if test -f ${DESTDIR}${libdir}/objfw-config/$$i; then \ + if rm -f ${DESTDIR}${libdir}/objfw-config/$$i; then \ + ${DELETE_OK}; \ + else \ + ${DELETE_FAILED}; \ + fi \ + fi + rmdir ${DESTDIR}${libdir}/objfw-config >/dev/null 2>&1 || true + +CPPFLAGS += -I. \ + -I.. \ + -I../.. \ + -I../exceptions \ + -I../runtime \ + -DOBJFWHID_LOCAL_INCLUDES +LD = ${OBJC} +FRAMEWORK_LIBS := -F.. -framework ObjFW ${RUNTIME_LIBS} ${LIBS} +LIBS := -L.. -lobjfw -L../runtime ${RUNTIME_LIBS} ${LIBS} ADDED src/hid/OFEvdevGameController.h Index: src/hid/OFEvdevGameController.h ================================================================== --- /dev/null +++ src/hid/OFEvdevGameController.h @@ -0,0 +1,52 @@ +/* + * 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; + bool _discardUntilReport; + unsigned long *_keyBits; + uint16_t _vendorID, _productID; + OFString *_name; + OFMutableSet OF_GENERIC(OFGameControllerButton) *_buttons; + OFMutableSet OF_GENERIC(OFGameControllerButton) *_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); +- (void)of_pollState; +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFEvdevGameController.m Index: src/hid/OFEvdevGameController.m ================================================================== --- /dev/null +++ src/hid/OFEvdevGameController.m @@ -0,0 +1,707 @@ +/* + * 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_X, BTN_Y, 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_Z: + return OFGameControllerCaptureButton; + case BTN_TR: + return @"SL"; + case BTN_TR2: + return @"SR"; + } + } else if (vendorID == vendorIDNintendo && + productID == productIDRightJoycon) { + switch (button) { + case BTN_NORTH: + return OFGameControllerNorthButton; + case BTN_WEST: + return OFGameControllerWestButton; + case BTN_TL: + return @"SL"; + case BTN_TL2: + return @"SR"; + } + } else if (vendorID == vendorIDNintendo && + productID == productIDN64Controller) { + switch (button) { + case BTN_B: + return OFGameControllerWestButton; + case BTN_SELECT: + case BTN_X: + case BTN_Y: + case BTN_C: + /* Used to emulate right analog stick. */ + return nil; + 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 @"Assistant"; + 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; + } + + 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)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 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]; + + _keyBits = OFAllocZeroedMemory(OFRoundUpToPowerOf2(OF_ULONG_BIT, + KEY_MAX) / OF_ULONG_BIT, sizeof(unsigned long)); + + if (ioctl(_fd, EVIOCGBIT(EV_KEY, OFRoundUpToPowerOf2( + OF_ULONG_BIT, KEY_MAX) / OF_ULONG_BIT * + sizeof(unsigned long)), _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)) + _hasLeftAnalogStick = true; + + if (OFBitSetIsSet(absBits, _rightAnalogStickXBit) && + OFBitSetIsSet(absBits, _rightAnalogStickYBit)) + _hasRightAnalogStick = true; + + if (_vendorID == vendorIDNintendo && + _productID == productIDN64Controller && + OFBitSetIsSet(_keyBits, BTN_Y) && + OFBitSetIsSet(_keyBits, BTN_C) && + OFBitSetIsSet(_keyBits, BTN_SELECT) && + OFBitSetIsSet(_keyBits, BTN_X)) + _hasRightAnalogStick = true; + + 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)) { + _hasLeftTriggerPressure = true; + [_buttons addObject: + OFGameControllerLeftTriggerButton]; + } + + if (OFBitSetIsSet(absBits, _rightTriggerPressureBit)) { + _hasRightTriggerPressure = true; + [_buttons addObject: + OFGameControllerRightTriggerButton]; + } + } + + [_buttons makeImmutable]; + + @try { + [self of_pollState]; + } @catch (OFReadFailedException *e) { + @throw [OFInitializationFailedException exception]; + } + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_path release]; + + if (_fd != -1) + close(_fd); + + OFFreeMemory(_keyBits); + + [_name release]; + [_buttons release]; + [_pressedButtons release]; + + [super dealloc]; +} + +- (OFNumber *)vendorID +{ + return [OFNumber numberWithUnsignedShort: _vendorID]; +} + +- (OFNumber *)productID +{ + return [OFNumber numberWithUnsignedShort: _productID]; +} + +- (void)of_pollState +{ + unsigned long keyState[OFRoundUpToPowerOf2(OF_ULONG_BIT, KEY_MAX) / + OF_ULONG_BIT] = { 0 }; + + if (ioctl(_fd, EVIOCGKEY(sizeof(keyState)), &keyState) == -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(keyState) + errNo: errno]; + + [_pressedButtons removeAllObjects]; + + for (size_t i = 0; i < sizeof(buttons) / sizeof(*buttons); + i++) { + if (OFBitSetIsSet(_keyBits, buttons[i]) && + OFBitSetIsSet(keyState, buttons[i])) { + OFGameControllerButton button = buttonToName( + buttons[i], _vendorID, _productID); + + if (button != nil) + [_pressedButtons addObject: button]; + } + } + + if (_hasLeftAnalogStick) { + struct input_absinfo infoX, infoY; + + if (ioctl(_fd, EVIOCGABS(ABS_X), &infoX) == -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(infoX) + errNo: errno]; + + if (ioctl(_fd, EVIOCGABS(ABS_Y), &infoY) == -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(infoY) + errNo: errno]; + + _leftAnalogStickMinX = infoX.minimum; + _leftAnalogStickMaxX = infoX.maximum; + _leftAnalogStickMinY = infoY.minimum; + _leftAnalogStickMaxY = infoY.maximum; + _leftAnalogStickPosition.x = scale(infoX.value, + _leftAnalogStickMinX, _leftAnalogStickMaxX); + _leftAnalogStickPosition.y = scale(infoY.value, + _leftAnalogStickMinY, _leftAnalogStickMaxY); + } + + if (_vendorID == vendorIDNintendo && + _productID == productIDN64Controller && + OFBitSetIsSet(_keyBits, BTN_Y) && OFBitSetIsSet(_keyBits, BTN_C) && + OFBitSetIsSet(_keyBits, BTN_SELECT) && + OFBitSetIsSet(_keyBits, BTN_X)) + _rightAnalogStickPosition = OFMakePoint( + -OFBitSetIsSet(keyState, BTN_Y) + + OFBitSetIsSet(keyState, BTN_C), + -OFBitSetIsSet(keyState, BTN_SELECT) + + OFBitSetIsSet(keyState, BTN_X)); + else if (_hasRightAnalogStick) { + struct input_absinfo infoX, infoY; + + if (ioctl(_fd, EVIOCGABS(_rightAnalogStickXBit), &infoX) == -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(infoX) + errNo: errno]; + + if (ioctl(_fd, EVIOCGABS(_rightAnalogStickYBit), &infoY) == -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(infoY) + errNo: errno]; + + _rightAnalogStickMinX = infoX.minimum; + _rightAnalogStickMaxX = infoX.maximum; + _rightAnalogStickMinY = infoY.minimum; + _rightAnalogStickMaxY = infoY.maximum; + _rightAnalogStickPosition.x = scale(infoX.value, + _rightAnalogStickMinX, _rightAnalogStickMaxX); + _rightAnalogStickPosition.y = scale(infoY.value, + _rightAnalogStickMinY, _rightAnalogStickMaxY); + } + + if (_hasLeftTriggerPressure) { + struct input_absinfo info; + + if (ioctl(_fd, EVIOCGABS( _leftTriggerPressureBit), &info) == + -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(info) + errNo: errno]; + + _leftTriggerMinPressure = info.minimum; + _leftTriggerMaxPressure = info.maximum; + _leftTriggerPressure = scale(info.value, + _leftTriggerMinPressure, + _leftTriggerMaxPressure); + } + + if (_hasRightTriggerPressure) { + struct input_absinfo info; + + if (ioctl(_fd, EVIOCGABS(_rightTriggerPressureBit), &info) == + -1) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(info) + errNo: errno]; + + _rightTriggerMinPressure = info.minimum; + _rightTriggerMaxPressure = info.maximum; + _leftTriggerPressure = scale(info.value, + _leftTriggerMinPressure, + _leftTriggerMaxPressure); + } +} + +- (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]; + } + + if (_discardUntilReport) { + if (event.type == EV_SYN && event.value == SYN_REPORT) { + _discardUntilReport = false; + [self of_pollState]; + } + + continue; + } + + switch (event.type) { + case EV_SYN: + if (event.value == SYN_DROPPED) { + _discardUntilReport = true; + continue; + } + break; + case EV_KEY: + if ((button = buttonToName(event.code, _vendorID, + _productID)) != nil) { + if (event.value) + [_pressedButtons addObject: button]; + else + [_pressedButtons removeObject: button]; + } + + /* Use C buttons to emulate right analog stick */ + if (_vendorID == vendorIDNintendo && + _productID == productIDN64Controller) { + switch (event.code) { + case BTN_Y: + _rightAnalogStickPosition.x += + (event.value ? -1 : 1); + break; + case BTN_C: + _rightAnalogStickPosition.x += + (event.value ? 1 : -1); + break; + case BTN_SELECT: + _rightAnalogStickPosition.y += + (event.value ? -1 : 1); + break; + case BTN_X: + _rightAnalogStickPosition.y += + (event.value ? 1 : -1); + break; + } + } + + 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 [super pressureForButton: button]; +} +@end ADDED src/hid/OFGameController.h Index: src/hid/OFGameController.h ================================================================== --- /dev/null +++ src/hid/OFGameController.h @@ -0,0 +1,258 @@ +/* + * 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 + * . + */ + +#ifdef OBJFWHID_LOCAL_INCLUDES +# import "OFObject.h" +# import "OFString.h" +#else +# if defined(__has_feature) && __has_feature(modules) +@import ObjFW; +# else +# import +# import +# endif +#endif + +OF_ASSUME_NONNULL_BEGIN + +/** @file */ + +@class OFArray OF_GENERIC(ObjectType); +@class OFMutableSet OF_GENERIC(ObjectType); +@class OFNumber; +@class OFSet OF_GENERIC(ObjectType); + +/** + * @brief A button on a controller. + * + * Possible values are: + * + * * @ref OFGameControllerNorthButton + * * @ref OFGameControllerSouthButton + * * @ref OFGameControllerWestButton + * * @ref OFGameControllerEastButton + * * @ref OFGameControllerLeftTriggerButton + * * @ref OFGameControllerRightTriggerButton + * * @ref OFGameControllerLeftShoulderButton + * * @ref OFGameControllerRightShoulderButton + * * @ref OFGameControllerLeftStickButton + * * @ref OFGameControllerRightStickButton + * * @ref OFGameControllerDPadUpButton + * * @ref OFGameControllerDPadDownButton + * * @ref OFGameControllerDPadLeftButton + * * @ref OFGameControllerDPadRightButton + * * @ref OFGameControllerStartButton + * * @ref OFGameControllerSelectButton + * * @ref OFGameControllerHomeButton + * * @ref OFGameControllerCaptureButton + */ +typedef OFConstantString *OFGameControllerButton; + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief The north button on a game controller's diamond pad. + */ +extern const OFGameControllerButton OFGameControllerNorthButton; + +/** + * @brief The south button on a game controller's diamond pad. + */ +extern const OFGameControllerButton OFGameControllerSouthButton; + +/** + * @brief The west button on a game controller's diamond pad. + */ +extern const OFGameControllerButton OFGameControllerWestButton; + +/** + * @brief The east button on a game controller's diamond pad. + */ +extern const OFGameControllerButton OFGameControllerEastButton; + +/** + * @brief The left trigger button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerLeftTriggerButton; + +/** + * @brief The right trigger button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerRightTriggerButton; + +/** + * @brief The left shoulder button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerLeftShoulderButton; + +/** + * @brief The right shoulder button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerRightShoulderButton; + +/** + * @brief The left stick button (pressing the left stick) on a game controller. + */ +extern const OFGameControllerButton OFGameControllerLeftStickButton; + +/** + * @brief The right stick button (pressing the right stick) on a game + * controller. + */ +extern const OFGameControllerButton OFGameControllerRightStickButton; + +/** + * @brief The D-Pad Up button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerDPadUpButton; + +/** + * @brief The D-Pad Down button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerDPadDownButton; + +/** + * @brief The D-Pad Left button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerDPadLeftButton; + +/** + * @brief The D-Pad Right button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerDPadRightButton; + +/** + * @brief The Start button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerStartButton; + +/** + * @brief The Select button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerSelectButton; + +/** + * @brief The Home button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerHomeButton; + +/** + * @brief The Capture button on a game controller. + */ +extern const OFGameControllerButton OFGameControllerCaptureButton; +#ifdef __cplusplus +} +#endif + +/** + * @brief A class for reading state from a game controller. + */ +@interface OFGameController: OFObject +#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 vendor ID of the controller or `nil` if unavailable. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFNumber *vendorID; + +/** + * @brief The product ID of the controller or `nil` if unavailable. + */ +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFNumber *productID; + +/** + * @brief The buttons the controller has. + */ +@property (readonly, nonatomic, copy) + OFSet OF_GENERIC(OFGameControllerButton) *buttons; + +/** + * @brief The currently pressed buttons on the controller. + */ +@property (readonly, nonatomic, copy) + OFSet OF_GENERIC(OFGameControllerButton) *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. + * + * @note The Nintendo 64 controller has no right analog stick, however, the C + * buttons are used to emulate one. + */ +@property (readonly, nonatomic) bool hasRightAnalogStick; + +/** + * @brief The position of the right analog stick. + * + * The range is from (-1, -1) to (1, 1). + * + * @note The Nintendo 64 controller has no right analog stick, however, the C + * buttons are used to emulate one. + */ +@property (readonly, nonatomic) OFPoint rightAnalogStickPosition; + +/** + * @brief Returns the available controllers. + * + * @return The available controllers + */ ++ (OFArray OF_GENERIC(OFGameController *) *)controllers; + +/** + * @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. + * + * @throw OFReadFailedException The controller's state could not be read + */ +- (void)retrieveState; + +/** + * @brief Returns how hard the specified button is pressed. + * + * The returned value is in the range from 0 to 1. + * + * @param button The button for which to return how hard it is pressed. + * @return How hard the specified button is pressed + */ +- (float)pressureForButton: (OFGameControllerButton)button; +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFGameController.m Index: src/hid/OFGameController.m ================================================================== --- /dev/null +++ src/hid/OFGameController.m @@ -0,0 +1,150 @@ +/* + * 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" + +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) +# include "OFEvdevGameController.h" +#endif +#ifdef OF_WINDOWS +# include "OFXInputGameController.h" +#endif +#ifdef OF_WII +# include "OFWiiGameController.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"; +const OFGameControllerButton OFGameControllerLeftTriggerButton = + @"Left Trigger"; +const OFGameControllerButton OFGameControllerRightTriggerButton = + @"Right Trigger"; +const OFGameControllerButton OFGameControllerLeftShoulderButton = + @"Left Shoulder"; +const OFGameControllerButton OFGameControllerRightShoulderButton = + @"Right Shoulder"; +const OFGameControllerButton OFGameControllerLeftStickButton = @"Left Stick"; +const OFGameControllerButton OFGameControllerRightStickButton = @"Right Stick"; +const OFGameControllerButton OFGameControllerDPadUpButton = @"D-Pad Up"; +const OFGameControllerButton OFGameControllerDPadDownButton = @"D-Pad Down"; +const OFGameControllerButton OFGameControllerDPadLeftButton = @"D-Pad Left"; +const OFGameControllerButton OFGameControllerDPadRightButton = @"D-Pad Right"; +const OFGameControllerButton OFGameControllerStartButton = @"Start"; +const OFGameControllerButton OFGameControllerSelectButton = @"Select"; +const OFGameControllerButton OFGameControllerHomeButton = @"Home"; +const OFGameControllerButton OFGameControllerCaptureButton = @"Capture"; + +@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_WII) + return [OFWiiGameController controllers]; +#elif defined(OF_NINTENDO_DS) + return [OFNintendoDSGameController controllers]; +#elif defined(OF_NINTENDO_3DS) + return [OFNintendo3DSGameController controllers]; +#else + return [OFArray array]; +#endif +} + +- (instancetype)init +{ + if ([self isMemberOfClass: [OFGameController class]]) { + @try { + [self doesNotRecognizeSelector: _cmd]; + } @catch (id e) { + [self release]; + @throw e; + } + + abort(); + } + + return [super init]; +} + +- (OFNumber *)vendorID +{ + return nil; +} + +- (OFNumber *)productID +{ + return nil; +} + +- (void)retrieveState +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + return ([self.pressedButtons containsObject: button] ? 1 : 0); +} + +- (OFString *)description +{ + if (self.vendorID != nil && self.productID != nil) + return [OFString stringWithFormat: + @"<%@: %@ [%04X:%04X]>", + self.class, self.name, self.vendorID.unsignedShortValue, + self.productID.unsignedShortValue]; + else + return [OFString stringWithFormat: @"<%@: %@>", + self.class, self.name]; +} +@end + +#if defined(OF_LINUX) && defined(OF_HAVE_FILES) +# include "OFEvdevGameController.m" +#endif +#ifdef OF_WINDOWS +# include "OFXInputGameController.m" +#endif +#ifdef OF_WII +# include "OFWiiGameController.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 OF_GENERIC(OFGameControllerButton) *_pressedButtons; + OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; +} +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFNintendo3DSGameController.m Index: src/hid/OFNintendo3DSGameController.m ================================================================== --- /dev/null +++ src/hid/OFNintendo3DSGameController.m @@ -0,0 +1,184 @@ +/* + * 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; + +@implementation OFNintendo3DSGameController +@synthesize leftAnalogStickPosition = _leftAnalogStickPosition; +@synthesize rightAnalogStickPosition = _rightAnalogStickPosition; + ++ (void)initialize +{ + void *pool; + + if (self != [OFNintendo3DSGameController class]) + return; + + pool = objc_autoreleasePoolPush(); + controllers = [[OFArray alloc] initWithObject: + [[[OFNintendo3DSGameController alloc] init] autorelease]]; + objc_autoreleasePoolPop(pool); +} + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + return controllers; +} + +- (instancetype)init +{ + self = [super init]; + + @try { + _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 14]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_pressedButtons release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + u32 keys; + circlePosition leftPos, rightPos; + + hidScanInput(); + + keys = hidKeysHeld(); + hidCircleRead(&leftPos); + hidCstickRead(&rightPos); + + [_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 (leftPos.dx > 150) + leftPos.dx = 150; + if (leftPos.dx < -150) + leftPos.dx = -150; + if (leftPos.dy > 150) + leftPos.dy = 150; + if (leftPos.dy < -150) + leftPos.dy = -150; + + if (rightPos.dx > 150) + rightPos.dx = 150; + if (rightPos.dx < -150) + rightPos.dx = -150; + if (rightPos.dy > 150) + rightPos.dy = 150; + if (rightPos.dy < -150) + rightPos.dy = -150; + + _leftAnalogStickPosition = OFMakePoint( + (float)leftPos.dx / 150, -(float)leftPos.dy / 150); + _rightAnalogStickPosition = OFMakePoint( + (float)rightPos.dx / 150, -(float)rightPos.dy / 150); +} + +- (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, nil]; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (bool)hasLeftAnalogStick +{ + return true; +} + +- (bool)hasRightAnalogStick +{ + return true; +} +@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 OF_GENERIC(OFGameControllerButton) *_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,148 @@ +/* + * 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; + +@implementation OFNintendoDSGameController ++ (void)initialize +{ + void *pool; + + if (self != [OFNintendoDSGameController class]) + return; + + pool = objc_autoreleasePoolPush(); + controllers = [[OFArray alloc] initWithObject: + [[[OFNintendoDSGameController alloc] init] autorelease]]; + objc_autoreleasePoolPop(pool); +} + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + return controllers; +} + +- (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; +} +@end ADDED src/hid/OFWiiGameController.h Index: src/hid/OFWiiGameController.h ================================================================== --- /dev/null +++ src/hid/OFWiiGameController.h @@ -0,0 +1,37 @@ +/* + * 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 OFWiiGameController: OFGameController +{ + int32_t _index; + uint32_t _type; + OFMutableSet OF_GENERIC(OFGameControllerButton) *_pressedButtons; + OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition; + float _leftTriggerPressure, _rightTriggerPressure; +} + +- (instancetype)of_initWithIndex: (int32_t)index + type: (uint32_t)type OF_METHOD_FAMILY(init); +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OFWiiGameController.m Index: src/hid/OFWiiGameController.m ================================================================== --- /dev/null +++ src/hid/OFWiiGameController.m @@ -0,0 +1,301 @@ +/* + * 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 "OFWiiGameController.h" +#import "OFMutableSet.h" + +#import "OFInitializationFailedException.h" +#import "OFNotImplementedException.h" +#import "OFReadFailedException.h" + +#define asm __asm__ +#include +#undef asm + +static float +scale(float value, float min, float max, float center) +{ + if (value < min) + value = min; + if (value > max) + value = max; + + if (value >= center) + return (value - center) / (max - center); + else + return (value - center) / (center - min); +} + +@implementation OFWiiGameController ++ (void)initialize +{ + if (self != [OFWiiGameController class]) + return; + + if (WPAD_Init() != WPAD_ERR_NONE) + @throw [OFInitializationFailedException + exceptionWithClass: self]; +} + ++ (OFArray OF_GENERIC(OFGameController *) *)controllers +{ + OFMutableArray *controllers = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + + for (int32_t i = 0; i < WPAD_MAX_WIIMOTES; i++) { + uint32_t type; + + if (WPAD_Probe(i, &type) == WPAD_ERR_NONE && + (type == WPAD_EXP_NONE || type == WPAD_EXP_NUNCHUK || + type == WPAD_EXP_CLASSIC)) + [controllers addObject: [[[OFWiiGameController alloc] + of_initWithIndex: i + type: type] autorelease]]; + } + + [controllers makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return controllers; +} + +- (instancetype)of_initWithIndex: (int32_t)index type: (uint32_t)type +{ + self = [super init]; + + @try { + _index = index; + _type = type; + + _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 15]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_pressedButtons release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + WPADData *data; + + if (WPAD_ReadPending(_index, NULL) < WPAD_ERR_NONE) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: sizeof(WPADData) + errNo: 0]; + + data = WPAD_Data(_index); + + [_pressedButtons removeAllObjects]; + + if (data->btns_h & WPAD_BUTTON_A) + [_pressedButtons addObject: OFGameControllerEastButton]; + if (data->btns_h & WPAD_BUTTON_B) + [_pressedButtons addObject: OFGameControllerRightTriggerButton]; + if (data->btns_h & WPAD_BUTTON_1) + [_pressedButtons addObject: OFGameControllerWestButton]; + if (data->btns_h & WPAD_BUTTON_2) + [_pressedButtons addObject: OFGameControllerSouthButton]; + if (data->btns_h & WPAD_BUTTON_UP) + [_pressedButtons addObject: OFGameControllerDPadUpButton]; + if (data->btns_h & WPAD_BUTTON_DOWN) + [_pressedButtons addObject: OFGameControllerDPadDownButton]; + if (data->btns_h & WPAD_BUTTON_LEFT) + [_pressedButtons addObject: OFGameControllerDPadLeftButton]; + if (data->btns_h & WPAD_BUTTON_RIGHT) + [_pressedButtons addObject: OFGameControllerDPadRightButton]; + if (data->btns_h & WPAD_BUTTON_PLUS) + [_pressedButtons addObject: OFGameControllerStartButton]; + if (data->btns_h & WPAD_BUTTON_MINUS) + [_pressedButtons addObject: OFGameControllerSelectButton]; + if (data->btns_h & WPAD_BUTTON_HOME) + [_pressedButtons addObject: OFGameControllerHomeButton]; + + if (_type == WPAD_EXP_NUNCHUK) { + joystick_t *js = &data->exp.nunchuk.js; + + if (data->btns_h & WPAD_NUNCHUK_BUTTON_C) + [_pressedButtons addObject: + OFGameControllerLeftShoulderButton]; + if (data->btns_h & WPAD_NUNCHUK_BUTTON_Z) + [_pressedButtons addObject: + OFGameControllerLeftTriggerButton]; + + _leftAnalogStickPosition = OFMakePoint( + scale(js->pos.x, js->min.x, js->max.x, js->center.x), + -scale(js->pos.y, js->min.y, js->max.y, js->center.y)); + } else if (_type == WPAD_EXP_CLASSIC) { + joystick_t *ljs = &data->exp.classic.ljs; + joystick_t *rjs = &data->exp.classic.rjs; + + if (data->btns_h & WPAD_CLASSIC_BUTTON_X) + [_pressedButtons addObject: + OFGameControllerNorthButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_B) + [_pressedButtons addObject: + OFGameControllerSouthButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_Y) + [_pressedButtons addObject: OFGameControllerWestButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_A) + [_pressedButtons addObject: OFGameControllerEastButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_FULL_L) + [_pressedButtons addObject: + OFGameControllerLeftTriggerButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_FULL_R) + [_pressedButtons addObject: + OFGameControllerRightTriggerButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_ZL) + [_pressedButtons addObject: + OFGameControllerLeftShoulderButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_ZR) + [_pressedButtons addObject: + OFGameControllerRightShoulderButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_UP) + [_pressedButtons addObject: + OFGameControllerDPadUpButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_DOWN) + [_pressedButtons addObject: + OFGameControllerDPadDownButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_LEFT) + [_pressedButtons addObject: + OFGameControllerDPadLeftButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_RIGHT) + [_pressedButtons addObject: + OFGameControllerDPadRightButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_PLUS) + [_pressedButtons addObject: + OFGameControllerStartButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_MINUS) + [_pressedButtons addObject: + OFGameControllerSelectButton]; + if (data->btns_h & WPAD_CLASSIC_BUTTON_HOME) + [_pressedButtons addObject: OFGameControllerHomeButton]; + + _leftAnalogStickPosition = OFMakePoint( + scale(ljs->pos.x, ljs->min.x, ljs->max.x, ljs->center.x), + -scale(ljs->pos.y, ljs->min.y, ljs->max.y, ljs->center.y)); + _rightAnalogStickPosition = OFMakePoint( + scale(rjs->pos.x, rjs->min.x, rjs->max.x, rjs->center.x), + -scale(rjs->pos.y, rjs->min.y, rjs->max.y, rjs->center.y)); + + _leftTriggerPressure = data->exp.classic.l_shoulder; + _rightTriggerPressure = data->exp.classic.r_shoulder; + } +} + +- (OFString *)name +{ + if (_type == WPAD_EXP_NUNCHUK) + return @"Wiimote with Nunchuk"; + else if (_type == WPAD_EXP_CLASSIC) + return @"Wiimote with Classic Controller"; + else + return @"Wiimote"; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons +{ + OFMutableSet *buttons = [OFMutableSet setWithCapacity: 15]; + + [buttons addObject: OFGameControllerSouthButton]; + [buttons addObject: OFGameControllerRightTriggerButton]; + [buttons addObject: OFGameControllerWestButton]; + [buttons addObject: OFGameControllerEastButton]; + [buttons addObject: OFGameControllerDPadUpButton]; + [buttons addObject: OFGameControllerDPadDownButton]; + [buttons addObject: OFGameControllerDPadLeftButton]; + [buttons addObject: OFGameControllerDPadRightButton]; + [buttons addObject: OFGameControllerStartButton]; + [buttons addObject: OFGameControllerSelectButton]; + [buttons addObject: OFGameControllerHomeButton]; + + if (_type == WPAD_EXP_NUNCHUK) { + [buttons addObject: OFGameControllerLeftShoulderButton]; + [buttons addObject: OFGameControllerLeftTriggerButton]; + } else if (_type == WPAD_EXP_CLASSIC) { + [buttons addObject: OFGameControllerNorthButton]; + [buttons addObject: OFGameControllerLeftTriggerButton]; + [buttons addObject: OFGameControllerLeftShoulderButton]; + [buttons addObject: OFGameControllerRightShoulderButton]; + } + + [buttons makeImmutable]; + + return buttons; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons +{ + return [[_pressedButtons copy] autorelease]; +} + +- (bool)hasLeftAnalogStick +{ + return (_type == WPAD_EXP_NUNCHUK || _type == WPAD_EXP_CLASSIC); +} + +- (bool)hasRightAnalogStick +{ + return (_type == WPAD_EXP_CLASSIC); +} + +- (OFPoint)leftAnalogStickPosition +{ + if (_type != WPAD_EXP_NUNCHUK && _type != WPAD_EXP_CLASSIC) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + + return _leftAnalogStickPosition; +} + +- (OFPoint)rightAnalogStickPosition +{ + if (_type != WPAD_EXP_CLASSIC) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + + return _rightAnalogStickPosition; +} + +- (float)pressureForButton: (OFGameControllerButton)button +{ + if (_type == WPAD_EXP_CLASSIC) { + if (button == OFGameControllerLeftTriggerButton) + return _leftTriggerPressure; + if (button == OFGameControllerRightTriggerButton) + return _rightTriggerPressure; + } + + return [super pressureForButton: button]; +} +@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 OF_GENERIC(OFGameControllerButton) *_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,284 @@ +/* + * 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 + +#ifndef XINPUT_GAMEPAD_GUIDE +# define XINPUT_GAMEPAD_GUIDE 0x400 +#endif + +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 int 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, (LPCSTR)100); + XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD, + DWORD, struct XInputCapabilitiesEx *)) + GetProcAddress(module, "XInputGetCapabilitiesEx"); + XInputVersion = 14; + } else if ((module = LoadLibraryA("xinput1_3.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, (LPCSTR)100); + XInputVersion = 13; + } else if ((module = LoadLibraryA("xinput9_1_0.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, "XInputGetState"); + XInputVersion = 910; + } +} + ++ (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)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: 17]; + + [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]; + if (XInputVersion != 910 && + state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE) + [_pressedButtons addObject: OFGameControllerHomeButton]; + + _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 +{ + switch (XInputVersion) { + case 14: + return @"XInput 1.4 device"; + case 13: + return @"XInput 1.3 device"; + case 910: + return @"XInput 9.1.0 device"; + } + + return nil; +} + +- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons +{ + return [OFSet setWithObjects: + OFGameControllerNorthButton, + OFGameControllerSouthButton, + OFGameControllerWestButton, + OFGameControllerEastButton, + OFGameControllerLeftTriggerButton, + OFGameControllerRightTriggerButton, + OFGameControllerLeftShoulderButton, + OFGameControllerRightShoulderButton, + OFGameControllerLeftStickButton, + OFGameControllerRightStickButton, + OFGameControllerDPadLeftButton, + OFGameControllerDPadRightButton, + OFGameControllerDPadUpButton, + OFGameControllerDPadDownButton, + OFGameControllerStartButton, + OFGameControllerSelectButton, + (XInputVersion != 910 ? OFGameControllerHomeButton : nil), 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 [super pressureForButton: button]; +} +@end ADDED src/hid/ObjFWHID.h Index: src/hid/ObjFWHID.h ================================================================== --- /dev/null +++ src/hid/ObjFWHID.h @@ -0,0 +1,20 @@ +/* + * 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" ADDED src/hid/ObjFWHID.oc.in Index: src/hid/ObjFWHID.oc.in ================================================================== --- /dev/null +++ src/hid/ObjFWHID.oc.in @@ -0,0 +1,4 @@ +package_format 1 +LIBS="-lobjfwhid @HID_LIBS@ $LIBS" +FRAMEWORK_LIBS="-framework ObjFWHID @HID_LIBS@ $FRAMEWORK_LIBS" +STATIC_LIBS="${libdir}/libobjfwhid.a @HID_LIBS@ $STATIC_LIBS" Index: src/test/Makefile ================================================================== --- src/test/Makefile +++ src/test/Makefile @@ -40,8 +40,10 @@ CPPFLAGS += -I. \ -I.. \ -I../.. \ -I../exceptions \ + -I../hid \ -I../runtime \ + -DOBJFWHID_LOCAL_INCLUDES \ -DOBJFWTEST_LOCAL_INCLUDES LD = ${OBJC} Index: src/test/OTAppDelegate.m ================================================================== --- src/test/OTAppDelegate.m +++ src/test/OTAppDelegate.m @@ -23,41 +23,24 @@ #import "OFColor.h" #import "OFDictionary.h" #import "OFMethodSignature.h" #import "OFSet.h" #import "OFStdIOStream.h" +#import "OFThread.h" #import "OFValue.h" #import "OTTestCase.h" + +#import "OFGameController.h" #import "OTAssertionFailedException.h" #import "OTTestSkippedException.h" #ifdef OF_IOS # include #endif -#ifdef OF_WII -# define asm __asm__ -# include -# include -# undef asm -#endif - -#ifdef OF_NINTENDO_DS -# define asm __asm__ -# include -# undef asm -#endif - -#ifdef OF_NINTENDO_3DS -/* Newer versions of libctru started using id as a parameter name. */ -# define id id_3ds -# include <3ds.h> -# undef id -#endif - #ifdef OF_NINTENDO_SWITCH # define id nx_id # include # undef id @@ -123,37 +106,12 @@ [[OFFileManager defaultManager] changeCurrentDirectoryPath: [OFString stringWithUTF8String: (const char *)resourcesPath]]; CFRelease(resourcesURL); -#elif defined(OF_WII) - GXRModeObj *mode; - void *nextFB; - - VIDEO_Init(); - WPAD_Init(); - - mode = VIDEO_GetPreferredMode(NULL); - nextFB = MEM_K0_TO_K1(SYS_AllocateFramebuffer(mode)); - VIDEO_Configure(mode); - VIDEO_SetNextFramebuffer(nextFB); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - - VIDEO_WaitVSync(); - if (mode->viTVMode & VI_NON_INTERLACE) - VIDEO_WaitVSync(); - - CON_InitEx(mode, 2, 2, mode->fbWidth - 4, mode->xfbHeight - 4); - VIDEO_ClearFrameBuffer(mode, nextFB, COLOR_BLACK); -#elif defined(OF_NINTENDO_DS) - consoleDemoInit(); -#elif defined(OF_NINTENDO_3DS) - gfxInitDefault(); - atexit(gfxExit); - - consoleInit(GFX_TOP, NULL); +#elif defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) + [OFStdIOStream setUpConsole]; #elif defined(OF_NINTENDO_SWITCH) consoleInit(NULL); padConfigureInput(1, HidNpadStyleSet_NpadStandard); updateConsole(true); #endif @@ -317,44 +275,28 @@ break; } if (status == StatusFailed) { -#if defined(OF_WII) - [OFStdOut setForegroundColor: [OFColor silver]]; - [OFStdOut writeLine: @"Press A to continue"]; - - for (;;) { - WPAD_ScanPads(); - - if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) - break; - - VIDEO_WaitVSync(); - } -#elif defined(OF_NINTENDO_DS) - [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(); +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) + [OFStdOut setForegroundColor: [OFColor silver]]; + [OFStdOut writeLine: @"Press A to continue"]; + + for (;;) { + void *pool = objc_autoreleasePoolPush(); + OFGameController *controller = + [[OFGameController controllers] objectAtIndex: 0]; + + [controller retrieveState]; + + if ([controller.pressedButtons containsObject: + OFGameControllerEastButton]) + break; + + [OFThread waitForVerticalBlank]; + + objc_autoreleasePoolPop(pool); } #elif defined(OF_NINTENDO_SWITCH) [OFStdOut setForegroundColor: [OFColor silver]]; [OFStdOut writeLine: @"Press A to continue"]; @@ -591,44 +533,37 @@ [OFStdOut setForegroundColor: [OFColor purple]]; [OFStdOut writeFormat: @" test%s skipped\n", (numSkipped != 1 ? "s" : "")]; [OFStdOut reset]; -#if defined(OF_WII) - [OFStdOut setForegroundColor: [OFColor silver]]; - [OFStdOut writeLine: @"Press home button to exit"]; - - for (;;) { - WPAD_ScanPads(); - - if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) - break; - - VIDEO_WaitVSync(); - } -#elif defined(OF_NINTENDO_DS) - [OFStdOut setForegroundColor: [OFColor silver]]; - [OFStdOut writeLine: @"Press start button to exit"]; - - for (;;) { - swiWaitForVBlank(); - scanKeys(); - - if (keysDown() & KEY_START) - break; - } -#elif defined(OF_NINTENDO_3DS) - [OFStdOut setForegroundColor: [OFColor silver]]; - [OFStdOut writeLine: @"Press start button to exit"]; - - for (;;) { - hidScanInput(); - - if (hidKeysDown() & KEY_START) - break; - - gspWaitForVBlank(); +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) + [OFStdOut setForegroundColor: [OFColor silver]]; +# ifdef OF_WII + [OFStdOut writeLine: @"Press Home button to exit"]; +# else + [OFStdOut writeLine: @"Press Start button to exit"]; +# endif + + for (;;) { + void *pool = objc_autoreleasePoolPush(); + OFGameController *controller = + [[OFGameController controllers] objectAtIndex: 0]; + + [controller retrieveState]; + +# ifdef OF_WII + if ([controller.pressedButtons containsObject: + OFGameControllerHomeButton]) +# else + if ([controller.pressedButtons containsObject: + OFGameControllerStartButton]) +# endif + break; + + [OFThread waitForVerticalBlank]; + + objc_autoreleasePoolPop(pool); } #elif defined(OF_NINTENDO_SWITCH) while (appletMainLoop()) updateConsole(true); Index: src/test/ObjFWTest.oc ================================================================== --- src/test/ObjFWTest.oc +++ src/test/ObjFWTest.oc @@ -1,4 +1,5 @@ package_format 1 +package_depends_on ObjFWHID LIBS="-lobjfwtest $LIBS" FRAMEWORK_LIBS="-lobjfwtest $FRAMEWORK_LIBS" STATIC_LIBS="${libdir}/libobjfwtest.a $STATIC_LIBS" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -1,14 +1,16 @@ include ../extra.mk -SUBDIRS = ${TESTPLUGIN} \ - ${SUBPROCESS} \ - ${OBJC_SYNC} \ +SUBDIRS = ${TESTPLUGIN} \ + ${SUBPROCESS} \ + gamecontroller \ + ${OBJC_SYNC} \ terminal CLEAN = EBOOT.PBP \ boot.dol \ + ${PROG_NOINST}.3dsx \ ${PROG_NOINST}.arm9 \ ${PROG_NOINST}.nds \ ${PROG_NOINST}.nro \ ${PROG_NOINST}.rpx \ big_dictionary_msgpack_gz.m \ @@ -121,10 +123,14 @@ 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 + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR} + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR} + rm -f objfwhid${OBJFWHID_LIB_MAJOR}.dll + rm -f libobjfwhid.${OBJFWHID_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 \ @@ -131,11 +137,11 @@ ${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; \ + objfw${OBJFW_LIB_MAJOR}.dll; \ fi if test -f ../src/libobjfw.dylib; then \ ${LN_S} ../src/libobjfw.dylib \ libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ fi @@ -142,23 +148,44 @@ 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}; \ + 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; \ + 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 + if test -f ../src/hid/libobjfwhid.so; then \ + ${LN_S} ../src/hid/libobjfwhid.so \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR}; \ + ${LN_S} ../src/hid/libobjfwhid.so \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + elif test -f ../src/hid/libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + then \ + ${LN_S} ../src/hid/libobjfwhid.so.${OBJFWHIID_LIB_MAJOR_MINOR} \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + fi + if test -f ../src/hid/objfwhid${OBJFWHID_LIB_MAJOR}.dll; then \ + ${LN_S} ../src/hid/objfwhid${OBJFWHID_LIB_MAJOR}.dll \ + objfwhid${OBJFWHID_LIB_MAJOR}.dll; \ + fi + if test -f ../src/hid/libobjfwhid.dylib; then \ + ${LN_S} ../src/hid/libobjfwhid.dylib \ + libobjfwhid.${OBJFWHID_LIB_MAJOR}.dylib; \ + fi LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ - DYLD_FRAMEWORK_PATH=../src:../src/runtime$${DYLD_FRAMEWORK_PATH+:}$$DYLD_FRAMEWORK_PATH \ + DYLD_FRAMEWORK_PATH=../src:../src/runtime:../src/hid$${DYLD_FRAMEWORK_PATH+:}$$DYLD_FRAMEWORK_PATH \ DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ ASAN_OPTIONS=allocator_may_return_null=1 \ ${WRAPPER} ./${PROG_NOINST} ${TESTCASES}; EXIT=$$?; \ rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ @@ -167,10 +194,14 @@ 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; \ + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR}; \ + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + rm -f objfwhid${OBJFWHID_LIB_MAJOR}.dll; \ + rm -f libobjfwhid.${OBJFWHID_LIB_MAJOR}.dylib; \ exit $$EXIT run-on-android: all echo "Uploading files to Android device..." if test -f ../src/libobjfw.so; then \ @@ -179,10 +210,14 @@ fi if test -f ../src/runtime/libobjfwrt.so; then \ adb push ../src/runtime/libobjfwrt.so \ /data/local/tmp/objfw/libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ fi + if test -f ../src/hid/libobjfwhid.so; then \ + adb push ../src/hid/libobjfwhid.so \ + /data/local/tmp/objfw/libobjfwhid.so.${OBJFWHID_LIB_MAJOR}; \ + fi adb push tests /data/local/tmp/objfw/tests adb push testfile.txt /data/local/tmp/objfw/testfile.txt if test -f plugin/TestPlugin.so; then \ adb push plugin/TestPlugin.so \ /data/local/tmp/objfw/plugin/TestPlugin.so; \ @@ -197,11 +232,12 @@ pack-pbp $@ PARAM.SFO NULL NULL NULL NULL NULL ${PROG_NOINST} NULL boot.dol: ${PROG_NOINST} elf2dol ${PROG_NOINST} $@ -${PROG_NOINST}: ${LIBOBJFW_DEP} ${LIBOBJFWRT_DEP} ../src/test/libobjfwtest.a +${PROG_NOINST}: ${LIBOBJFW_DEP} ${LIBOBJFWRT_DEP} ../src/test/libobjfwtest.a \ + ${LIBOBJFWHID_DEP} ${PROG_NOINST}.3dsx: ${PROG_NOINST} 3dsxtool $< $@ ${PROG_NOINST}.arm9: ${PROG_NOINST} ADDED tests/gamecontroller/GameControllerTests.m Index: tests/gamecontroller/GameControllerTests.m ================================================================== --- /dev/null +++ tests/gamecontroller/GameControllerTests.m @@ -0,0 +1,144 @@ +/* + * 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 "OFDate.h" +#import "OFGameController.h" +#import "OFNumber.h" +#import "OFSet.h" +#import "OFStdIOStream.h" +#import "OFThread.h" + +#if defined(OF_NINTENDO_DS) +static size_t buttonsPerLine = 2; +#elif defined(OF_NINTENDO_3DS) +static size_t buttonsPerLine = 3; +#else +static size_t buttonsPerLine = 5; +#endif + +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) +# define red maroon +# define yellow olive +# define gray silver +#endif + +@interface GameControllerTests: OFObject +{ + OFArray OF_GENERIC(OFGameController *) *_controllers; + OFDate *_lastControllersUpdate; +} +@end + +OF_APPLICATION_DELEGATE(GameControllerTests) + +@implementation GameControllerTests +- (void)applicationDidFinishLaunching: (OFNotification *)notification +{ +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) + [OFStdIOStream setUpConsole]; +#endif + + for (;;) { + void *pool = objc_autoreleasePoolPush(); + + if (_lastControllersUpdate == nil || + -[_lastControllersUpdate timeIntervalSinceNow] > 1) { + [_controllers release]; + [_lastControllersUpdate release]; + + _controllers = [[OFGameController controllers] retain]; + _lastControllersUpdate = [[OFDate alloc] init]; + + [OFStdOut clear]; + } + + [OFStdOut setCursorPosition: OFMakePoint(0, 0)]; + + for (OFGameController *controller in _controllers) { + OFArray OF_GENERIC(OFGameControllerButton) *buttons = + controller.buttons.allObjects.sortedArray; + size_t i = 0; + + [OFStdOut setForegroundColor: [OFColor green]]; + [OFStdOut writeString: controller.description]; + + [controller retrieveState]; + + for (OFGameControllerButton button in buttons) { + float pressure; + + if (i == 0) + [OFStdOut writeString: @"\n"]; + + pressure = + [controller pressureForButton: button]; + + if (pressure == 1) + [OFStdOut setForegroundColor: + [OFColor red]]; + else if (pressure > 0.5) + [OFStdOut setForegroundColor: + [OFColor yellow]]; + else if (pressure > 0) + [OFStdOut setForegroundColor: + [OFColor green]]; + else + [OFStdOut setForegroundColor: + [OFColor gray]]; + + [OFStdOut writeFormat: @"[%@]", button]; + + if (++i == buttonsPerLine) { + 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"]; + } + +#if defined(OF_WII) || defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS) + [OFThread waitForVerticalBlank]; +#else + [OFThread sleepForTimeInterval: 1.f / 60.f]; +#endif + + objc_autoreleasePoolPop(pool); + } +} +@end ADDED tests/gamecontroller/Makefile Index: tests/gamecontroller/Makefile ================================================================== --- /dev/null +++ tests/gamecontroller/Makefile @@ -0,0 +1,123 @@ +include ../../extra.mk + +CLEAN = boot.dol \ + ${PROG_NOINST}.3dsx \ + ${PROG_NOINST}.arm9 \ + ${PROG_NOINST}.ndsd \ + +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 + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR} + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR} + rm -f objfwhid${OBJFWHID_LIB_MAJOR}.dll + rm -f libobjfwhid.${OBJFWHID_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 + if test -f ../../src/hid/libobjfwhid.so; then \ + ${LN_S} ../../src/hid/libobjfwhid.so \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR}; \ + ${LN_S} ../../src/hid/libobjfwhid.so \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + elif test -f ../../src/hid/libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + then \ + ${LN_S} ../../src/hid/libobjfwhid.so.${OBJFWHIID_LIB_MAJOR_MINOR} \ + libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + fi + if test -f ../../src/hid/objfwhid${OBJFWHID_LIB_MAJOR}.dll; then \ + ${LN_S} ../../src/hid/objfwhid${OBJFWHID_LIB_MAJOR}.dll \ + objfwhid${OBJFWHID_LIB_MAJOR}.dll; \ + fi + if test -f ../../src/hid/libobjfwhid.dylib; then \ + ${LN_S} ../../src/hid/libobjfwhid.dylib \ + libobjfwhid.${OBJFWHID_LIB_MAJOR}.dylib; \ + fi + LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ + DYLD_FRAMEWORK_PATH=../../src:../../src/runtime:../../src/hid$${DYLD_FRAMEWORK_PATH+:}$$DYLD_FRAMEWORK_PATH \ + DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ + LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ + ASAN_OPTIONS=allocator_may_return_null=1 \ + ${WRAPPER} ./${PROG_NOINST} ${TESTCASES}; 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; \ + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR}; \ + rm -f libobjfwhid.so.${OBJFWHID_LIB_MAJOR_MINOR}; \ + rm -f objfwhid${OBJFWHID_LIB_MAJOR}.dll; \ + rm -f libobjfwhid.${OBJFWHID_LIB_MAJOR}.dylib; \ + exit $$EXIT + +boot.dol: ${PROG_NOINST} + elf2dol ${PROG_NOINST} $@ + +${PROG_NOINST}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFWRT_DEP_LVL2} \ + ${LIBOBJFWHID_DEP_LVL2} + +${PROG_NOINST}.3dsx: ${PROG_NOINST} + 3dsxtool $< $@ + +${PROG_NOINST}.arm9: ${PROG_NOINST} + arm-none-eabi-objcopy -O binary $< $@ + +${PROG_NOINST}.nds: ${PROG_NOINST}.arm9 + ndstool -c $@ -9 ${PROG_NOINST} + +CPPFLAGS += -I../../src \ + -I../../src/exceptions \ + -I../../src/hid \ + -I../../src/runtime \ + -I../.. \ + -DOBJFWHID_LOCAL_INCLUDES +LIBS := -L../../src/hid -lobjfwhid ${HID_LIBS} \ + -L../../src -lobjfw \ + -L../../src/runtime ${RUNTIME_LIBS} \ + ${LIBS} +LD = ${OBJC}