Index: .fossil-settings/ignore-glob
==================================================================
--- .fossil-settings/ignore-glob
+++ .fossil-settings/ignore-glob
@@ -28,10 +28,11 @@
extra.mk
generators/library/gen_libraries
generators/unicode/gen_tables
src/Info.plist
src/bridge/Info.plist
+src/hid/Info.plist
src/libobjfw.*
src/objfw-defs.h
src/runtime/Info.plist
src/runtime/libobjfwrt.*
src/test/libobjfwtest.a
@@ -40,10 +41,11 @@
tests/DerivedData
tests/EBOOT.PBP
tests/Info.plist
tests/PARAM.SFO
tests/big_dictionary_msgpack_gz.m
+tests/gamecontroller/gamecontroller_tests
tests/iOS.xcodeproj/*.pbxuser
tests/iOS.xcodeproj/project.xcworkspace
tests/iOS.xcodeproj/xcuserdata
tests/objc_sync/objc_sync
tests/plugin/Info.plist
Index: configure.ac
==================================================================
--- configure.ac
+++ configure.ac
@@ -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,21 @@
])
], [
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 -lobjfw $TESTS_LIBS"
+ TESTS_LIBS="-L../src/runtime \${RUNTIME_LIBS} $TESTS_LIBS"
+ TESTS_LIBS="-L../src/hid -lobjfwhid -L../src -lobjfw $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 +448,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}')
@@ -2381,10 +2388,11 @@
AC_CONFIG_FILES([
buildsys.mk
extra.mk
src/Info.plist
+ src/hid/Info.plist
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@
@@ -37,10 +44,12 @@
EXCEPTIONS_A = @EXCEPTIONS_A@
EXCEPTIONS_LIB_A = @EXCEPTIONS_LIB_A@
FORWARDING_A = @FORWARDING_A@
FORWARDING_LIB_A = @FORWARDING_LIB_A@
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}
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/OFGameController.h
Index: src/hid/OFGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFGameController.h
@@ -0,0 +1,373 @@
+/*
+ * 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 OFGameControllerButtonA
+ * * @ref OFGameControllerButtonB
+ * * @ref OFGameControllerButtonC
+ * * @ref OFGameControllerButtonX
+ * * @ref OFGameControllerButtonY
+ * * @ref OFGameControllerButtonZ
+ * * @ref OFGameControllerButtonL
+ * * @ref OFGameControllerButtonR
+ * * @ref OFGameControllerButtonZL
+ * * @ref OFGameControllerButtonZR
+ * * @ref OFGameControllerButtonSelect
+ * * @ref OFGameControllerButtonStart
+ * * @ref OFGameControllerButtonHome
+ * * @ref OFGameControllerButtonCapture
+ * * @ref OFGameControllerButtonLeftStick
+ * * @ref OFGameControllerButtonRightStick
+ * * @ref OFGameControllerButtonDPadUp
+ * * @ref OFGameControllerButtonDPadDown
+ * * @ref OFGameControllerButtonDPadLeft
+ * * @ref OFGameControllerButtonDPadRight
+ * * @ref OFGameControllerButtonCPadUp
+ * * @ref OFGameControllerButtonCPadDown
+ * * @ref OFGameControllerButtonCPadLeft
+ * * @ref OFGameControllerButtonCPadRight
+ * * @ref OFGameControllerButtonSL
+ * * @ref OFGameControllerButtonSR
+ * * @ref OFGameControllerButtonMode
+ */
+typedef OFConstantString *OFGameControllerButton;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/**
+ * @brief The A button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonA;
+
+/**
+ * @brief The B button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonB;
+
+/**
+ * @brief The C button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonC;
+
+/**
+ * @brief The X button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonX;
+
+/**
+ * @brief The Y button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonY;
+
+/**
+ * @brief The Z button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonZ;
+
+/**
+ * @brief The L button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonL;
+
+/**
+ * @brief The R button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonR;
+
+/**
+ * @brief The ZL button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonZL;
+
+/**
+ * @brief The ZR button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonZR;
+
+/**
+ * @brief The Select button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonSelect;
+
+/**
+ * @brief The Start button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonStart;
+
+/**
+ * @brief The Home button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonHome;
+
+/**
+ * @brief The Capture button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonCapture;
+
+/**
+ * @brief The left stick button (pressing the left stick) on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonLeftStick;
+
+/**
+ * @brief The right stick button (pressing the right stick) on a game
+ * controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonRightStick;
+
+/**
+ * @brief The D-Pad Up button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonDPadUp;
+
+/**
+ * @brief The D-Pad Down button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonDPadDown;
+
+/**
+ * @brief The D-Pad Left button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonDPadLeft;
+
+/**
+ * @brief The D-Pad Right button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonDPadRight;
+
+/**
+ * @brief The C-Pad Up button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonCPadUp;
+
+/**
+ * @brief The C-Pad Down button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonCPadDown;
+
+/**
+ * @brief The C-Pad Left button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonCPadLeft;
+
+/**
+ * @brief The C-Pad Right button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonCPadRight;
+
+/**
+ * @brief The + button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonPlus;
+
+/**
+ * @brief The - button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonMinus;
+
+/**
+ * @brief The SL button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonSL;
+
+/**
+ * @brief The SR button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonSR;
+
+/**
+ * @brief The Mode button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerButtonMode;
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @brief A class for reading state from a game controller.
+ */
+OF_SUBCLASSING_RESTRICTED
+@interface OFGameController: OFObject
+{
+#if defined(OF_LINUX)
+ OFString *_path;
+ int _fd;
+ uint16_t _vendorID, _productID;
+ OFString *_name;
+ OFMutableSet *_buttons, *_pressedButtons;
+ bool _hasLeftAnalogStick, _hasRightAnalogStick;
+ bool _hasZLPressure, _hasZRPressure;
+ OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition;
+ float _ZLPressure, _ZRPressure;
+ int32_t _leftAnalogStickMinX, _leftAnalogStickMaxX;
+ int32_t _leftAnalogStickMinY, _leftAnalogStickMaxY;
+ int32_t _rightAnalogStickMinX, _rightAnalogStickMaxX;
+ int32_t _rightAnalogStickMinY, _rightAnalogStickMaxY;
+ int32_t _ZLMinPressure, _ZLMaxPressure, _ZRMinPressure, _ZRMaxPressure;
+#elif defined(OF_NINTENDO_DS)
+ OFMutableSet *_pressedButtons;
+#elif defined(OF_NINTENDO_3DS)
+ OFMutableSet *_pressedButtons;
+ OFPoint _leftAnalogStickPosition;
+#elif defined(OF_WINDOWS)
+ DWORD _index;
+ OFNumber *_Nullable _vendorID, *_Nullable productID;
+ OFMutableSet *_pressedButtons;
+ OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition;
+ float _ZLPressure, _ZRPressure;
+#endif
+}
+
+#ifdef OF_HAVE_CLASS_PROPERTIES
+@property (class, readonly, nonatomic)
+ OFArray *controllers;
+#endif
+
+/**
+ * @brief The name of the controller.
+ */
+@property (readonly, nonatomic, copy) OFString *name;
+
+/**
+ * @brief The 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.
+ */
+@property (readonly, nonatomic) bool hasRightAnalogStick;
+
+/**
+ * @brief The position of the right analog stick.
+ *
+ * The range is from (-1, -1) to (1, 1).
+ */
+@property (readonly, nonatomic) OFPoint rightAnalogStickPosition;
+
+/**
+ * @brief The north button on the right diamond pad or `nil` if there is none.
+ */
+@property OF_NULLABLE_PROPERTY (readonly, nonatomic)
+ OFGameControllerButton northButton;
+
+/**
+ * @brief The south button on the right diamond pad or `nil` if there is none.
+ */
+@property OF_NULLABLE_PROPERTY (readonly, nonatomic)
+ OFGameControllerButton southButton;
+
+/**
+ * @brief The west button on the right diamond pad or `nil` if there is none.
+ */
+@property OF_NULLABLE_PROPERTY (readonly, nonatomic)
+ OFGameControllerButton westButton;
+
+/**
+ * @brief The east button on the right diamond pad or `nil` if there is none.
+ */
+@property OF_NULLABLE_PROPERTY (readonly, nonatomic)
+ OFGameControllerButton eastButton;
+
+/**
+ * @brief Returns the available controllers.
+ *
+ * @return The available controllers
+ */
++ (OFArray OF_GENERIC(OFGameController *) *)controllers;
+
+- (instancetype)init OF_UNAVAILABLE;
+
+/**
+ * @brief Retrieve the current state from the game controller.
+ *
+ * The state returned by @ref OFGameController's messages does not change until
+ * this method is called.
+ *
+ * @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,98 @@
+/*
+ * 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"
+
+const OFGameControllerButton OFGameControllerButtonA = @"A";
+const OFGameControllerButton OFGameControllerButtonB = @"B";
+const OFGameControllerButton OFGameControllerButtonC = @"C";
+const OFGameControllerButton OFGameControllerButtonX = @"X";
+const OFGameControllerButton OFGameControllerButtonY = @"Y";
+const OFGameControllerButton OFGameControllerButtonZ = @"Z";
+const OFGameControllerButton OFGameControllerButtonL = @"L";
+const OFGameControllerButton OFGameControllerButtonR = @"R";
+const OFGameControllerButton OFGameControllerButtonZL = @"ZL";
+const OFGameControllerButton OFGameControllerButtonZR = @"ZR";
+const OFGameControllerButton OFGameControllerButtonSelect = @"Select";
+const OFGameControllerButton OFGameControllerButtonStart = @"Start";
+const OFGameControllerButton OFGameControllerButtonHome = @"Home";
+const OFGameControllerButton OFGameControllerButtonCapture = @"Capture";
+const OFGameControllerButton OFGameControllerButtonLeftStick = @"Left Stick";
+const OFGameControllerButton OFGameControllerButtonRightStick = @"Right Stick";
+const OFGameControllerButton OFGameControllerButtonDPadUp = @"D-Pad Up";
+const OFGameControllerButton OFGameControllerButtonDPadDown = @"D-Pad Down";
+const OFGameControllerButton OFGameControllerButtonDPadLeft = @"D-Pad Left";
+const OFGameControllerButton OFGameControllerButtonDPadRight = @"D-Pad Right";
+const OFGameControllerButton OFGameControllerButtonCPadUp = @"C-Pad Up";
+const OFGameControllerButton OFGameControllerButtonCPadDown = @"C-Pad Down";
+const OFGameControllerButton OFGameControllerButtonCPadLeft = @"C-Pad Left";
+const OFGameControllerButton OFGameControllerButtonCPadRight = @"C-Pad Right";
+const OFGameControllerButton OFGameControllerButtonPlus = @"+";
+const OFGameControllerButton OFGameControllerButtonMinus = @"-";
+const OFGameControllerButton OFGameControllerButtonSL = @"SL";
+const OFGameControllerButton OFGameControllerButtonSR = @"SR";
+const OFGameControllerButton OFGameControllerButtonMode = @"Mode";
+
+#if defined(OF_LINUX) && defined(OF_HAVE_FILES)
+# include "platform/Linux/OFGameController.m"
+#elif defined(OF_WINDOWS)
+# include "platform/Windows/OFGameController.m"
+#elif defined(OF_NINTENDO_DS)
+# include "platform/NintendoDS/OFGameController.m"
+#elif defined(OF_NINTENDO_3DS)
+# include "platform/Nintendo3DS/OFGameController.m"
+#else
+@implementation OFGameController
+@dynamic name, buttons, pressedButtons, hasLeftAnalogStick;
+@dynamic leftAnalogStickPosition, hasRightAnalogStick, rightAnalogStickPosition;
+@dynamic northButton, southButton, westButton, eastButton;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ return [OFArray array];
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (OFNumber *)vendorID
+{
+ return nil;
+}
+
+- (OFNumber *)productID
+{
+ return nil;
+}
+
+- (void)retrieveState
+{
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ return 0;
+}
+@end
+#endif
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
Index: src/hid/ObjFWHID.oc
==================================================================
--- /dev/null
+++ src/hid/ObjFWHID.oc
@@ -0,0 +1,4 @@
+package_format 1
+LIBS="-lobjfwhid $LIBS"
+FRAMEWORK_LIBS="-framework ObjFWHID $FRAMEWORK_LIBS"
+STATIC_LIBS="${libdir}/libobjfwhid.a $STATIC_LIBS"
ADDED src/hid/platform/Linux/OFGameController.m
Index: src/hid/platform/Linux/OFGameController.m
==================================================================
--- /dev/null
+++ src/hid/platform/Linux/OFGameController.m
@@ -0,0 +1,596 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#import "OFGameController.h"
+#import "OFArray.h"
+#import "OFFileManager.h"
+#import "OFLocale.h"
+#import "OFNumber.h"
+#import "OFSet.h"
+
+#include
+#include
+
+#import "OFInitializationFailedException.h"
+#import "OFInvalidArgumentException.h"
+#import "OFOpenItemFailedException.h"
+#import "OFOutOfRangeException.h"
+#import "OFReadFailedException.h"
+
+static const uint16_t vendorIDNintendo = 0x057E;
+static const uint16_t productIDLeftJoycon = 0x2006;
+static const uint16_t productIDRightJoycon = 0x2007;
+static const uint16_t productIDN64Controller = 0x2019;
+
+@interface OFGameController ()
+- (instancetype)of_initWithPath: (OFString *)path OF_METHOD_FAMILY(init);
+@end
+
+static const uint16_t buttons[] = {
+ BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2,
+ BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_THUMBL, BTN_THUMBR,
+ BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT
+};
+
+static OFGameControllerButton
+buttonToName(uint16_t button, uint16_t vendorID, uint16_t productID)
+{
+ if (vendorID == vendorIDNintendo &&
+ productID == productIDLeftJoycon) {
+ switch (button) {
+ case BTN_SELECT:
+ return OFGameControllerButtonMinus;
+ case BTN_Z:
+ return OFGameControllerButtonCapture;
+ case BTN_TR:
+ return OFGameControllerButtonSL;
+ case BTN_TR2:
+ return OFGameControllerButtonSR;
+ }
+ } else if (vendorID == vendorIDNintendo &&
+ productID == productIDRightJoycon) {
+ switch (button) {
+ case BTN_B:
+ return OFGameControllerButtonA;
+ case BTN_A:
+ return OFGameControllerButtonB;
+ case BTN_START:
+ return OFGameControllerButtonPlus;
+ case BTN_TL:
+ return OFGameControllerButtonSL;
+ case BTN_TL2:
+ return OFGameControllerButtonSR;
+ }
+ } else if (vendorID == vendorIDNintendo &&
+ productID == productIDN64Controller) {
+ switch (button) {
+ case BTN_TL2:
+ return OFGameControllerButtonZ;
+ case BTN_Y:
+ return OFGameControllerButtonCPadLeft;
+ case BTN_C:
+ return OFGameControllerButtonCPadRight;
+ case BTN_SELECT:
+ return OFGameControllerButtonCPadUp;
+ case BTN_X:
+ return OFGameControllerButtonCPadDown;
+ case BTN_MODE:
+ return OFGameControllerButtonHome;
+ case BTN_Z:
+ return OFGameControllerButtonCapture;
+ }
+ }
+
+ switch (button) {
+ case BTN_A:
+ return OFGameControllerButtonA;
+ case BTN_B:
+ return OFGameControllerButtonB;
+ case BTN_C:
+ return OFGameControllerButtonC;
+ case BTN_X:
+ return OFGameControllerButtonX;
+ case BTN_Y:
+ return OFGameControllerButtonY;
+ case BTN_Z:
+ return OFGameControllerButtonZ;
+ case BTN_TL:
+ return OFGameControllerButtonL;
+ case BTN_TR:
+ return OFGameControllerButtonR;
+ case BTN_TL2:
+ return OFGameControllerButtonZL;
+ case BTN_TR2:
+ return OFGameControllerButtonZR;
+ case BTN_SELECT:
+ return OFGameControllerButtonSelect;
+ case BTN_START:
+ return OFGameControllerButtonStart;
+ case BTN_MODE:
+ return OFGameControllerButtonHome;
+ case BTN_THUMBL:
+ return OFGameControllerButtonLeftStick;
+ case BTN_THUMBR:
+ return OFGameControllerButtonRightStick;
+ case BTN_DPAD_UP:
+ return OFGameControllerButtonDPadUp;
+ case BTN_DPAD_DOWN:
+ return OFGameControllerButtonDPadDown;
+ case BTN_DPAD_LEFT:
+ return OFGameControllerButtonDPadLeft;
+ case BTN_DPAD_RIGHT:
+ return OFGameControllerButtonDPadRight;
+ }
+
+ return nil;
+}
+
+static float
+scale(float value, float min, float max)
+{
+ if (value < min)
+ value = min;
+ if (value > max)
+ value = max;
+
+ return ((value - min) / (max - min) * 2) - 1;
+}
+
+@implementation OFGameController
+@synthesize name = _name, buttons = _buttons;
+@synthesize hasLeftAnalogStick = _hasLeftAnalogStick;
+@synthesize hasRightAnalogStick = _hasRightAnalogStick;
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+@synthesize rightAnalogStickPosition = _rightAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ OFMutableArray *controllers = [OFMutableArray array];
+ void *pool = objc_autoreleasePoolPush();
+
+ for (OFString *device in [[OFFileManager defaultManager]
+ contentsOfDirectoryAtPath: @"/dev/input"]) {
+ OFString *path;
+ OFGameController *controller;
+
+ if (![device hasPrefix: @"event"])
+ continue;
+
+ path = [@"/dev/input" stringByAppendingPathComponent: device];
+
+ @try {
+ controller = [[[OFGameController alloc]
+ of_initWithPath: path] autorelease];
+ } @catch (OFOpenItemFailedException *e) {
+ if (e.errNo == EACCES)
+ continue;
+
+ @throw e;
+ } @catch (OFInvalidArgumentException *e) {
+ /* Not a game controller. */
+ continue;
+ }
+
+ [controllers addObject: controller];
+ }
+
+ [controllers sort];
+ [controllers makeImmutable];
+
+ objc_autoreleasePoolPop(pool);
+
+ return controllers;
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_initWithPath: (OFString *)path
+{
+ self = [super init];
+
+ @try {
+ OFStringEncoding encoding = [OFLocale encoding];
+ unsigned long evBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ EV_MAX) / OF_ULONG_BIT] = { 0 };
+ unsigned long keyBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ KEY_MAX) / OF_ULONG_BIT] = { 0 };
+ unsigned long absBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ ABS_MAX) / OF_ULONG_BIT] = { 0 };
+ struct input_id inputID;
+ char name[128];
+
+ _path = [path copy];
+
+ if ((_fd = open([_path cStringWithEncoding: encoding],
+ O_RDONLY | O_NONBLOCK)) == -1)
+ @throw [OFOpenItemFailedException
+ exceptionWithPath: _path
+ mode: @"r"
+ errNo: errno];
+
+ if (ioctl(_fd, EVIOCGBIT(0, sizeof(evBits)), evBits) == -1)
+ @throw [OFInitializationFailedException exception];
+
+ if (!OFBitSetIsSet(evBits, EV_KEY))
+ @throw [OFInvalidArgumentException exception];
+
+ if (ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) ==
+ -1)
+ @throw [OFInitializationFailedException exception];
+
+ if (!OFBitSetIsSet(keyBits, BTN_GAMEPAD) &&
+ !OFBitSetIsSet(keyBits, BTN_DPAD_UP))
+ @throw [OFInvalidArgumentException exception];
+
+ if (ioctl(_fd, EVIOCGID, &inputID) == -1)
+ @throw [OFInvalidArgumentException exception];
+
+ _vendorID = inputID.vendor;
+ _productID = inputID.product;
+
+ if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) == -1)
+ @throw [OFInitializationFailedException exception];
+
+ _name = [[OFString alloc] initWithCString: name
+ encoding: encoding];
+
+ _buttons = [[OFMutableSet alloc] init];
+ for (size_t i = 0; i < sizeof(buttons) / sizeof(*buttons);
+ i++)
+ if (OFBitSetIsSet(keyBits, buttons[i]))
+ [_buttons addObject: buttonToName(
+ buttons[i], _vendorID, _productID)];
+
+ _pressedButtons = [[OFMutableSet alloc] init];
+
+ if (OFBitSetIsSet(evBits, EV_ABS)) {
+ if (ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(absBits)),
+ absBits) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (OFBitSetIsSet(absBits, ABS_X) &&
+ OFBitSetIsSet(absBits, ABS_Y)) {
+ struct input_absinfo infoX, infoY;
+
+ _hasLeftAnalogStick = true;
+
+ if (ioctl(_fd, EVIOCGABS(ABS_X), &infoX) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (ioctl(_fd, EVIOCGABS(ABS_Y), &infoY) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _leftAnalogStickMinX = infoX.minimum;
+ _leftAnalogStickMaxX = infoX.maximum;
+ _leftAnalogStickMinY = infoY.minimum;
+ _leftAnalogStickMaxY = infoY.maximum;
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_RX) &&
+ OFBitSetIsSet(absBits, ABS_RY)) {
+ struct input_absinfo infoX, infoY;
+
+ _hasRightAnalogStick = true;
+
+ if (ioctl(_fd, EVIOCGABS(ABS_RX), &infoX) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (ioctl(_fd, EVIOCGABS(ABS_RY), &infoY) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _rightAnalogStickMinX = infoX.minimum;
+ _rightAnalogStickMaxX = infoX.maximum;
+ _rightAnalogStickMinY = infoY.minimum;
+ _rightAnalogStickMaxY = infoY.maximum;
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_HAT0X) &&
+ OFBitSetIsSet(absBits, ABS_HAT0Y)) {
+ [_buttons addObject:
+ OFGameControllerButtonDPadLeft];
+ [_buttons addObject:
+ OFGameControllerButtonDPadRight];
+ [_buttons addObject:
+ OFGameControllerButtonDPadUp];
+ [_buttons addObject:
+ OFGameControllerButtonDPadDown];
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_Z)) {
+ struct input_absinfo info;
+
+ _hasZLPressure = true;
+
+ if (ioctl(_fd, EVIOCGABS(ABS_Z), &info) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _ZLMinPressure = info.minimum;
+ _ZLMaxPressure = info.maximum;
+
+ [_buttons addObject: OFGameControllerButtonZL];
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_RZ)) {
+ struct input_absinfo info;
+
+ _hasZRPressure = true;
+
+ if (ioctl(_fd, EVIOCGABS(ABS_RZ), &info) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _ZRMinPressure = info.minimum;
+ _ZRMaxPressure = info.maximum;
+
+ [_buttons addObject: OFGameControllerButtonZR];
+ }
+ }
+
+ [_buttons makeImmutable];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_path release];
+
+ if (_fd != -1)
+ close(_fd);
+
+ [_name release];
+ [_buttons release];
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (OFNumber *)vendorID
+{
+ return [OFNumber numberWithUnsignedShort: _vendorID];
+}
+
+- (OFNumber *)productID
+{
+ return [OFNumber numberWithUnsignedShort: _productID];
+}
+
+- (void)retrieveState
+{
+ struct input_event event;
+
+ for (;;) {
+ errno = 0;
+
+ if (read(_fd, &event, sizeof(event)) < (int)sizeof(event)) {
+ if (errno == EWOULDBLOCK)
+ return;
+
+ @throw [OFReadFailedException
+ exceptionWithObject: self
+ requestedLength: sizeof(event)
+ errNo: errno];
+ }
+
+ switch (event.type) {
+ case EV_KEY:
+ if (event.value)
+ [_pressedButtons addObject: buttonToName(
+ event.code, _vendorID, _productID)];
+ else
+ [_pressedButtons removeObject: buttonToName(
+ event.code, _vendorID, _productID)];
+ break;
+ case EV_ABS:
+ switch (event.code) {
+ case ABS_X:
+ _leftAnalogStickPosition.x = scale(event.value,
+ _leftAnalogStickMinX, _leftAnalogStickMaxX);
+ break;
+ case ABS_Y:
+ _leftAnalogStickPosition.y = scale(event.value,
+ _leftAnalogStickMinY, _leftAnalogStickMaxY);
+ break;
+ case ABS_RX:
+ _rightAnalogStickPosition.x = scale(event.value,
+ _rightAnalogStickMinX,
+ _rightAnalogStickMaxX);
+ break;
+ case ABS_RY:
+ _rightAnalogStickPosition.y = scale(event.value,
+ _rightAnalogStickMinY,
+ _rightAnalogStickMaxY);
+ break;
+ case ABS_HAT0X:
+ if (event.value < 0) {
+ [_pressedButtons addObject:
+ OFGameControllerButtonDPadLeft];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadRight];
+ } else if (event.value > 0) {
+ [_pressedButtons addObject:
+ OFGameControllerButtonDPadRight];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadLeft];
+ } else {
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadLeft];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadRight];
+ }
+ break;
+ case ABS_HAT0Y:
+ if (event.value < 0) {
+ [_pressedButtons addObject:
+ OFGameControllerButtonDPadUp];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadDown];
+ } else if (event.value > 0) {
+ [_pressedButtons addObject:
+ OFGameControllerButtonDPadDown];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadUp];
+ } else {
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadUp];
+ [_pressedButtons removeObject:
+ OFGameControllerButtonDPadDown];
+ }
+ break;
+ case ABS_Z:
+ _ZLPressure = scale(event.value,
+ _ZLMinPressure, _ZLMaxPressure);
+
+ if (_ZLPressure > 0)
+ [_pressedButtons addObject:
+ OFGameControllerButtonZL];
+ else
+ [_pressedButtons removeObject:
+ OFGameControllerButtonZL];
+ break;
+ case ABS_RZ:
+ _ZRPressure = scale(event.value,
+ _ZRMinPressure, _ZRMaxPressure);
+
+ if (_ZRPressure > 0)
+ [_pressedButtons addObject:
+ OFGameControllerButtonZR];
+ else
+ [_pressedButtons removeObject:
+ OFGameControllerButtonZR];
+ break;
+ }
+
+ break;
+ }
+ }
+}
+
+- (OFComparisonResult)compare: (OFGameController *)otherController
+{
+ unsigned long long selfIndex, otherIndex;
+
+ if (![otherController isKindOfClass: [OFGameController class]])
+ @throw [OFInvalidArgumentException exception];
+
+ selfIndex = [_path substringFromIndex: 16].unsignedLongLongValue;
+ otherIndex = [otherController->_path substringFromIndex: 16]
+ .unsignedLongLongValue;
+
+ if (selfIndex > otherIndex)
+ return OFOrderedDescending;
+ if (selfIndex < otherIndex)
+ return OFOrderedAscending;
+
+ return OFOrderedSame;
+}
+
+- (OFSet *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ if (button == OFGameControllerButtonZL && _hasZLPressure)
+ return _ZLPressure;
+ if (button == OFGameControllerButtonZR && _hasZRPressure)
+ return _ZRPressure;
+
+ return ([self.pressedButtons containsObject: button] ? 1 : 0);
+}
+
+- (OFGameControllerButton)northButton
+{
+ if (_vendorID == vendorIDNintendo && _productID == productIDLeftJoycon)
+ return nil;
+ if (_vendorID == vendorIDNintendo && _productID == productIDRightJoycon)
+ return OFGameControllerButtonX;
+ if (_vendorID == vendorIDNintendo &&
+ _productID == productIDN64Controller)
+ return nil;
+
+ return OFGameControllerButtonY;
+}
+
+- (OFGameControllerButton)southButton
+{
+ if (_vendorID == vendorIDNintendo && _productID == productIDLeftJoycon)
+ return nil;
+ if (_vendorID == vendorIDNintendo && _productID == productIDRightJoycon)
+ return OFGameControllerButtonB;
+ if (_vendorID == vendorIDNintendo &&
+ _productID == productIDN64Controller)
+ return nil;
+
+ return OFGameControllerButtonA;
+}
+
+- (OFGameControllerButton)westButton
+{
+ if (_vendorID == vendorIDNintendo && _productID == productIDLeftJoycon)
+ return nil;
+ if (_vendorID == vendorIDNintendo && _productID == productIDRightJoycon)
+ return OFGameControllerButtonY;
+ if (_vendorID == vendorIDNintendo &&
+ _productID == productIDN64Controller)
+ return nil;
+
+ return OFGameControllerButtonX;
+}
+
+- (OFGameControllerButton)eastButton
+{
+ if (_vendorID == vendorIDNintendo && _productID == productIDLeftJoycon)
+ return nil;
+ if (_vendorID == vendorIDNintendo && _productID == productIDRightJoycon)
+ return OFGameControllerButtonA;
+ if (_vendorID == vendorIDNintendo &&
+ _productID == productIDN64Controller)
+ return nil;
+
+ return OFGameControllerButtonB;
+}
+
+- (OFString *)description
+{
+ return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name];
+}
+@end
ADDED src/hid/platform/Nintendo3DS/OFGameController.m
Index: src/hid/platform/Nintendo3DS/OFGameController.m
==================================================================
--- /dev/null
+++ src/hid/platform/Nintendo3DS/OFGameController.m
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFGameController.h"
+#import "OFArray.h"
+#import "OFSet.h"
+
+#import "OFOutOfRangeException.h"
+
+#define id id_3ds
+#include <3ds.h>
+#undef id
+
+@interface OFGameController ()
+- (instancetype)of_init OF_METHOD_FAMILY(init);
+@end
+
+static OFArray OF_GENERIC(OFGameController *) *controllers;
+
+static void
+initControllers(void)
+{
+ void *pool = objc_autoreleasePoolPush();
+
+ controllers = [[OFArray alloc] initWithObject:
+ [[[OFGameController alloc] of_init] autorelease]];
+
+ objc_autoreleasePoolPop(pool);
+}
+
+@implementation OFGameController
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+@dynamic rightAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ static OFOnceControl onceControl = OFOnceControlInitValue;
+
+ OFOnce(&onceControl, initControllers);
+
+ return [[controllers retain] autorelease];
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_init
+{
+ self = [super init];
+
+ @try {
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 18];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ u32 keys;
+ circlePosition pos;
+
+ hidScanInput();
+
+ keys = hidKeysHeld();
+ hidCircleRead(&pos);
+
+ [_pressedButtons removeAllObjects];
+
+ if (keys & KEY_A)
+ [_pressedButtons addObject: OFGameControllerButtonA];
+ if (keys & KEY_B)
+ [_pressedButtons addObject: OFGameControllerButtonB];
+ if (keys & KEY_SELECT)
+ [_pressedButtons addObject: OFGameControllerButtonSelect];
+ if (keys & KEY_START)
+ [_pressedButtons addObject: OFGameControllerButtonStart];
+ if (keys & KEY_DRIGHT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadRight];
+ if (keys & KEY_DLEFT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadLeft];
+ if (keys & KEY_DUP)
+ [_pressedButtons addObject: OFGameControllerButtonDPadUp];
+ if (keys & KEY_DDOWN)
+ [_pressedButtons addObject: OFGameControllerButtonDPadDown];
+ if (keys & KEY_R)
+ [_pressedButtons addObject: OFGameControllerButtonR];
+ if (keys & KEY_L)
+ [_pressedButtons addObject: OFGameControllerButtonL];
+ if (keys & KEY_X)
+ [_pressedButtons addObject: OFGameControllerButtonX];
+ if (keys & KEY_Y)
+ [_pressedButtons addObject: OFGameControllerButtonY];
+ if (keys & KEY_ZL)
+ [_pressedButtons addObject: OFGameControllerButtonZL];
+ if (keys & KEY_ZR)
+ [_pressedButtons addObject: OFGameControllerButtonZR];
+ if (keys & KEY_CSTICK_RIGHT)
+ [_pressedButtons addObject: OFGameControllerButtonCPadRight];
+ if (keys & KEY_CSTICK_LEFT)
+ [_pressedButtons addObject: OFGameControllerButtonCPadLeft];
+ if (keys & KEY_CSTICK_UP)
+ [_pressedButtons addObject: OFGameControllerButtonCPadUp];
+ if (keys & KEY_CSTICK_DOWN)
+ [_pressedButtons addObject: OFGameControllerButtonCPadDown];
+
+ _leftAnalogStickPosition = OFMakePoint(
+ (float)pos.dx / (pos.dx < 0 ? -INT16_MIN : INT16_MAX),
+ (float)pos.dy / (pos.dy < 0 ? -INT16_MIN : INT16_MAX));
+}
+
+- (OFString *)name
+{
+ return @"Nintendo 3DS";
+}
+
+- (OFNumber *)vendorID
+{
+ return nil;
+}
+
+- (OFNumber *)productID
+{
+ return nil;
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects: OFGameControllerButtonA,
+ OFGameControllerButtonB, OFGameControllerButtonSelect,
+ OFGameControllerButtonStart, OFGameControllerButtonDPadRight,
+ OFGameControllerButtonDPadLeft, OFGameControllerButtonDPadUp,
+ OFGameControllerButtonDPadDown, OFGameControllerButtonR,
+ OFGameControllerButtonL, OFGameControllerButtonX,
+ OFGameControllerButtonY, OFGameControllerButtonZL,
+ OFGameControllerButtonZR, OFGameControllerButtonCPadRight,
+ OFGameControllerButtonCPadLeft, OFGameControllerButtonCPadUp,
+ OFGameControllerButtonCPadDown, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return true;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return false;
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ return ([self.pressedButtons containsObject: button] ? 1 : 0);
+}
+
+- (OFGameControllerButton)northButton
+{
+ return OFGameControllerButtonX;
+}
+
+- (OFGameControllerButton)southButton
+{
+ return OFGameControllerButtonB;
+}
+
+- (OFGameControllerButton)westButton
+{
+ return OFGameControllerButtonY;
+}
+
+- (OFGameControllerButton)eastButton
+{
+ return OFGameControllerButtonA;
+}
+
+- (OFString *)description
+{
+ return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name];
+}
+@end
ADDED src/hid/platform/NintendoDS/OFGameController.m
Index: src/hid/platform/NintendoDS/OFGameController.m
==================================================================
--- /dev/null
+++ src/hid/platform/NintendoDS/OFGameController.m
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFGameController.h"
+#import "OFArray.h"
+#import "OFSet.h"
+
+#import "OFOutOfRangeException.h"
+
+#define asm __asm__
+#include
+#undef asm
+
+@interface OFGameController ()
+- (instancetype)of_init OF_METHOD_FAMILY(init);
+@end
+
+static OFArray OF_GENERIC(OFGameController *) *controllers;
+
+static void
+initControllers(void)
+{
+ void *pool = objc_autoreleasePoolPush();
+
+ controllers = [[OFArray alloc] initWithObject:
+ [[[OFGameController alloc] of_init] autorelease]];
+
+ objc_autoreleasePoolPop(pool);
+}
+
+@implementation OFGameController
+@dynamic leftAnalogStickPosition, rightAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ static OFOnceControl onceControl = OFOnceControlInitValue;
+
+ OFOnce(&onceControl, initControllers);
+
+ return [[controllers retain] autorelease];
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_init
+{
+ self = [super init];
+
+ @try {
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 12];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ uint32 keys;
+
+ scanKeys();
+ keys = keysCurrent();
+
+ [_pressedButtons removeAllObjects];
+
+ if (keys & KEY_A)
+ [_pressedButtons addObject: OFGameControllerButtonA];
+ if (keys & KEY_B)
+ [_pressedButtons addObject: OFGameControllerButtonB];
+ if (keys & KEY_SELECT)
+ [_pressedButtons addObject: OFGameControllerButtonSelect];
+ if (keys & KEY_START)
+ [_pressedButtons addObject: OFGameControllerButtonStart];
+ if (keys & KEY_RIGHT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadRight];
+ if (keys & KEY_LEFT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadLeft];
+ if (keys & KEY_UP)
+ [_pressedButtons addObject: OFGameControllerButtonDPadUp];
+ if (keys & KEY_DOWN)
+ [_pressedButtons addObject: OFGameControllerButtonDPadDown];
+ if (keys & KEY_R)
+ [_pressedButtons addObject: OFGameControllerButtonR];
+ if (keys & KEY_L)
+ [_pressedButtons addObject: OFGameControllerButtonL];
+ if (keys & KEY_X)
+ [_pressedButtons addObject: OFGameControllerButtonX];
+ if (keys & KEY_Y)
+ [_pressedButtons addObject: OFGameControllerButtonY];
+}
+
+- (OFString *)name
+{
+ return @"Nintendo DS";
+}
+
+- (OFNumber *)vendorID
+{
+ return nil;
+}
+
+- (OFNumber *)productID
+{
+ return nil;
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects: OFGameControllerButtonA,
+ OFGameControllerButtonB, OFGameControllerButtonSelect,
+ OFGameControllerButtonStart, OFGameControllerButtonDPadRight,
+ OFGameControllerButtonDPadLeft, OFGameControllerButtonDPadUp,
+ OFGameControllerButtonDPadDown, OFGameControllerButtonR,
+ OFGameControllerButtonL, OFGameControllerButtonX,
+ OFGameControllerButtonY, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return false;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return false;
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ return ([self.pressedButtons containsObject: button] ? 1 : 0);
+}
+
+- (OFGameControllerButton)northButton
+{
+ return OFGameControllerButtonX;
+}
+
+- (OFGameControllerButton)southButton
+{
+ return OFGameControllerButtonB;
+}
+
+- (OFGameControllerButton)westButton
+{
+ return OFGameControllerButtonY;
+}
+
+- (OFGameControllerButton)eastButton
+{
+ return OFGameControllerButtonA;
+}
+
+- (OFString *)description
+{
+ return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name];
+}
+@end
ADDED src/hid/platform/Windows/OFGameController.m
Index: src/hid/platform/Windows/OFGameController.m
==================================================================
--- /dev/null
+++ src/hid/platform/Windows/OFGameController.m
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFGameController.h"
+#import "OFArray.h"
+#import "OFNumber.h"
+#import "OFSet.h"
+
+#import "OFInitializationFailedException.h"
+#import "OFReadFailedException.h"
+
+#include
+
+@interface OFGameController ()
+- (instancetype)of_initWithIndex: (DWORD)index OF_METHOD_FAMILY(init);
+@end
+
+struct XInputCapabilitiesEx {
+ XINPUT_CAPABILITIES capabilities;
+ WORD vendorID;
+ WORD productID;
+ WORD versionNumber;
+ WORD unknown1;
+ DWORD unknown2;
+};
+
+static WINAPI DWORD (*XInputGetStateFuncPtr)(DWORD, XINPUT_STATE *);
+static WINAPI DWORD (*XInputGetCapabilitiesExFuncPtr)(DWORD, DWORD, DWORD,
+ struct XInputCapabilitiesEx *);
+
+@implementation OFGameController
+@synthesize vendorID = _vendorID, productID = _productID;
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+@synthesize rightAnalogStickPosition = _rightAnalogStickPosition;
+
++ (void)initialize
+{
+ HMODULE module;
+
+ if (self != [OFGameController class])
+ return;
+
+ if ((module = LoadLibraryA("xinput1_4.dll")) != NULL) {
+ XInputGetStateFuncPtr =
+ (WINAPI DWORD (*)(DWORD, XINPUT_STATE *))
+ GetProcAddress(module, "XInputGetState");
+ XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD,
+ DWORD, struct XInputCapabilitiesEx *))
+ GetProcAddress(module, "XInputGetCapabilitiesEx");
+ }
+}
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ OFMutableArray *controllers = [OFMutableArray array];
+
+ if (XInputGetStateFuncPtr != NULL) {
+ void *pool = objc_autoreleasePoolPush();
+
+ for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
+ OFGameController *controller;
+
+ @try {
+ controller = [[[OFGameController alloc]
+ of_initWithIndex: i] autorelease];
+ } @catch (OFInitializationFailedException *e) {
+ /* Controller does not exist. */
+ continue;
+ }
+
+ [controllers addObject: controller];
+ }
+
+ objc_autoreleasePoolPop(pool);
+ }
+
+ [controllers makeImmutable];
+
+ return controllers;
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_initWithIndex: (DWORD)index
+{
+ self = [super init];
+
+ @try {
+ XINPUT_STATE state = { 0 };
+
+ if (XInputGetStateFuncPtr(index, &state) ==
+ ERROR_DEVICE_NOT_CONNECTED)
+ @throw [OFInitializationFailedException exception];
+
+ _index = index;
+
+ if (XInputGetCapabilitiesExFuncPtr != NULL) {
+ struct XInputCapabilitiesEx capabilities;
+
+ if (XInputGetCapabilitiesExFuncPtr(1, _index,
+ XINPUT_FLAG_GAMEPAD, &capabilities) ==
+ ERROR_SUCCESS) {
+ _vendorID = [[OFNumber alloc]
+ initWithUnsignedShort:
+ capabilities.vendorID];
+ _productID = [[OFNumber alloc]
+ initWithUnsignedShort:
+ capabilities.productID];
+ }
+ }
+
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 16];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_vendorID release];
+ [_productID release];
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ XINPUT_STATE state = { 0 };
+
+ if (XInputGetStateFuncPtr(_index, &state) != ERROR_SUCCESS)
+ @throw [OFReadFailedException exceptionWithObject: self
+ requestedLength: sizeof(state)
+ errNo: 0];
+
+ [_pressedButtons removeAllObjects];
+
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)
+ [_pressedButtons addObject: OFGameControllerButtonDPadUp];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
+ [_pressedButtons addObject: OFGameControllerButtonDPadDown];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadLeft];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
+ [_pressedButtons addObject: OFGameControllerButtonDPadRight];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START)
+ [_pressedButtons addObject: OFGameControllerButtonStart];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK)
+ [_pressedButtons addObject: OFGameControllerButtonSelect];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB)
+ [_pressedButtons addObject: OFGameControllerButtonLeftStick];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB)
+ [_pressedButtons addObject: OFGameControllerButtonRightStick];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)
+ [_pressedButtons addObject: OFGameControllerButtonL];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)
+ [_pressedButtons addObject: OFGameControllerButtonR];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A)
+ [_pressedButtons addObject: OFGameControllerButtonA];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B)
+ [_pressedButtons addObject: OFGameControllerButtonB];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X)
+ [_pressedButtons addObject: OFGameControllerButtonX];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y)
+ [_pressedButtons addObject: OFGameControllerButtonY];
+
+ _ZLPressure = (float)state.Gamepad.bLeftTrigger / 255;
+ _ZRPressure = (float)state.Gamepad.bRightTrigger / 255;
+
+ if (_ZLPressure > 0)
+ [_pressedButtons addObject: OFGameControllerButtonZL];
+ if (_ZRPressure > 0)
+ [_pressedButtons addObject: OFGameControllerButtonZR];
+
+ _leftAnalogStickPosition = OFMakePoint(
+ (float)state.Gamepad.sThumbLX /
+ (state.Gamepad.sThumbLX < 0 ? -INT16_MIN : INT16_MAX),
+ -(float)state.Gamepad.sThumbLY /
+ (state.Gamepad.sThumbLY < 0 ? -INT16_MIN : INT16_MAX));
+ _rightAnalogStickPosition = OFMakePoint(
+ (float)state.Gamepad.sThumbRX /
+ (state.Gamepad.sThumbRX < 0 ? -INT16_MIN : INT16_MAX),
+ -(float)state.Gamepad.sThumbRY /
+ (state.Gamepad.sThumbRY < 0 ? -INT16_MIN : INT16_MAX));
+}
+
+- (OFString *)name
+{
+ return @"XInput 1.3";
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects:
+ OFGameControllerButtonA, OFGameControllerButtonB,
+ OFGameControllerButtonX, OFGameControllerButtonY,
+ OFGameControllerButtonL, OFGameControllerButtonR,
+ OFGameControllerButtonZL, OFGameControllerButtonZR,
+ OFGameControllerButtonStart, OFGameControllerButtonSelect,
+ OFGameControllerButtonLeftStick, OFGameControllerButtonRightStick,
+ OFGameControllerButtonDPadLeft, OFGameControllerButtonDPadRight,
+ OFGameControllerButtonDPadUp, OFGameControllerButtonDPadDown, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return true;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return true;
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ if (button == OFGameControllerButtonZL)
+ return _ZLPressure;
+ if (button == OFGameControllerButtonZR)
+ return _ZRPressure;
+
+ return ([self.pressedButtons containsObject: button] ? 1 : 0);
+}
+
+- (OFGameControllerButton)northButton
+{
+ return OFGameControllerButtonY;
+}
+
+- (OFGameControllerButton)southButton
+{
+ return OFGameControllerButtonA;
+}
+
+- (OFGameControllerButton)westButton
+{
+ return OFGameControllerButtonX;
+}
+
+- (OFGameControllerButton)eastButton
+{
+ return OFGameControllerButtonB;
+}
+
+- (OFString *)description
+{
+ return [OFString stringWithFormat: @"<%@: %@>", self.class, self.name];
+}
+@end
Index: src/test/Makefile
==================================================================
--- src/test/Makefile
+++ src/test/Makefile
@@ -40,8 +40,9 @@
CPPFLAGS += -I. \
-I.. \
-I../.. \
-I../exceptions \
+ -I../hid \
-I../runtime \
-DOBJFWTEST_LOCAL_INCLUDES
LD = ${OBJC}
Index: src/test/OTAppDelegate.m
==================================================================
--- src/test/OTAppDelegate.m
+++ src/test/OTAppDelegate.m
@@ -321,32 +321,28 @@
if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A)
break;
VIDEO_WaitVSync();
}
-#elif defined(OF_NINTENDO_DS)
+#elif defined(OF_NINTENDO_DS) || defined(OF_NINTENDO_3DS)
[OFStdOut setForegroundColor: [OFColor silver]];
[OFStdOut writeLine: @"Press A to continue"];
for (;;) {
- swiWaitForVBlank();
- scanKeys();
-
- if (keysDown() & KEY_A)
- break;
- }
-#elif defined(OF_NINTENDO_3DS)
- [OFStdOut setForegroundColor: [OFColor silver]];
- [OFStdOut writeLine: @"Press A to continue"];
-
- for (;;) {
- hidScanInput();
-
- if (hidKeysDown() & KEY_A)
- break;
-
- gspWaitForVBlank();
+ void *pool = objc_autoreleasePoolPush();
+ OFGameController *controller =
+ [[OFGameController controllers] objectAtIndex: 0];
+
+ if ([controller.pressedButtons containsObject: @"A"])
+ break;
+
+# if defined(OF_NINTENDO_DS)
+ swiWaitForVBlank();
+# elif defined(OF_NINTENDO_3DS)
+ gspWaitForVBlank();
+# endif
+ objc_autoreleasePoolPop(pool);
}
#elif defined(OF_NINTENDO_SWITCH)
[OFStdOut setForegroundColor: [OFColor silver]];
[OFStdOut writeLine: @"Press A to continue"];
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,10 +1,11 @@
include ../extra.mk
-SUBDIRS = ${TESTPLUGIN} \
- ${SUBPROCESS} \
- ${OBJC_SYNC} \
+SUBDIRS = ${TESTPLUGIN} \
+ ${SUBPROCESS} \
+ gamecontroller \
+ ${OBJC_SYNC} \
terminal
CLEAN = EBOOT.PBP \
boot.dol \
${PROG_NOINST}.arm9 \
@@ -121,10 +122,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 +136,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 +147,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 +193,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 +209,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 +231,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,116 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFApplication.h"
+#import "OFArray.h"
+#import "OFColor.h"
+#import "OFGameController.h"
+#import "OFNumber.h"
+#import "OFSet.h"
+#import "OFStdIOStream.h"
+#import "OFThread.h"
+
+@interface GameControllerTests: OFObject
+@end
+
+OF_APPLICATION_DELEGATE(GameControllerTests)
+
+@implementation GameControllerTests
+- (void)applicationDidFinishLaunching: (OFNotification *)notification
+{
+ OFArray *controllers = [OFGameController controllers];
+
+ [OFStdOut clear];
+
+ for (;;) {
+ [OFStdOut setCursorPosition: OFMakePoint(0, 0)];
+
+ for (OFGameController *controller in controllers) {
+ OFArray OF_GENERIC(OFGameControllerButton) *buttons =
+ controller.buttons.allObjects.sortedArray;
+ size_t i = 0;
+
+ [OFStdOut setForegroundColor: [OFColor green]];
+ [OFStdOut writeString: controller.name];
+
+ if (controller.vendorID != nil &&
+ controller.productID != nil) {
+ [OFStdOut setForegroundColor: [OFColor teal]];
+ [OFStdOut writeFormat: @" [%04X:%04X]",
+ controller.vendorID.unsignedShortValue,
+ controller.productID.unsignedShortValue];
+ }
+
+ [OFStdOut setForegroundColor: [OFColor blue]];
+ [OFStdOut writeFormat:
+ @"\nNorth: %@ South: %@ West: %@ East: %@\n",
+ controller.northButton, controller.southButton,
+ controller.westButton, controller.eastButton];
+
+ [controller retrieveState];
+
+ for (OFGameControllerButton button in buttons) {
+ float 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 == 5) {
+ [OFStdOut writeString: @"\n"];
+ i = 0;
+ } else
+ [OFStdOut writeString: @" "];
+ }
+ [OFStdOut setForegroundColor: [OFColor gray]];
+ [OFStdOut writeString: @"\n"];
+
+ if (controller.hasLeftAnalogStick) {
+ OFPoint position =
+ controller.leftAnalogStickPosition;
+ [OFStdOut writeFormat: @"(%5.2f, %5.2f) ",
+ position.x, position.y];
+ }
+ if (controller.hasRightAnalogStick) {
+ OFPoint position =
+ controller.rightAnalogStickPosition;
+ [OFStdOut writeFormat: @"(%5.2f, %5.2f)",
+ position.x, position.y];
+ }
+ [OFStdOut writeString: @"\n"];
+ }
+
+ [OFThread sleepForTimeInterval: 1.f / 60.f];
+ }
+}
+@end
ADDED tests/gamecontroller/Makefile
Index: tests/gamecontroller/Makefile
==================================================================
--- /dev/null
+++ tests/gamecontroller/Makefile
@@ -0,0 +1,106 @@
+include ../../extra.mk
+
+PROG_NOINST = gamecontroller_tests${PROG_SUFFIX}
+SRCS = GameControllerTests.m
+
+include ../../buildsys.mk
+
+.PHONY: run
+run:
+ rm -f libobjfw.so.${OBJFW_LIB_MAJOR}
+ rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}
+ rm -f objfw${OBJFW_LIB_MAJOR}.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib
+ rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}
+ rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}
+ rm -f objfwrt${OBJFWRT_LIB_MAJOR}.dll
+ rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib
+ 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
+
+${PROG_NOINST}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFWRT_DEP_LVL2} \
+ ${LIBOBJFWHID_DEP_LVL2}
+
+CPPFLAGS += -I../../src \
+ -I../../src/exceptions \
+ -I../../src/hid \
+ -I../../src/runtime \
+ -I../.. \
+ -DOBJFWHID_LOCAL_INCLUDES
+LIBS := -L../../src/hid -lobjfwhid \
+ -L../../src -lobjfw \
+ -L../../src/runtime ${RUNTIME_LIBS} \
+ ${LIBS}
+LD = ${OBJC}