ObjFW  Check-in [5f0cc05894]

Overview
Comment:ObjFWHID: Restore XInput support
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 5f0cc058949d62a490abadf3564af07c4cb9506fb4a95187781b8d2e2715c58e
User & Date: js on 2024-06-08 19:18:45
Other Links: manifest | tags
Context
2024-06-08
19:26
OHGamepad: Add missing documentation check-in: 21d148d1f9 user: js tags: trunk
19:18
ObjFWHID: Restore XInput support check-in: 5f0cc05894 user: js tags: trunk
2024-06-04
23:51
Add OHGamepad check-in: c45b04b1f8 user: js tags: trunk
Changes

Modified configure.ac from [6db61d455a] to [f83f5209e0].

2351
2352
2353
2354
2355
2356
2357



2358
2359
2360
2361
2362
2363
2364

case "$host_os" in
linux*)
	AS_IF([test x"$enable_files" != x"no"], [
		AC_SUBST(USE_SRCS_EVDEV, '${SRCS_EVDEV}')
	])
	;;



esac

AS_IF([test x"$cross_compiling" = x"yes"], [
	AC_SUBST(BIN_PREFIX, "${host_alias}-")

	case "$host" in
	i?86-*-mingw*)







>
>
>







2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367

case "$host_os" in
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}-")

	case "$host" in
	i?86-*-mingw*)

Modified extra.mk.in from [6abfed9392] to [dae9e19b6f].

104
105
106
107
108
109
110

111
112
USE_SRCS_SCTP = @USE_SRCS_SCTP@
USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@
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@

WII_U_TESTS_LIBS = @WII_U_TESTS_LIBS@
WRAPPER = @WRAPPER@







>


104
105
106
107
108
109
110
111
112
113
USE_SRCS_SCTP = @USE_SRCS_SCTP@
USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@
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@

Modified src/hid/Makefile from [f964e9ff82] to [df135a085c].

12
13
14
15
16
17
18
19

20
21


22
23
24
25
26
27
28
SRCS = OHGameController.m		\
       OHGameControllerAxis.m		\
       OHGameControllerButton.m		\
       OHGameControllerDirectionalPad.m	\
       OHGameControllerElement.m	\
       OHGameControllerProfile.m	\
       OHGamepad.m			\
       ${USE_SRCS_EVDEV}

SRCS_EVDEV = OHEvdevGameController.m	\
	     OHEvdevGamepad.m



INCLUDES := ${SRCS:.m=.h}	\
	    ObjFWHID.h

SRCS += OHGameControllerEmulatedAxis.m		\
	OHGameControllerEmulatedButton.m	\
	OHGameControllerEmulatedTriggerButton.m







|
>


>
>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
SRCS = OHGameController.m		\
       OHGameControllerAxis.m		\
       OHGameControllerButton.m		\
       OHGameControllerDirectionalPad.m	\
       OHGameControllerElement.m	\
       OHGameControllerProfile.m	\
       OHGamepad.m			\
       ${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		\
	OHGameControllerEmulatedButton.m	\
	OHGameControllerEmulatedTriggerButton.m

Modified src/hid/OHGameController.m from [48b11dd8fc] to [501a6c45ed].

22
23
24
25
26
27
28
29



30
31
32
33
34
35
36
37
38


39
40
41
42
43
44
45
#import "OHGameController.h"
#import "OFArray.h"
#import "OFNumber.h"
#import "OFSet.h"
#import "OHGamepad.h"

#if defined(OF_LINUX) && defined(OF_HAVE_FILES)
# include "OHEvdevGameController.h"



#endif

@implementation OHGameController
@dynamic name, rawProfile;

+ (OFArray OF_GENERIC(OHGameController *) *)controllers
{
#if defined(OF_LINUX) && defined(OF_HAVE_FILES)
	return [OHEvdevGameController controllers];


#else
	return [OFArray array];
#endif
}

- (instancetype)init
{







|
>
>
>









>
>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#import "OHGameController.h"
#import "OFArray.h"
#import "OFNumber.h"
#import "OFSet.h"
#import "OHGamepad.h"

#if defined(OF_LINUX) && defined(OF_HAVE_FILES)
# 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
}

- (instancetype)init
{

Added src/hid/OHXInputGameController.h version [9cb565dbba].















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

#import "OHGameController.h"

#include <windows.h>

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 version [44a8b230ca].





































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

#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 <xinput.h>

#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 version [2938c0d988].























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

#import "OHGamepad.h"

OF_ASSUME_NONNULL_BEGIN

@interface OHXInputGamepad: OHGamepad
@end

OF_ASSUME_NONNULL_END

Added src/hid/OHXInputGamepad.m version [4438c4bc5d].













































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * <https://www.gnu.org/licenses/>.
 */

#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