Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -2353,10 +2353,13 @@ linux*) AS_IF([test x"$enable_files" != x"no"], [ AC_SUBST(USE_SRCS_EVDEV, '${SRCS_EVDEV}') ]) ;; +mingw*) + AC_SUBST(USE_SRCS_XINPUT, '${SRCS_XINPUT}') + ;; esac AS_IF([test x"$cross_compiling" = x"yes"], [ AC_SUBST(BIN_PREFIX, "${host_alias}-") Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -106,7 +106,8 @@ USE_SRCS_SUBPROCESSES = @USE_SRCS_SUBPROCESSES@ USE_SRCS_TAGGED_POINTERS = @USE_SRCS_TAGGED_POINTERS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ USE_SRCS_UNIX_SOCKETS = @USE_SRCS_UNIX_SOCKETS@ USE_SRCS_WINDOWS = @USE_SRCS_WINDOWS@ +USE_SRCS_XINPUT = @USE_SRCS_XINPUT@ WII_U_TESTS_LIBS = @WII_U_TESTS_LIBS@ WRAPPER = @WRAPPER@ Index: src/hid/Makefile ================================================================== --- src/hid/Makefile +++ src/hid/Makefile @@ -14,13 +14,16 @@ OHGameControllerButton.m \ OHGameControllerDirectionalPad.m \ OHGameControllerElement.m \ OHGameControllerProfile.m \ OHGamepad.m \ - ${USE_SRCS_EVDEV} + ${USE_SRCS_EVDEV} \ + ${USE_SRCS_XINPUT} SRCS_EVDEV = OHEvdevGameController.m \ OHEvdevGamepad.m +SRCS_XINPUT = OHXInputGameController.m \ + OHXInputGamepad.m INCLUDES := ${SRCS:.m=.h} \ ObjFWHID.h SRCS += OHGameControllerEmulatedAxis.m \ Index: src/hid/OHGameController.m ================================================================== --- src/hid/OHGameController.m +++ src/hid/OHGameController.m @@ -24,20 +24,25 @@ #import "OFNumber.h" #import "OFSet.h" #import "OHGamepad.h" #if defined(OF_LINUX) && defined(OF_HAVE_FILES) -# include "OHEvdevGameController.h" +# import "OHEvdevGameController.h" +#endif +#ifdef OF_WINDOWS +# import "OHXInputGameController.h" #endif @implementation OHGameController @dynamic name, rawProfile; + (OFArray OF_GENERIC(OHGameController *) *)controllers { #if defined(OF_LINUX) && defined(OF_HAVE_FILES) return [OHEvdevGameController controllers]; +#elif defined(OF_WINDOWS) + return [OHXInputGameController controllers]; #else return [OFArray array]; #endif } ADDED src/hid/OHXInputGameController.h Index: src/hid/OHXInputGameController.h ================================================================== --- /dev/null +++ src/hid/OHXInputGameController.h @@ -0,0 +1,39 @@ +/* + * 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 "OHGameController.h" + +#include + +OF_ASSUME_NONNULL_BEGIN + +@class OHXInputGamepad; + +@interface OHXInputGameController: OHGameController +{ + DWORD _index; + OFNumber *_Nullable _vendorID, *_Nullable _productID; + OHXInputGamepad *_gamepad; +} + +- (instancetype)oh_initWithIndex: (DWORD)index OF_METHOD_FAMILY(init); +@end + +OF_ASSUME_NONNULL_END + ADDED src/hid/OHXInputGameController.m Index: src/hid/OHXInputGameController.m ================================================================== --- /dev/null +++ src/hid/OHXInputGameController.m @@ -0,0 +1,242 @@ +/* + * 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 "OHXInputGameController.h" +#import "OFArray.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OHGameControllerAxis.h" +#import "OHGameControllerButton.h" +#import "OHGameControllerDirectionalPad.h" +#import "OHXInputGamepad.h" + +#import "OFInitializationFailedException.h" +#import "OFReadFailedException.h" + +#include + +#ifndef XINPUT_GAMEPAD_GUIDE +# define XINPUT_GAMEPAD_GUIDE 0x400 +#endif + +struct XInputCapabilitiesEx { + XINPUT_CAPABILITIES capabilities; + WORD vendorID; + WORD productID; + WORD versionNumber; + WORD unknown1; + DWORD unknown2; +}; + +static WINAPI DWORD (*XInputGetStateFuncPtr)(DWORD, XINPUT_STATE *); +static WINAPI DWORD (*XInputGetCapabilitiesExFuncPtr)(DWORD, DWORD, DWORD, + struct XInputCapabilitiesEx *); +static int XInputVersion; + +@implementation OHXInputGameController +@synthesize vendorID = _vendorID, productID = _productID, gamepad = _gamepad; + ++ (void)initialize +{ + HMODULE module; + + if (self != [OHXInputGameController class]) + return; + + if ((module = LoadLibraryA("xinput1_4.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, (LPCSTR)100); + XInputGetCapabilitiesExFuncPtr = (WINAPI DWORD (*)(DWORD, DWORD, + DWORD, struct XInputCapabilitiesEx *)) + GetProcAddress(module, (LPCSTR)108); + XInputVersion = 14; + } else if ((module = LoadLibrary("xinput1_3.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, (LPCSTR)100); + XInputVersion = 13; + } else if ((module = LoadLibrary("xinput9_1_0.dll")) != NULL) { + XInputGetStateFuncPtr = + (WINAPI DWORD (*)(DWORD, XINPUT_STATE *)) + GetProcAddress(module, "XInputGetState"); + XInputVersion = 910; + } +} + ++ (OFArray OF_GENERIC(OHGameController *) *)controllers +{ + OFMutableArray *controllers = [OFMutableArray array]; + + if (XInputGetStateFuncPtr != NULL) { + void *pool = objc_autoreleasePoolPush(); + + for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) { + OHGameController *controller; + + @try { + controller = [[[OHXInputGameController alloc] + oh_initWithIndex: i] autorelease]; + } @catch (OFInitializationFailedException *e) { + /* Controller does not exist. */ + continue; + } + + [controllers addObject: controller]; + } + + objc_autoreleasePoolPop(pool); + } + + [controllers makeImmutable]; + + return controllers; +} + +- (instancetype)oh_initWithIndex: (DWORD)index +{ + self = [super init]; + + @try { + XINPUT_STATE state = { 0 }; + + if (XInputGetStateFuncPtr(index, &state) == + ERROR_DEVICE_NOT_CONNECTED) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + _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]; + } + } + + _gamepad = [[OHXInputGamepad alloc] init]; + + [self retrieveState]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_vendorID release]; + [_productID release]; + [_gamepad release]; + + [super dealloc]; +} + +- (void)retrieveState +{ + XINPUT_STATE state = { 0 }; + + if (XInputGetStateFuncPtr(_index, &state) != ERROR_SUCCESS) + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: sizeof(state) + errNo: 0]; + + _gamepad.northButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); + _gamepad.southButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A); + _gamepad.westButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X); + _gamepad.eastButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B); + _gamepad.leftShoulderButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + _gamepad.rightShoulderButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + _gamepad.leftThumbstickButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + _gamepad.rightThumbstickButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + _gamepad.menuButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_START); + _gamepad.optionsButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); + if (XInputVersion != 910) + _gamepad.homeButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE); + + _gamepad.leftTriggerButton.value = + (float)state.Gamepad.bLeftTrigger / 255; + _gamepad.rightTriggerButton.value = + (float)state.Gamepad.bRightTrigger / 255; + + _gamepad.leftThumbstick.xAxis.value = + (float)state.Gamepad.sThumbLX / + (state.Gamepad.sThumbLX < 0 ? -INT16_MIN : INT16_MAX); + _gamepad.leftThumbstick.yAxis.value = + -(float)state.Gamepad.sThumbLY / + (state.Gamepad.sThumbLY < 0 ? -INT16_MIN : INT16_MAX); + _gamepad.rightThumbstick.xAxis.value = + (float)state.Gamepad.sThumbRX / + (state.Gamepad.sThumbRX < 0 ? -INT16_MIN : INT16_MAX); + _gamepad.rightThumbstick.yAxis.value = + -(float)state.Gamepad.sThumbRY / + (state.Gamepad.sThumbRY < 0 ? -INT16_MIN : INT16_MAX); + + _gamepad.directionalPad.upButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP); + _gamepad.directionalPad.downButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN); + _gamepad.directionalPad.leftButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT); + _gamepad.directionalPad.rightButton.value = + !!(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT); +} + +- (OFString *)name +{ + switch (XInputVersion) { + case 14: + return @"XInput 1.4 device"; + case 13: + return @"XInput 1.3 device"; + case 910: + return @"XInput 9.1.0 device"; + } + + return nil; +} + +- (OHGameControllerProfile *)rawProfile +{ + return _gamepad; +} +@end ADDED src/hid/OHXInputGamepad.h Index: src/hid/OHXInputGamepad.h ================================================================== --- /dev/null +++ src/hid/OHXInputGamepad.h @@ -0,0 +1,27 @@ +/* + * 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 "OHGamepad.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OHXInputGamepad: OHGamepad +@end + +OF_ASSUME_NONNULL_END ADDED src/hid/OHXInputGamepad.m Index: src/hid/OHXInputGamepad.m ================================================================== --- /dev/null +++ src/hid/OHXInputGamepad.m @@ -0,0 +1,198 @@ +/* + * 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 "OHXInputGamepad.h" +#import "OFDictionary.h" +#import "OHGameControllerAxis.h" +#import "OHGameControllerButton.h" +#import "OHGameControllerDirectionalPad.h" + +static OFString *const buttonNames[] = { + @"A", @"B", @"X", @"Y", @"LB", @"RB", @"LT", @"RT", @"LSB", @"RSB", + @"Start", @"Back", @"Guide" +}; +static const size_t numButtons = sizeof(buttonNames) / sizeof(*buttonNames); + +@implementation OHXInputGamepad +- (instancetype)init +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + OFMutableDictionary *buttons = + [OFMutableDictionary dictionaryWithCapacity: numButtons]; + OFMutableDictionary *directionalPads; + OHGameControllerAxis *xAxis, *yAxis; + OHGameControllerDirectionalPad *directionalPad; + OHGameControllerButton *upButton, *downButton; + OHGameControllerButton *leftButton, *rightButton; + + for (size_t i = 0; i < numButtons; i++) { + OHGameControllerButton *button = + [[OHGameControllerButton alloc] + initWithName: buttonNames[i]]; + + @try { + [buttons setObject: button + forKey: buttonNames[i]]; + } @finally { + [button release]; + } + } + [buttons makeImmutable]; + _buttons = [buttons retain]; + + _axes = [[OFDictionary alloc] init]; + + directionalPads = + [OFMutableDictionary dictionaryWithCapacity: 3]; + + xAxis = [[[OHGameControllerAxis alloc] + initWithName: @"X"] autorelease]; + yAxis = [[[OHGameControllerAxis alloc] + initWithName: @"Y"] autorelease]; + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"Left Thumbstick" + xAxis: xAxis + yAxis: yAxis] autorelease]; + [directionalPads setObject: directionalPad + forKey: @"Left Thumbstick"]; + + xAxis = [[[OHGameControllerAxis alloc] + initWithName: @"RX"] autorelease]; + yAxis = [[[OHGameControllerAxis alloc] + initWithName: @"RY"] autorelease]; + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"Right Thumbstick" + xAxis: xAxis + yAxis: yAxis] autorelease]; + [directionalPads setObject: directionalPad + forKey: @"Right Thumbstick"]; + + upButton = [[[OHGameControllerButton alloc] + initWithName: @"D-Pad Up"] autorelease]; + downButton = [[[OHGameControllerButton alloc] + initWithName: @"D-Pad Down"] autorelease]; + leftButton = [[[OHGameControllerButton alloc] + initWithName: @"D-Pad Left"] autorelease]; + rightButton = [[[OHGameControllerButton alloc] + initWithName: @"D-Pad Right"] autorelease]; + directionalPad = [[[OHGameControllerDirectionalPad alloc] + initWithName: @"D-Pad" + upButton: upButton + downButton: downButton + leftButton: leftButton + rightButton: rightButton] autorelease]; + [directionalPads setObject: directionalPad forKey: @"D-Pad"]; + + [directionalPads makeImmutable]; + _directionalPads = [directionalPads retain]; + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (OHGameControllerButton *)northButton +{ + return [_buttons objectForKey: @"Y"]; +} + +- (OHGameControllerButton *)southButton +{ + return [_buttons objectForKey: @"A"]; +} + +- (OHGameControllerButton *)westButton +{ + return [_buttons objectForKey: @"X"]; +} + +- (OHGameControllerButton *)eastButton +{ + return [_buttons objectForKey: @"B"]; +} + +- (OHGameControllerButton *)leftShoulderButton +{ + return [_buttons objectForKey: @"LB"]; +} + +- (OHGameControllerButton *)rightShoulderButton +{ + return [_buttons objectForKey: @"RB"]; +} + +- (OHGameControllerButton *)leftTriggerButton +{ + return [_buttons objectForKey: @"LT"]; +} + +- (OHGameControllerButton *)rightTriggerButton +{ + return [_buttons objectForKey: @"RT"]; +} + +- (OHGameControllerButton *)leftThumbstickButton +{ + return [_buttons objectForKey: @"LSB"]; +} + +- (OHGameControllerButton *)rightThumbstickButton +{ + return [_buttons objectForKey: @"RSB"]; +} + +- (OHGameControllerButton *)menuButton +{ + return [_buttons objectForKey: @"Start"]; +} + +- (OHGameControllerButton *)optionsButton +{ + return [_buttons objectForKey: @"Back"]; +} + +- (OHGameControllerButton *)homeButton +{ + return [_buttons objectForKey: @"Guide"]; +} + +- (OHGameControllerDirectionalPad *)leftThumbstick +{ + return [_directionalPads objectForKey: @"Left Thumbstick"]; +} + +- (OHGameControllerDirectionalPad *)rightThumbstick +{ + return [_directionalPads objectForKey: @"Right Thumbstick"]; +} + +- (OHGameControllerDirectionalPad *)directionalPad +{ + return [_directionalPads objectForKey: @"D-Pad"]; +} +@end