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/OFEvdevGameController.h
Index: src/hid/OFEvdevGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFEvdevGameController.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#import "OFGameController.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+@interface OFEvdevGameController: OFGameController
+{
+ OFString *_path;
+ int _fd;
+ uint16_t _vendorID, _productID;
+ OFString *_name;
+ OFMutableSet *_buttons, *_pressedButtons;
+ bool _hasLeftAnalogStick, _hasRightAnalogStick;
+ bool _hasLeftTriggerPressure, _hasRightTriggerPressure;
+ unsigned int _leftTriggerPressureBit, _rightTriggerPressureBit;
+ OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition;
+ float _leftTriggerPressure, _rightTriggerPressure;
+ int32_t _leftAnalogStickMinX, _leftAnalogStickMaxX;
+ int32_t _leftAnalogStickMinY, _leftAnalogStickMaxY;
+ unsigned int _rightAnalogStickXBit, _rightAnalogStickYBit;
+ int32_t _rightAnalogStickMinX, _rightAnalogStickMaxX;
+ int32_t _rightAnalogStickMinY, _rightAnalogStickMaxY;
+ int32_t _leftTriggerMinPressure, _leftTriggerMaxPressure;
+ int32_t _rightTriggerMinPressure, _rightTriggerMaxPressure;
+}
+
+- (instancetype)of_initWithPath: (OFString *)path OF_METHOD_FAMILY(init);
+@end
+
+OF_ASSUME_NONNULL_END
ADDED src/hid/OFEvdevGameController.m
Index: src/hid/OFEvdevGameController.m
==================================================================
--- /dev/null
+++ src/hid/OFEvdevGameController.m
@@ -0,0 +1,601 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#import "OFEvdevGameController.h"
+#import "OFArray.h"
+#import "OFFileManager.h"
+#import "OFLocale.h"
+#import "OFNumber.h"
+#import "OFSet.h"
+
+#include
+#include
+
+#import "OFInitializationFailedException.h"
+#import "OFInvalidArgumentException.h"
+#import "OFOpenItemFailedException.h"
+#import "OFOutOfRangeException.h"
+#import "OFReadFailedException.h"
+
+/*
+ * Controllers with tested correct mapping:
+ *
+ * Microsoft X-Box 360 pad [045E:028E]
+ * Joy-Con (L) [057E:2006]
+ * Joy-Con (R) [057E:2007]
+ * N64 Controller [057E:2019]
+ * Sony Interactive Entertainment DualSense Wireless Controller [054C:0CE6]
+ * 8BitDo Pro 2 Wired Controller [2DC8:3106]
+ * Stadia2SZY-0d6c [18D1:9400]
+ * Wireless Controller [054C:09CC]
+ */
+
+static const uint16_t vendorIDMicrosoft = 0x045E;
+static const uint16_t vendorIDNintendo = 0x057E;
+static const uint16_t vendorIDSony = 0x054C;
+static const uint16_t vendorIDGoogle = 0x18D1;
+
+/* Microsoft controllers */
+static const uint16_t productIDXbox360 = 0x028E;
+
+/* Nintendo controllers */
+static const uint16_t productIDLeftJoycon = 0x2006;
+static const uint16_t productIDRightJoycon = 0x2007;
+static const uint16_t productIDN64Controller = 0x2019;
+
+/* Sony controllers */
+static const uint16_t productIDDualSense = 0x0CE6;
+static const uint16_t productIDDualShock4 = 0x09CC;
+
+/* Google controllers */
+static const uint16_t productIDStadia = 0x9400;
+
+static const uint16_t buttons[] = {
+ BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, BTN_TL, BTN_TR, BTN_TL2,
+ BTN_TR2, BTN_SELECT, BTN_START, BTN_MODE, BTN_THUMBL, BTN_THUMBR,
+ BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT,
+ BTN_TRIGGER_HAPPY1, BTN_TRIGGER_HAPPY2
+};
+
+static OFGameControllerButton
+buttonToName(uint16_t button, uint16_t vendorID, uint16_t productID)
+{
+ if (vendorID == vendorIDNintendo &&
+ productID == productIDLeftJoycon) {
+ switch (button) {
+ case BTN_Z:
+ return OFGameControllerCaptureButton;
+ case BTN_TR:
+ return OFGameControllerSLButton;
+ case BTN_TR2:
+ return OFGameControllerSRButton;
+ }
+ } else if (vendorID == vendorIDNintendo &&
+ productID == productIDRightJoycon) {
+ switch (button) {
+ case BTN_NORTH:
+ return OFGameControllerNorthButton;
+ case BTN_WEST:
+ return OFGameControllerWestButton;
+ case BTN_TL:
+ return OFGameControllerSLButton;
+ case BTN_TL2:
+ return OFGameControllerSRButton;
+ }
+ } else if (vendorID == vendorIDNintendo &&
+ productID == productIDN64Controller) {
+ switch (button) {
+ case BTN_A:
+ return OFGameControllerAButton;
+ case BTN_B:
+ return OFGameControllerBButton;
+ case BTN_SELECT:
+ return OFGameControllerCPadUpButton;
+ case BTN_X:
+ return OFGameControllerCPadDownButton;
+ case BTN_Y:
+ return OFGameControllerCPadLeftButton;
+ case BTN_C:
+ return OFGameControllerCPadRightButton;
+ case BTN_Z:
+ return OFGameControllerCaptureButton;
+ }
+ } else if (vendorID == vendorIDSony &&
+ (productID == productIDDualSense ||
+ productID == productIDDualShock4)) {
+ switch (button) {
+ case BTN_NORTH:
+ return OFGameControllerNorthButton;
+ case BTN_WEST:
+ return OFGameControllerWestButton;
+ }
+ } else if (vendorID == vendorIDGoogle && productID == productIDStadia) {
+ switch (button) {
+ case BTN_TRIGGER_HAPPY1:
+ return OFGameControllerAssistantButton;
+ case BTN_TRIGGER_HAPPY2:
+ return OFGameControllerCaptureButton;
+ }
+ }
+
+ switch (button) {
+ case BTN_Y:
+ return OFGameControllerNorthButton;
+ case BTN_A:
+ return OFGameControllerSouthButton;
+ case BTN_X:
+ return OFGameControllerWestButton;
+ case BTN_B:
+ return OFGameControllerEastButton;
+ case BTN_TL2:
+ return OFGameControllerLeftTriggerButton;
+ case BTN_TR2:
+ return OFGameControllerRightTriggerButton;
+ case BTN_TL:
+ return OFGameControllerLeftShoulderButton;
+ case BTN_TR:
+ return OFGameControllerRightShoulderButton;
+ case BTN_THUMBL:
+ return OFGameControllerLeftStickButton;
+ case BTN_THUMBR:
+ return OFGameControllerRightStickButton;
+ case BTN_DPAD_UP:
+ return OFGameControllerDPadUpButton;
+ case BTN_DPAD_DOWN:
+ return OFGameControllerDPadDownButton;
+ case BTN_DPAD_LEFT:
+ return OFGameControllerDPadLeftButton;
+ case BTN_DPAD_RIGHT:
+ return OFGameControllerDPadRightButton;
+ case BTN_START:
+ return OFGameControllerStartButton;
+ case BTN_SELECT:
+ return OFGameControllerSelectButton;
+ case BTN_MODE:
+ return OFGameControllerHomeButton;
+ case BTN_C:
+ return OFGameControllerCButton;
+ case BTN_Z:
+ return OFGameControllerZButton;
+ }
+
+ return nil;
+}
+
+static float
+scale(float value, float min, float max)
+{
+ if (value < min)
+ value = min;
+ if (value > max)
+ value = max;
+
+ return ((value - min) / (max - min) * 2) - 1;
+}
+
+@implementation OFEvdevGameController
+@synthesize name = _name, buttons = _buttons;
+@synthesize hasLeftAnalogStick = _hasLeftAnalogStick;
+@synthesize hasRightAnalogStick = _hasRightAnalogStick;
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+@synthesize rightAnalogStickPosition = _rightAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ OFMutableArray *controllers = [OFMutableArray array];
+ void *pool = objc_autoreleasePoolPush();
+
+ for (OFString *device in [[OFFileManager defaultManager]
+ contentsOfDirectoryAtPath: @"/dev/input"]) {
+ OFString *path;
+ OFGameController *controller;
+
+ if (![device hasPrefix: @"event"])
+ continue;
+
+ path = [@"/dev/input" stringByAppendingPathComponent: device];
+
+ @try {
+ controller = [[[OFEvdevGameController alloc]
+ of_initWithPath: path] autorelease];
+ } @catch (OFOpenItemFailedException *e) {
+ if (e.errNo == EACCES)
+ continue;
+
+ @throw e;
+ } @catch (OFInvalidArgumentException *e) {
+ /* Not a game controller. */
+ continue;
+ }
+
+ [controllers addObject: controller];
+ }
+
+ [controllers sort];
+ [controllers makeImmutable];
+
+ objc_autoreleasePoolPop(pool);
+
+ return controllers;
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_initWithPath: (OFString *)path
+{
+ self = [super init];
+
+ @try {
+ OFStringEncoding encoding = [OFLocale encoding];
+ unsigned long evBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ EV_MAX) / OF_ULONG_BIT] = { 0 };
+ unsigned long keyBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ KEY_MAX) / OF_ULONG_BIT] = { 0 };
+ unsigned long absBits[OFRoundUpToPowerOf2(OF_ULONG_BIT,
+ ABS_MAX) / OF_ULONG_BIT] = { 0 };
+ struct input_id inputID;
+ char name[128];
+
+ _path = [path copy];
+
+ if ((_fd = open([_path cStringWithEncoding: encoding],
+ O_RDONLY | O_NONBLOCK)) == -1)
+ @throw [OFOpenItemFailedException
+ exceptionWithPath: _path
+ mode: @"r"
+ errNo: errno];
+
+ if (ioctl(_fd, EVIOCGBIT(0, sizeof(evBits)), evBits) == -1)
+ @throw [OFInitializationFailedException exception];
+
+ if (!OFBitSetIsSet(evBits, EV_KEY))
+ @throw [OFInvalidArgumentException exception];
+
+ if (ioctl(_fd, EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) ==
+ -1)
+ @throw [OFInitializationFailedException exception];
+
+ if (!OFBitSetIsSet(keyBits, BTN_GAMEPAD) &&
+ !OFBitSetIsSet(keyBits, BTN_DPAD_UP))
+ @throw [OFInvalidArgumentException exception];
+
+ if (ioctl(_fd, EVIOCGID, &inputID) == -1)
+ @throw [OFInvalidArgumentException exception];
+
+ _vendorID = inputID.vendor;
+ _productID = inputID.product;
+
+ if (ioctl(_fd, EVIOCGNAME(sizeof(name)), name) == -1)
+ @throw [OFInitializationFailedException exception];
+
+ _name = [[OFString alloc] initWithCString: name
+ encoding: encoding];
+
+ _buttons = [[OFMutableSet alloc] init];
+ for (size_t i = 0; i < sizeof(buttons) / sizeof(*buttons);
+ i++) {
+ if (OFBitSetIsSet(keyBits, buttons[i])) {
+ OFGameControllerButton button = buttonToName(
+ buttons[i], _vendorID, _productID);
+
+ if (button != nil)
+ [_buttons addObject: button];
+ }
+ }
+
+ _pressedButtons = [[OFMutableSet alloc] init];
+
+ if (OFBitSetIsSet(evBits, EV_ABS)) {
+ if (ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(absBits)),
+ absBits) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (_vendorID == vendorIDGoogle &&
+ _productID == productIDStadia) {
+ /*
+ * It's unclear how this can be screwed up
+ * *this* bad.
+ */
+ _rightAnalogStickXBit = ABS_Z;
+ _rightAnalogStickYBit = ABS_RZ;
+ _leftTriggerPressureBit = ABS_BRAKE;
+ _rightTriggerPressureBit = ABS_GAS;
+ } else {
+ _rightAnalogStickXBit = ABS_RX;
+ _rightAnalogStickYBit = ABS_RY;
+ _leftTriggerPressureBit = ABS_Z;
+ _rightTriggerPressureBit = ABS_RZ;
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_X) &&
+ OFBitSetIsSet(absBits, ABS_Y)) {
+ struct input_absinfo infoX, infoY;
+
+ _hasLeftAnalogStick = true;
+
+ if (ioctl(_fd, EVIOCGABS(ABS_X), &infoX) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (ioctl(_fd, EVIOCGABS(ABS_Y), &infoY) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _leftAnalogStickMinX = infoX.minimum;
+ _leftAnalogStickMaxX = infoX.maximum;
+ _leftAnalogStickMinY = infoY.minimum;
+ _leftAnalogStickMaxY = infoY.maximum;
+ }
+
+ if (OFBitSetIsSet(absBits, _rightAnalogStickXBit) &&
+ OFBitSetIsSet(absBits, _rightAnalogStickYBit)) {
+ struct input_absinfo infoX, infoY;
+
+ _hasRightAnalogStick = true;
+
+ if (ioctl(_fd, EVIOCGABS(_rightAnalogStickXBit),
+ &infoX) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ if (ioctl(_fd, EVIOCGABS(_rightAnalogStickYBit),
+ &infoY) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _rightAnalogStickMinX = infoX.minimum;
+ _rightAnalogStickMaxX = infoX.maximum;
+ _rightAnalogStickMinY = infoY.minimum;
+ _rightAnalogStickMaxY = infoY.maximum;
+ }
+
+ if (OFBitSetIsSet(absBits, ABS_HAT0X) &&
+ OFBitSetIsSet(absBits, ABS_HAT0Y)) {
+ [_buttons addObject:
+ OFGameControllerDPadLeftButton];
+ [_buttons addObject:
+ OFGameControllerDPadRightButton];
+ [_buttons addObject:
+ OFGameControllerDPadUpButton];
+ [_buttons addObject:
+ OFGameControllerDPadDownButton];
+ }
+
+ if (OFBitSetIsSet(absBits, _leftTriggerPressureBit)) {
+ struct input_absinfo info;
+
+ _hasLeftTriggerPressure = true;
+
+ if (ioctl(_fd, EVIOCGABS(
+ _leftTriggerPressureBit), &info) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _leftTriggerMinPressure = info.minimum;
+ _leftTriggerMaxPressure = info.maximum;
+
+ [_buttons addObject:
+ OFGameControllerLeftTriggerButton];
+ }
+
+ if (OFBitSetIsSet(absBits, _rightTriggerPressureBit)) {
+ struct input_absinfo info;
+
+ _hasRightTriggerPressure = true;
+
+ if (ioctl(_fd, EVIOCGABS(
+ _rightTriggerPressureBit), &info) == -1)
+ @throw [OFInitializationFailedException
+ exception];
+
+ _rightTriggerMinPressure = info.minimum;
+ _rightTriggerMaxPressure = info.maximum;
+
+ [_buttons addObject:
+ OFGameControllerRightTriggerButton];
+ }
+ }
+
+ [_buttons makeImmutable];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_path release];
+
+ if (_fd != -1)
+ close(_fd);
+
+ [_name release];
+ [_buttons release];
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (OFNumber *)vendorID
+{
+ return [OFNumber numberWithUnsignedShort: _vendorID];
+}
+
+- (OFNumber *)productID
+{
+ return [OFNumber numberWithUnsignedShort: _productID];
+}
+
+- (void)retrieveState
+{
+ struct input_event event;
+
+ for (;;) {
+ OFGameControllerButton button;
+
+ errno = 0;
+
+ if (read(_fd, &event, sizeof(event)) < (int)sizeof(event)) {
+ if (errno == EWOULDBLOCK)
+ return;
+
+ @throw [OFReadFailedException
+ exceptionWithObject: self
+ requestedLength: sizeof(event)
+ errNo: errno];
+ }
+
+ switch (event.type) {
+ case EV_KEY:
+ if ((button = buttonToName(event.code, _vendorID,
+ _productID)) != nil) {
+ if (event.value)
+ [_pressedButtons addObject: button];
+ else
+ [_pressedButtons removeObject: button];
+ }
+ break;
+ case EV_ABS:
+ if (event.code == ABS_X)
+ _leftAnalogStickPosition.x = scale(event.value,
+ _leftAnalogStickMinX, _leftAnalogStickMaxX);
+ else if (event.code == ABS_Y)
+ _leftAnalogStickPosition.y = scale(event.value,
+ _leftAnalogStickMinY, _leftAnalogStickMaxY);
+ else if (event.code == _rightAnalogStickXBit)
+ _rightAnalogStickPosition.x = scale(event.value,
+ _rightAnalogStickMinX,
+ _rightAnalogStickMaxX);
+ else if (event.code == _rightAnalogStickYBit)
+ _rightAnalogStickPosition.y = scale(event.value,
+ _rightAnalogStickMinY,
+ _rightAnalogStickMaxY);
+ else if (event.code == ABS_HAT0X) {
+ if (event.value < 0) {
+ [_pressedButtons addObject:
+ OFGameControllerDPadLeftButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadRightButton];
+ } else if (event.value > 0) {
+ [_pressedButtons addObject:
+ OFGameControllerDPadRightButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadLeftButton];
+ } else {
+ [_pressedButtons removeObject:
+ OFGameControllerDPadLeftButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadRightButton];
+ }
+ } else if (event.code == ABS_HAT0Y) {
+ if (event.value < 0) {
+ [_pressedButtons addObject:
+ OFGameControllerDPadUpButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadDownButton];
+ } else if (event.value > 0) {
+ [_pressedButtons addObject:
+ OFGameControllerDPadDownButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadUpButton];
+ } else {
+ [_pressedButtons removeObject:
+ OFGameControllerDPadUpButton];
+ [_pressedButtons removeObject:
+ OFGameControllerDPadDownButton];
+ }
+ } else if (event.code == _leftTriggerPressureBit) {
+ _leftTriggerPressure = scale(event.value,
+ _leftTriggerMinPressure,
+ _leftTriggerMaxPressure);
+
+ if (_leftTriggerPressure > 0)
+ [_pressedButtons addObject:
+ OFGameControllerLeftTriggerButton];
+ else
+ [_pressedButtons removeObject:
+ OFGameControllerLeftTriggerButton];
+ } else if (event.code == _rightTriggerPressureBit) {
+ _rightTriggerPressure = scale(event.value,
+ _rightTriggerMinPressure,
+ _rightTriggerMaxPressure);
+
+ if (_rightTriggerPressure > 0)
+ [_pressedButtons addObject:
+ OFGameControllerRightTriggerButton];
+ else
+ [_pressedButtons removeObject:
+ OFGameControllerRightTriggerButton];
+ }
+ break;
+ }
+ }
+}
+
+- (OFComparisonResult)compare: (OFEvdevGameController *)otherController
+{
+ unsigned long long selfIndex, otherIndex;
+
+ if (![otherController isKindOfClass: [OFEvdevGameController class]])
+ @throw [OFInvalidArgumentException exception];
+
+ selfIndex = [_path substringFromIndex: 16].unsignedLongLongValue;
+ otherIndex = [otherController->_path substringFromIndex: 16]
+ .unsignedLongLongValue;
+
+ if (selfIndex > otherIndex)
+ return OFOrderedDescending;
+ if (selfIndex < otherIndex)
+ return OFOrderedAscending;
+
+ return OFOrderedSame;
+}
+
+- (OFSet *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ if (button == OFGameControllerLeftTriggerButton &&
+ _hasLeftTriggerPressure)
+ return _leftTriggerPressure;
+ if (button == OFGameControllerRightTriggerButton &&
+ _hasRightTriggerPressure)
+ return _rightTriggerPressure;
+
+ return [super pressureForButton: button];
+}
+@end
ADDED src/hid/OFGameController.h
Index: src/hid/OFGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFGameController.h
@@ -0,0 +1,335 @@
+/*
+ * 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
+ * * @ref OFGameControllerAButton
+ * * @ref OFGameControllerBButton
+ * * @ref OFGameControllerCButton
+ * * @ref OFGameControllerXButton
+ * * @ref OFGameControllerYButton
+ * * @ref OFGameControllerZButton
+ * * @ref OFGameControllerCPadUpButton
+ * * @ref OFGameControllerCPadDownButton
+ * * @ref OFGameControllerCPadLeftButton
+ * * @ref OFGameControllerCPadRightButton
+ * * @ref OFGameControllerSLButton
+ * * @ref OFGameControllerSRButton
+ * * @ref OFGameControllerModeButton
+ */
+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;
+
+/**
+ * @brief The A button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerAButton;
+
+/**
+ * @brief The B button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerBButton;
+
+/**
+ * @brief The C button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerCButton;
+
+/**
+ * @brief The X button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerXButton;
+
+/**
+ * @brief The Y button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerYButton;
+
+/**
+ * @brief The Z button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerZButton;
+
+/**
+ * @brief The C-Pad Up button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerCPadUpButton;
+
+/**
+ * @brief The C-Pad Down button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerCPadDownButton;
+
+/**
+ * @brief The C-Pad Left button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerCPadLeftButton;
+
+/**
+ * @brief The C-Pad Right button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerCPadRightButton;
+
+/**
+ * @brief The SL button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerSLButton;
+
+/**
+ * @brief The SR button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerSRButton;
+
+/**
+ * @brief The Mode button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerModeButton;
+
+/**
+ * @brief The Assistant button on a game controller.
+ */
+extern const OFGameControllerButton OFGameControllerAssistantButton;
+#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.
+ */
+@property (readonly, nonatomic) bool hasRightAnalogStick;
+
+/**
+ * @brief The position of the right analog stick.
+ *
+ * The range is from (-1, -1) to (1, 1).
+ */
+@property (readonly, nonatomic) OFPoint rightAnalogStickPosition;
+
+/**
+ * @brief Returns the available controllers.
+ *
+ * @return The available controllers
+ */
++ (OFArray OF_GENERIC(OFGameController *) *)controllers;
+
+/**
+ * @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,156 @@
+/*
+ * 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_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";
+const OFGameControllerButton OFGameControllerAButton = @"A";
+const OFGameControllerButton OFGameControllerBButton = @"B";
+const OFGameControllerButton OFGameControllerCButton = @"C";
+const OFGameControllerButton OFGameControllerXButton = @"X";
+const OFGameControllerButton OFGameControllerYButton = @"Y";
+const OFGameControllerButton OFGameControllerZButton = @"Z";
+const OFGameControllerButton OFGameControllerCPadUpButton = @"C-Pad Up";
+const OFGameControllerButton OFGameControllerCPadDownButton = @"C-Pad Down";
+const OFGameControllerButton OFGameControllerCPadLeftButton = @"C-Pad Left";
+const OFGameControllerButton OFGameControllerCPadRightButton = @"C-Pad Right";
+const OFGameControllerButton OFGameControllerSLButton = @"SL";
+const OFGameControllerButton OFGameControllerSRButton = @"SR";
+const OFGameControllerButton OFGameControllerModeButton = @"Mode";
+const OFGameControllerButton OFGameControllerAssistantButton = @"Assistant";
+
+@implementation OFGameController
+@dynamic name, buttons, pressedButtons, hasLeftAnalogStick;
+@dynamic leftAnalogStickPosition, hasRightAnalogStick, rightAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+#if defined(OF_LINUX) && defined(OF_HAVE_FILES)
+ return [OFEvdevGameController controllers];
+#elif defined(OF_WINDOWS)
+ return [OFXInputGameController controllers];
+#elif defined(OF_NINTENDO_DS)
+ return [OFNintendoDSGameController controllers];
+#elif defined(OF_NINTENDO_3DS)
+ return [OFNintendo3DSGameController controllers];
+#else
+ return [OFArray array];
+#endif
+}
+
+- (instancetype)init
+{
+ 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_NINTENDO_DS
+# include "OFNintendoDSGameController.m"
+#endif
+#ifdef OF_NINTENDO_3DS
+# include "OFNintendo3DSGameController.m"
+#endif
ADDED src/hid/OFNintendo3DSGameController.h
Index: src/hid/OFNintendo3DSGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFNintendo3DSGameController.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#import "OFGameController.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+@interface OFNintendo3DSGameController: OFGameController
+{
+ OFMutableSet *_pressedButtons;
+ OFPoint _leftAnalogStickPosition;
+}
+@end
+
+OF_ASSUME_NONNULL_END
ADDED src/hid/OFNintendo3DSGameController.m
Index: src/hid/OFNintendo3DSGameController.m
==================================================================
--- /dev/null
+++ src/hid/OFNintendo3DSGameController.m
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFNintendo3DSGameController.h"
+#import "OFArray.h"
+#import "OFSet.h"
+
+#import "OFOutOfRangeException.h"
+
+#define id id_3ds
+#include <3ds.h>
+#undef id
+
+static OFArray OF_GENERIC(OFGameController *) *controllers;
+
+static void
+initControllers(void)
+{
+ void *pool = objc_autoreleasePoolPush();
+
+ controllers = [[OFArray alloc] initWithObject:
+ [[[OFNintendo3DSGameController alloc] init] autorelease]];
+
+ objc_autoreleasePoolPop(pool);
+}
+
+@implementation OFNintendo3DSGameController
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ static OFOnceControl onceControl = OFOnceControlInitValue;
+
+ OFOnce(&onceControl, initControllers);
+
+ return [[controllers retain] autorelease];
+}
+
+- (instancetype)init
+{
+ self = [super init];
+
+ @try {
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 18];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ u32 keys;
+ circlePosition pos;
+
+ hidScanInput();
+
+ keys = hidKeysHeld();
+ hidCircleRead(&pos);
+
+ [_pressedButtons removeAllObjects];
+
+ if (keys & KEY_X)
+ [_pressedButtons addObject: OFGameControllerNorthButton];
+ if (keys & KEY_B)
+ [_pressedButtons addObject: OFGameControllerSouthButton];
+ if (keys & KEY_Y)
+ [_pressedButtons addObject: OFGameControllerWestButton];
+ if (keys & KEY_A)
+ [_pressedButtons addObject: OFGameControllerEastButton];
+ if (keys & KEY_ZL)
+ [_pressedButtons addObject: OFGameControllerLeftTriggerButton];
+ if (keys & KEY_ZR)
+ [_pressedButtons addObject: OFGameControllerRightTriggerButton];
+ if (keys & KEY_L)
+ [_pressedButtons addObject: OFGameControllerLeftShoulderButton];
+ if (keys & KEY_R)
+ [_pressedButtons addObject:
+ OFGameControllerRightShoulderButton];
+ if (keys & KEY_DUP)
+ [_pressedButtons addObject: OFGameControllerDPadUpButton];
+ if (keys & KEY_DDOWN)
+ [_pressedButtons addObject: OFGameControllerDPadDownButton];
+ if (keys & KEY_DLEFT)
+ [_pressedButtons addObject: OFGameControllerDPadLeftButton];
+ if (keys & KEY_DRIGHT)
+ [_pressedButtons addObject: OFGameControllerDPadRightButton];
+ if (keys & KEY_START)
+ [_pressedButtons addObject: OFGameControllerStartButton];
+ if (keys & KEY_SELECT)
+ [_pressedButtons addObject: OFGameControllerSelectButton];
+ if (keys & KEY_CSTICK_UP)
+ [_pressedButtons addObject: OFGameControllerCPadUpButton];
+ if (keys & KEY_CSTICK_DOWN)
+ [_pressedButtons addObject: OFGameControllerCPadDownButton];
+ if (keys & KEY_CSTICK_LEFT)
+ [_pressedButtons addObject: OFGameControllerCPadLeftButton];
+ if (keys & KEY_CSTICK_RIGHT)
+ [_pressedButtons addObject: OFGameControllerCPadRightButton];
+
+ _leftAnalogStickPosition = OFMakePoint(
+ (float)pos.dx / (pos.dx < 0 ? -INT16_MIN : INT16_MAX),
+ (float)pos.dy / (pos.dy < 0 ? -INT16_MIN : INT16_MAX));
+}
+
+- (OFString *)name
+{
+ return @"Nintendo 3DS";
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects:
+ OFGameControllerNorthButton,
+ OFGameControllerSouthButton,
+ OFGameControllerWestButton,
+ OFGameControllerEastButton,
+ OFGameControllerLeftTriggerButton,
+ OFGameControllerRightTriggerButton,
+ OFGameControllerRightShoulderButton,
+ OFGameControllerLeftShoulderButton,
+ OFGameControllerDPadUpButton,
+ OFGameControllerDPadDownButton,
+ OFGameControllerDPadLeftButton,
+ OFGameControllerDPadRightButton,
+ OFGameControllerStartButton,
+ OFGameControllerSelectButton,
+ OFGameControllerCPadRightButton,
+ OFGameControllerCPadLeftButton,
+ OFGameControllerCPadUpButton,
+ OFGameControllerCPadDownButton, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return true;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return false;
+}
+@end
ADDED src/hid/OFNintendoDSGameController.h
Index: src/hid/OFNintendoDSGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFNintendoDSGameController.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#import "OFGameController.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+@interface OFNintendoDSGameController: OFGameController
+{
+ OFMutableSet *_pressedButtons;
+}
+@end
+
+OF_ASSUME_NONNULL_END
ADDED src/hid/OFNintendoDSGameController.m
Index: src/hid/OFNintendoDSGameController.m
==================================================================
--- /dev/null
+++ src/hid/OFNintendoDSGameController.m
@@ -0,0 +1,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 "OFNintendoDSGameController.h"
+#import "OFArray.h"
+#import "OFSet.h"
+
+#import "OFOutOfRangeException.h"
+
+#define asm __asm__
+#include
+#undef asm
+
+static OFArray OF_GENERIC(OFGameController *) *controllers;
+
+static void
+initControllers(void)
+{
+ void *pool = objc_autoreleasePoolPush();
+
+ controllers = [[OFArray alloc] initWithObject:
+ [[[OFNintendoDSGameController alloc] init] autorelease]];
+
+ objc_autoreleasePoolPop(pool);
+}
+
+@implementation OFNintendoDSGameController
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ static OFOnceControl onceControl = OFOnceControlInitValue;
+
+ OFOnce(&onceControl, initControllers);
+
+ return [[controllers retain] autorelease];
+}
+
+- (instancetype)init
+{
+ self = [super init];
+
+ @try {
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 12];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ uint32 keys;
+
+ scanKeys();
+ keys = keysCurrent();
+
+ [_pressedButtons removeAllObjects];
+
+ if (keys & KEY_X)
+ [_pressedButtons addObject: OFGameControllerNorthButton];
+ if (keys & KEY_B)
+ [_pressedButtons addObject: OFGameControllerSouthButton];
+ if (keys & KEY_Y)
+ [_pressedButtons addObject: OFGameControllerWestButton];
+ if (keys & KEY_A)
+ [_pressedButtons addObject: OFGameControllerEastButton];
+ if (keys & KEY_L)
+ [_pressedButtons addObject: OFGameControllerLeftShoulderButton];
+ if (keys & KEY_R)
+ [_pressedButtons addObject:
+ OFGameControllerRightShoulderButton];
+ if (keys & KEY_UP)
+ [_pressedButtons addObject: OFGameControllerDPadUpButton];
+ if (keys & KEY_DOWN)
+ [_pressedButtons addObject: OFGameControllerDPadDownButton];
+ if (keys & KEY_LEFT)
+ [_pressedButtons addObject: OFGameControllerDPadLeftButton];
+ if (keys & KEY_RIGHT)
+ [_pressedButtons addObject: OFGameControllerDPadRightButton];
+ if (keys & KEY_START)
+ [_pressedButtons addObject: OFGameControllerStartButton];
+ if (keys & KEY_SELECT)
+ [_pressedButtons addObject: OFGameControllerSelectButton];
+}
+
+- (OFString *)name
+{
+ return @"Nintendo DS";
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects:
+ OFGameControllerNorthButton,
+ OFGameControllerSouthButton,
+ OFGameControllerWestButton,
+ OFGameControllerEastButton,
+ OFGameControllerLeftShoulderButton,
+ OFGameControllerRightShoulderButton,
+ OFGameControllerDPadUpButton,
+ OFGameControllerDPadDownButton,
+ OFGameControllerDPadLeftButton,
+ OFGameControllerDPadRightButton,
+ OFGameControllerStartButton,
+ OFGameControllerSelectButton, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return false;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return false;
+}
+@end
ADDED src/hid/OFXInputGameController.h
Index: src/hid/OFXInputGameController.h
==================================================================
--- /dev/null
+++ src/hid/OFXInputGameController.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#import "OFGameController.h"
+
+OF_ASSUME_NONNULL_BEGIN
+
+@interface OFXInputGameController: OFGameController
+{
+ DWORD _index;
+ OFNumber *_Nullable _vendorID, *_Nullable productID;
+ OFMutableSet *_pressedButtons;
+ OFPoint _leftAnalogStickPosition, _rightAnalogStickPosition;
+ float _leftTriggerPressure, _rightTriggerPressure;
+}
+
+- (instancetype)of_initWithIndex: (DWORD)index OF_METHOD_FAMILY(init);
+@end
+
+OF_ASSUME_NONNULL_END
ADDED src/hid/OFXInputGameController.m
Index: src/hid/OFXInputGameController.m
==================================================================
--- /dev/null
+++ src/hid/OFXInputGameController.m
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2008-2024 Jonathan Schleifer
+ *
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 3.0 only,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * version 3.0 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3.0 along with this program. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#import "OFXInputGameController.h"
+#import "OFArray.h"
+#import "OFNumber.h"
+#import "OFSet.h"
+
+#import "OFInitializationFailedException.h"
+#import "OFReadFailedException.h"
+
+#include
+
+struct XInputCapabilitiesEx {
+ XINPUT_CAPABILITIES capabilities;
+ WORD vendorID;
+ WORD productID;
+ WORD versionNumber;
+ WORD unknown1;
+ DWORD unknown2;
+};
+
+static WINAPI DWORD (*XInputGetStateFuncPtr)(DWORD, XINPUT_STATE *);
+static WINAPI DWORD (*XInputGetCapabilitiesExFuncPtr)(DWORD, DWORD, DWORD,
+ struct XInputCapabilitiesEx *);
+static const char *XInputVersion;
+
+@implementation OFXInputGameController
+@synthesize vendorID = _vendorID, productID = _productID;
+@synthesize leftAnalogStickPosition = _leftAnalogStickPosition;
+@synthesize rightAnalogStickPosition = _rightAnalogStickPosition;
+
++ (void)initialize
+{
+ HMODULE module;
+
+ if (self != [OFXInputGameController class])
+ return;
+
+ if ((module = LoadLibraryA("xinput1_4.dll")) != NULL) {
+ XInputGetStateFuncPtr =
+ (WINAPI DWORD (*)(DWORD, XINPUT_STATE *))
+ GetProcAddress(module, "XInputGetState");
+ XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD,
+ DWORD, struct XInputCapabilitiesEx *))
+ GetProcAddress(module, "XInputGetCapabilitiesEx");
+ XInputVersion = "1.4";
+ } else if ((module = LoadLibraryA("xinput1_3.dll")) != NULL) {
+ XInputGetStateFuncPtr =
+ (WINAPI DWORD (*)(DWORD, XINPUT_STATE *))
+ GetProcAddress(module, "XInputGetState");
+ XInputVersion = "1.3";
+ } else if ((module = LoadLibraryA("xinput9_1_0.dll")) != NULL) {
+ XInputGetStateFuncPtr =
+ (WINAPI DWORD (*)(DWORD, XINPUT_STATE *))
+ GetProcAddress(module, "XInputGetState");
+ XInputVersion = "9.1.0";
+ }
+}
+
++ (OFArray OF_GENERIC(OFGameController *) *)controllers
+{
+ OFMutableArray *controllers = [OFMutableArray array];
+
+ if (XInputGetStateFuncPtr != NULL) {
+ void *pool = objc_autoreleasePoolPush();
+
+ for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
+ OFGameController *controller;
+
+ @try {
+ controller = [[[OFXInputGameController alloc]
+ of_initWithIndex: i] autorelease];
+ } @catch (OFInitializationFailedException *e) {
+ /* Controller does not exist. */
+ continue;
+ }
+
+ [controllers addObject: controller];
+ }
+
+ objc_autoreleasePoolPop(pool);
+ }
+
+ [controllers makeImmutable];
+
+ return controllers;
+}
+
+- (instancetype)init
+{
+ OF_INVALID_INIT_METHOD
+}
+
+- (instancetype)of_initWithIndex: (DWORD)index
+{
+ self = [super init];
+
+ @try {
+ XINPUT_STATE state = { 0 };
+
+ if (XInputGetStateFuncPtr(index, &state) ==
+ ERROR_DEVICE_NOT_CONNECTED)
+ @throw [OFInitializationFailedException exception];
+
+ _index = index;
+
+ if (XInputGetCapabilitiesExFuncPtr != NULL) {
+ struct XInputCapabilitiesEx capabilities;
+
+ if (XInputGetCapabilitiesExFuncPtr(1, _index,
+ XINPUT_FLAG_GAMEPAD, &capabilities) ==
+ ERROR_SUCCESS) {
+ _vendorID = [[OFNumber alloc]
+ initWithUnsignedShort:
+ capabilities.vendorID];
+ _productID = [[OFNumber alloc]
+ initWithUnsignedShort:
+ capabilities.productID];
+ }
+ }
+
+ _pressedButtons = [[OFMutableSet alloc] initWithCapacity: 16];
+
+ [self retrieveState];
+ } @catch (id e) {
+ [self release];
+ @throw e;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [_vendorID release];
+ [_productID release];
+ [_pressedButtons release];
+
+ [super dealloc];
+}
+
+- (void)retrieveState
+{
+ XINPUT_STATE state = { 0 };
+
+ if (XInputGetStateFuncPtr(_index, &state) != ERROR_SUCCESS)
+ @throw [OFReadFailedException exceptionWithObject: self
+ requestedLength: sizeof(state)
+ errNo: 0];
+
+ [_pressedButtons removeAllObjects];
+
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y)
+ [_pressedButtons addObject: OFGameControllerNorthButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A)
+ [_pressedButtons addObject: OFGameControllerSouthButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X)
+ [_pressedButtons addObject: OFGameControllerWestButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B)
+ [_pressedButtons addObject: OFGameControllerEastButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)
+ [_pressedButtons addObject: OFGameControllerLeftShoulderButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)
+ [_pressedButtons addObject:
+ OFGameControllerRightShoulderButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB)
+ [_pressedButtons addObject: OFGameControllerLeftStickButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB)
+ [_pressedButtons addObject: OFGameControllerRightStickButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)
+ [_pressedButtons addObject: OFGameControllerDPadUpButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
+ [_pressedButtons addObject: OFGameControllerDPadDownButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
+ [_pressedButtons addObject: OFGameControllerDPadLeftButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
+ [_pressedButtons addObject: OFGameControllerDPadRightButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START)
+ [_pressedButtons addObject: OFGameControllerStartButton];
+ if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK)
+ [_pressedButtons addObject: OFGameControllerSelectButton];
+
+ _leftTriggerPressure = (float)state.Gamepad.bLeftTrigger / 255;
+ _rightTriggerPressure = (float)state.Gamepad.bRightTrigger / 255;
+
+ if (_leftTriggerPressure > 0)
+ [_pressedButtons addObject: OFGameControllerLeftTriggerButton];
+ if (_rightTriggerPressure > 0)
+ [_pressedButtons addObject: OFGameControllerRightTriggerButton];
+
+ _leftAnalogStickPosition = OFMakePoint(
+ (float)state.Gamepad.sThumbLX /
+ (state.Gamepad.sThumbLX < 0 ? -INT16_MIN : INT16_MAX),
+ -(float)state.Gamepad.sThumbLY /
+ (state.Gamepad.sThumbLY < 0 ? -INT16_MIN : INT16_MAX));
+ _rightAnalogStickPosition = OFMakePoint(
+ (float)state.Gamepad.sThumbRX /
+ (state.Gamepad.sThumbRX < 0 ? -INT16_MIN : INT16_MAX),
+ -(float)state.Gamepad.sThumbRY /
+ (state.Gamepad.sThumbRY < 0 ? -INT16_MIN : INT16_MAX));
+}
+
+- (OFString *)name
+{
+ return [OFString stringWithFormat: @"XInput %s device", XInputVersion];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)buttons
+{
+ return [OFSet setWithObjects:
+ OFGameControllerNorthButton,
+ OFGameControllerSouthButton,
+ OFGameControllerWestButton,
+ OFGameControllerEastButton,
+ OFGameControllerLeftTriggerButton,
+ OFGameControllerRightTriggerButton,
+ OFGameControllerLeftShoulderButton,
+ OFGameControllerRightShoulderButton,
+ OFGameControllerLeftStickButton,
+ OFGameControllerRightStickButton,
+ OFGameControllerDPadLeftButton,
+ OFGameControllerDPadRightButton,
+ OFGameControllerDPadUpButton,
+ OFGameControllerDPadDownButton,
+ OFGameControllerStartButton,
+ OFGameControllerSelectButton, nil];
+}
+
+- (OFSet OF_GENERIC(OFGameControllerButton) *)pressedButtons
+{
+ return [[_pressedButtons copy] autorelease];
+}
+
+- (bool)hasLeftAnalogStick
+{
+ return true;
+}
+
+- (bool)hasRightAnalogStick
+{
+ return true;
+}
+
+- (float)pressureForButton: (OFGameControllerButton)button
+{
+ if (button == OFGameControllerLeftTriggerButton)
+ return _leftTriggerPressure;
+ if (button == OFGameControllerRightTriggerButton)
+ return _rightTriggerPressure;
+
+ return [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
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"
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
@@ -26,10 +26,12 @@
#import "OFSet.h"
#import "OFStdIOStream.h"
#import "OFValue.h"
#import "OTTestCase.h"
+
+#import "OFGameController.h"
#import "OTAssertionFailedException.h"
#import "OTTestSkippedException.h"
#ifdef OF_IOS
@@ -321,32 +323,29 @@
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:
+ OFGameControllerEastButton])
+ 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,102 @@
+/*
+ * 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 writeLine: controller.description];
+
+ [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}