The OpenD Programming Language

1 /++
2 
3 	Provides a polling-based API to use gamepads/joysticks on Linux and Windows.
4 
5 	Pass `-version=ps1_style` or `-version=xbox_style` to pick your API style - the constants will use the names of the buttons on those controllers and attempt to emulate the other. ps1_style is compatible with more hardware and thus the default. XBox controllers work with either, though.
6 
7 	The docs for this file are quite weak, I suggest you view source of [arsd.game] for an example of how it might be used.
8 
9 	FIXME: on Linux, certain controller brands will not be recognized and you need to set the mappings yourself, e.g., `version(linux) joystickMapping[0] = &xbox360Mapping;`. I will formalize this into a proper api later.
10 +/
11 
12 /+
13 	XBox360 DDR pad layout:
14 
15 	Back         Start
16 	 B      Up      A
17 	Left         Right
18 	 Y     Down     X
19 
20 	 XBox360 Butons:
21 
22 	  Y
23 	 X B
24 	  A
25 +/
26 
27 /*
28 	FIXME: a simple function to integrate with sdpy event loop. templated function
29 
30 	HIGH LEVEL NOTES
31 
32 	This will offer a pollable state of two styles of controller: a PS1 or an XBox 360.
33 
34 
35 	Actually, maybe I'll combine the two controller types. Make L2 and R2 just digital aliases
36 	for the triggers, which are analog aliases for it.
37 
38 	Then have a virtual left stick which has the dpad aliases, while keeping the other two independent
39 	(physical dpad and physical left stick).
40 
41 	Everything else should basically just work. We'll simply be left with naming and I can do them with
42 	aliases too.
43 
44 
45 	I do NOT bother with pressure sensitive other buttons, though Xbox original and PS2 had them, they
46 	have been removed from the newer models. It makes things simpler anyway since we can check "was just
47 	pressed" instead of all deltas.
48 
49 
50 	The PS1 controller style works for a lot of games:
51 		* The D-pad is an alias for the left stick. Analog input still works too.
52 		* L2 and R2 are given as buttons
53 		* The keyboard works as buttons
54 		* The mouse is an alias for the right stick
55 		* Buttons are given as labeled on a playstation controller
56 
57 	The XBox controller style works if you need full, modern features:
58 		* The left stick and D-pad works independently of one another
59 			the d pad works as additional buttons.
60 		* The triggers work as independent analog inputs
61 			note that the WinMM driver doesn't support full independence
62 			since it is sent as a z-axis. Linux and modern Windows does though.
63 		* Buttons are labeled as they are on the XBox controller
64 		* The rumble motors are available, if the underlying driver supports it and noop if not.
65 		* Audio I/O is available, if the underlying driver supports it. NOT IMPLEMENTED.
66 
67 	You chose which one you want at compile time with a -version=xbox_style or -version=ps1_style switch.
68 	The default is ps1_style which works with xbox controllers too, it just simplifies them.
69 
70 	TODO:
71 		handling keyboard+mouse input as joystick aliases
72 		remapping support
73 		network transparent joysticks for at least the basic stuff.
74 
75 	=================================
76 
77 	LOW LEVEL NOTES
78 
79 	On Linux, I'll just use /dev/input/js*. It is easy and works with everything I care about. It can fire
80 	events to arsd.eventloop and also maintains state internally for polling. You do have to let it get
81 	events though to handle that input - either doing your own select (etc.) on the js file descriptor,
82 	or running the event loop (which is what I recommend).
83 
84 	On Windows, I'll support the mmsystem messages as far as I can, and XInput for more capabilities
85 	of the XBox 360 controller. (The mmsystem should support my old PS1 controller and xbox is the
86 	other one I have. I have PS3 controllers too which would be nice but since they require additional
87 	drivers, meh.)
88 
89 	linux notes:
90 		all basic input is available, no audio (I think), no force feedback (I think)
91 
92 	winmm notes:
93 		the xbox 360 controller basically works and sends events to the window for the buttons,
94 		left stick, and triggers. It doesn't send events for the right stick or dpad, but these
95 		are available through joyGetPosEx (the dpad is the POV hat and the right stick is
96 		the other axes).
97 
98 		The triggers are considered a z-axis with the left one going negative and right going positive.
99 
100 	windows xinput notes:
101 		all xbox 360 controller features are available via a polling api.
102 
103 		it doesn't seem to support events. That's OK for games generally though, because we just
104 		want to check state on each loop.
105 
106 		For non-games however, using the traditional message loop is probably easier.
107 
108 		XInput is only supported on newer operating systems (Vista I think),
109 		so I'm going to dynamically load it all and fallback on the old one if
110 		it fails.
111 
112 
113 
114 	Other fancy joysticks work low level on linux at least but the high level api reduces them to boredom but like
115 	hey the events are still there and it still basically works, you'd just have to give a custom mapping.
116 */
117 module arsd.joystick;
118 
119 // --------------------------------
120 // High level interface
121 // --------------------------------
122 
123 version(xbox_style) {
124 	version(ps1_style)
125 		static assert(0, "Pass only one xbox_style OR ps1_style");
126 } else
127 	version=ps1_style; // default is PS1 style as it is a lower common denominator
128 
129 version(xbox_style) {
130 	alias Axis = XBox360Axes;
131 	alias Button = XBox360Buttons;
132 } else version(ps1_style) {
133 	alias Axis = PS1AnalogAxes;
134 	alias Button = PS1Buttons;
135 }
136 
137 
138 version(Windows) {
139 	WindowsXInput wxi;
140 }
141 
142 version(OSX) {
143 	struct JoystickState {}
144 }
145 
146 JoystickState[4] joystickState;
147 
148 version(linux) {
149 	int[4] joystickFds = -1;
150 
151 
152 	// On Linux, we have to track state ourselves since we only get events from the OS
153 	struct JoystickState {
154 		short[8] axes;
155 		ubyte[16] buttons;
156 	}
157 
158 	const(JoystickMapping)*[4] joystickMapping;
159 
160 	struct JoystickMapping {
161 		// maps virtual buttons to real buttons, etc.
162 		int[__traits(allMembers, Axis).length] axisOffsets = -1;
163 		int[__traits(allMembers, Button).length] buttonOffsets = -1;
164 	}
165 
166 	/// If you have a real xbox 360 controller, use this mapping
167 	version(xbox_style) // xbox style maps directly to an xbox controller (of course)
168 	static immutable xbox360Mapping = JoystickMapping(
169 		[0,1,2,3,4,5,6,7],
170 		[0,1,2,3,4,5,6,7,8,9,10, 11,12,13,14]
171 	);
172 	else version(ps1_style)
173 	static immutable xbox360Mapping = JoystickMapping(
174 		// PS1AnalogAxes index to XBox360Axes values
175 		[XBox360Axes.horizontalLeftStick,
176 		XBox360Axes.verticalLeftStick,
177 		XBox360Axes.verticalRightStick,
178 		XBox360Axes.horizontalRightStick,
179 		XBox360Axes.horizontalDpad,
180 		XBox360Axes.verticalDpad],
181 		// PS1Buttons index to XBox360Buttons values
182 		[XBox360Buttons.y, XBox360Buttons.b, XBox360Buttons.a, XBox360Buttons.x,
183 			cast(XBox360Buttons) -1, cast(XBox360Buttons) -1, // L2 and R2 don't map easily
184 			XBox360Buttons.lb, XBox360Buttons.rb,
185 			XBox360Buttons.back, XBox360Buttons.start,
186 			XBox360Buttons.leftStick, XBox360Buttons.rightStick]
187 	);
188 
189 
190 	/// For a real ps1 controller
191 	version(ps1_style)
192 	static immutable ps1Mapping = JoystickMapping(
193 		[0,1,2,3,4,5],
194 		[0,1,2,3,4,5,6,7,8,9,10,11]
195 
196 	);
197 	else version(xbox_style)
198 	static immutable ps1Mapping = JoystickMapping(
199 		// FIXME... if we're going to support this at all
200 		// I think if I were to write a program using the xbox style,
201 		// I'd just use my xbox controller.
202 	);
203 
204 	/// For Linux only, reads the latest joystick events into the change buffer, if available.
205 	/// It is non-blocking
206 	void readJoystickEvents(int fd) {
207 		js_event event;
208 
209 		while(true) {
210 			auto r = read(fd, &event, event.sizeof);
211 			if(r == -1) {
212 				import core.stdc.errno;
213 				if(errno == EAGAIN || errno == EWOULDBLOCK)
214 					break;
215 				else assert(0); // , to!string(fd) ~ " " ~ to!string(errno));
216 			}
217 			if(r != event.sizeof)
218 				throw new Exception("Read something weird off the joystick event fd");
219 				//import std.stdio; writeln(event);
220 
221 			ptrdiff_t player = -1;
222 			foreach(i, f; joystickFds)
223 				if(f == fd) {
224 					player = i;
225 					break;
226 				}
227 
228 			assert(player >= 0 && player < joystickState.length);
229 
230 			if(event.type & JS_EVENT_AXIS) {
231 				joystickState[player].axes[event.number] = event.value;
232 
233 				if(event.type & JS_EVENT_INIT) {
234 					if(event.number == 5) {
235 						// After being initialized, if axes[6] == 32767, it seems to be my PS1 controller
236 						// If axes[5] is -32767, it might be an Xbox controller.
237 
238 						if(event.value == -32767 && joystickMapping[player] is null) {
239 							joystickMapping[player] = &xbox360Mapping;
240 						}
241 					} else if(event.number == 6) {
242 						if((event.value == 32767 || event.value == -32767) && joystickMapping[player] is null) {
243 							joystickMapping[player] = &ps1Mapping;
244 						}
245 					}
246 				}
247 			}
248 			if(event.type & JS_EVENT_BUTTON) {
249 				joystickState[player].buttons[event.number] = event.value ? 255 : 0;
250 				//writeln(player, " ", event.number, " ", event.value, " ", joystickState[player].buttons[event.number]);//, " != ", event.value ? 255 : 0);
251 			}
252 		}
253 	}
254 }
255 
256 version(Windows) {
257 	extern(Windows)
258 	DWORD function(DWORD, XINPUT_STATE*) getJoystickOSState;
259 
260 	extern(Windows)
261 	DWORD winMMFallback(DWORD id, XINPUT_STATE* state) {
262 		JOYINFOEX info;
263 		auto result = joyGetPosEx(id, &info);
264 		if(result == 0) {
265 			// FIXME
266 
267 		}
268 		return result;
269 	}
270 
271 	alias JoystickState = XINPUT_STATE;
272 }
273 
274 /// Returns the number of players actually connected
275 ///
276 /// The controller ID
277 int enableJoystickInput(
278 	int player1ControllerId = 0,
279 	int player2ControllerId = 1,
280 	int player3ControllerId = 2,
281 	int player4ControllerId = 3)
282 {
283 	version(linux) {
284 		bool preparePlayer(int player, int id) {
285 			if(id < 0)
286 				return false;
287 
288 			assert(player >= 0 && player < joystickFds.length);
289 			assert(id < 10);
290 			assert(id >= 0);
291 			char[] filename = "/dev/input/js0\0".dup;
292 			filename[$-2] = cast(char) (id + '0');
293 
294 			int fd = open(filename.ptr, O_RDONLY);
295 			if(fd > 0) {
296 				joystickFds[player] = fd;
297 
298 				version(with_eventloop) {
299 					import arsd.eventloop;
300 					makeNonBlocking(fd);
301 					addFileEventListeners(fd, &readJoystickEvents, null, null);
302 				} else {
303 					// for polling, we will set nonblocking mode anyway,
304 					// the readJoystickEvents function will handle this fine
305 					// so we can call it when needed even on like a game timer.
306 					auto flags = fcntl(fd, F_GETFL, 0);
307 					if(flags == -1)
308 						throw new Exception("fcntl get");
309 					flags |= O_NONBLOCK;
310 					auto s = fcntl(fd, F_SETFL, flags);
311 					if(s == -1)
312 						throw new Exception("fcntl set");
313 				}
314 
315 				return true;
316 			}
317 			return false;
318 		}
319 
320 		if(!preparePlayer(0, player1ControllerId) ? 1 : 0)
321 			return 0;
322 		if(!preparePlayer(1, player2ControllerId) ? 1 : 0)
323 			return 1;
324 		if(!preparePlayer(2, player3ControllerId) ? 1 : 0)
325 			return 2;
326 		if(!preparePlayer(3, player4ControllerId) ? 1 : 0)
327 			return 3;
328 		return 4; // all players successfully initialized
329 	} else version(Windows) {
330 		if(wxi.loadDll()) {
331 			getJoystickOSState = wxi.XInputGetState;
332 		} else {
333 			// WinMM fallback
334 			getJoystickOSState = &winMMFallback;
335 		}
336 
337 		assert(getJoystickOSState !is null);
338 
339 		if(getJoystickOSState(player1ControllerId, &(joystickState[0])))
340 			return 0;
341 		if(getJoystickOSState(player2ControllerId, &(joystickState[1])))
342 			return 1;
343 		if(getJoystickOSState(player3ControllerId, &(joystickState[2])))
344 			return 2;
345 		if(getJoystickOSState(player4ControllerId, &(joystickState[3])))
346 			return 3;
347 
348 		return 4;
349 	} else static assert(0, "Unsupported OS");
350 
351 	// return 0;
352 }
353 
354 ///
355 void closeJoysticks() {
356 	version(linux) {
357 		foreach(ref fd; joystickFds) {
358 			if(fd > 0) {
359 				version(with_eventloop) {
360 					import arsd.eventloop;
361 					removeFileEventListeners(fd);
362 				}
363 				close(fd);
364 			}
365 			fd = -1;
366 		}
367 	} else version(Windows) {
368 		getJoystickOSState = null;
369 		wxi.unloadDll();
370 	} else static assert(0);
371 }
372 
373 ///
374 struct JoystickUpdate {
375 	///
376 	int player;
377 
378 	JoystickState old;
379 	JoystickState current;
380 
381 	/// changes from last update
382 	bool buttonWasJustPressed(Button button) {
383 		return buttonIsPressed(button) && !oldButtonIsPressed(button);
384 	}
385 
386 	/// ditto
387 	bool buttonWasJustReleased(Button button) {
388 		return !buttonIsPressed(button) && oldButtonIsPressed(button);
389 	}
390 
391 	/// this is normalized down to a 16 step change
392 	/// and ignores a dead zone near the middle
393 	short axisChange(Axis axis) {
394 		return cast(short) (axisPosition(axis) - oldAxisPosition(axis));
395 	}
396 
397 	/// current state
398 	bool buttonIsPressed(Button button) {
399 		return buttonIsPressedHelper(button, &current);
400 	}
401 
402 	/// Note: UP is negative!
403 	/// Value will actually be -16 to 16 ish.
404 	short axisPosition(Axis axis, short digitalFallbackValue = short.max) {
405 		return axisPositionHelper(axis, &current, digitalFallbackValue);
406 	}
407 
408 	/* private */
409 
410 	// old state
411 	bool oldButtonIsPressed(Button button) {
412 		return buttonIsPressedHelper(button, &old);
413 	}
414 
415 	short oldAxisPosition(Axis axis, short digitalFallbackValue = short.max) {
416 		return axisPositionHelper(axis, &old, digitalFallbackValue);
417 	}
418 
419 	short axisPositionHelper(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
420 		version(ps1_style) {
421 			// on PS1, the d-pad and left stick are synonyms for each other
422 			// the dpad takes precedence, if it is pressed
423 
424 			if(axis == PS1AnalogAxes.horizontalDpad || axis == PS1AnalogAxes.horizontalLeftStick) {
425 				auto it = axisPositionHelperRaw(PS1AnalogAxes.horizontalDpad, what, digitalFallbackValue);
426 				if(!it)
427 					it = axisPositionHelperRaw(PS1AnalogAxes.horizontalLeftStick, what, digitalFallbackValue);
428 				version(linux)
429 				if(!it)
430 					it = current.buttons[XBox360Buttons.dpadLeft] ? cast(short)-cast(int)digitalFallbackValue : current.buttons[XBox360Buttons.dpadRight] ? digitalFallbackValue : 0;
431 				return it;
432 			}
433 
434 			if(axis == PS1AnalogAxes.verticalDpad || axis == PS1AnalogAxes.verticalLeftStick) {
435 				auto it = axisPositionHelperRaw(PS1AnalogAxes.verticalDpad, what, digitalFallbackValue);
436 				if(!it)
437 					it = axisPositionHelperRaw(PS1AnalogAxes.verticalLeftStick, what, digitalFallbackValue);
438 				version(linux)
439 				if(!it)
440 					it = current.buttons[XBox360Buttons.dpadUp] ? cast(short)-cast(int)digitalFallbackValue : current.buttons[XBox360Buttons.dpadDown] ? digitalFallbackValue : 0;
441 				return it;
442 			}
443 		}
444 
445 		return axisPositionHelperRaw(axis, what, digitalFallbackValue);
446 	}
447 
448 	static short normalizeAxis(short value) {
449 	/+
450 		auto v = normalizeAxisHack(value);
451 		import std.stdio;
452 		writeln(value, " :: ", v);
453 		return v;
454 	}
455 	static short normalizeAxisHack(short value) {
456 	+/
457 		if(value > -1600 && value < 1600)
458 			return 0; // the deadzone gives too much useless junk
459 		return cast(short) (value >>> 11);
460 	}
461 
462 	bool buttonIsPressedHelper(Button button, JoystickState* what) {
463 		version(linux) {
464 			int mapping = -1;
465 			if(auto ptr = joystickMapping[player])
466 				mapping = ptr.buttonOffsets[button];
467 			if(mapping != -1)
468 				return what.buttons[mapping] ? true : false;
469 			// otherwise what do we do?
470 			// FIXME
471 			return false; // the button isn't mapped, figure it isn't there and thus can't be pushed
472 		} else version(Windows) {
473 			// on Windows, I'm always assuming it is an XBox 360 controller
474 			// because that's what I have and the OS supports it so well
475 			version(xbox_style)
476 			final switch(button) {
477 				case XBox360Buttons.a: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
478 				case XBox360Buttons.b: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
479 				case XBox360Buttons.x: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
480 				case XBox360Buttons.y: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
481 
482 				case XBox360Buttons.lb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
483 				case XBox360Buttons.rb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
484 
485 				case XBox360Buttons.back: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
486 				case XBox360Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
487 
488 				case XBox360Buttons.leftStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
489 				case XBox360Buttons.rightStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
490 
491 				case XBox360Buttons.dpadLeft: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? true : false;
492 				case XBox360Buttons.dpadRight: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? true : false;
493 				case XBox360Buttons.dpadUp: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? true : false;
494 				case XBox360Buttons.dpadDown: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? true : false;
495 
496 				case XBox360Buttons.xboxLogo: return false;
497 			}
498 			else version(ps1_style)
499 			final switch(button) {
500 				case PS1Buttons.triangle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
501 				case PS1Buttons.square: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
502 				case PS1Buttons.cross: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
503 				case PS1Buttons.circle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
504 
505 				case PS1Buttons.select: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
506 				case PS1Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
507 
508 				case PS1Buttons.l1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
509 				case PS1Buttons.r1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
510 
511 				case PS1Buttons.l2: return (what.Gamepad.bLeftTrigger > 100);
512 				case PS1Buttons.r2: return (what.Gamepad.bRightTrigger > 100);
513 
514 				case PS1Buttons.l3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
515 				case PS1Buttons.r3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
516 			}
517 		}
518 	}
519 
520 	short axisPositionHelperRaw(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
521 		version(linux) {
522 			int mapping = -1;
523 			if(auto ptr = joystickMapping[player])
524 				mapping = ptr.axisOffsets[axis];
525 			if(mapping != -1)
526 				return normalizeAxis(what.axes[mapping]);
527 			return 0; // no such axis apparently, let the cooked one do something if it can
528 		} else version(Windows) {
529 			// on Windows, assuming it is an XBox 360 controller
530 			version(xbox_style)
531 			final switch(axis) {
532 				case XBox360Axes.horizontalLeftStick:
533 					return normalizeAxis(what.Gamepad.sThumbLX);
534 				case XBox360Axes.verticalLeftStick:
535 					return normalizeAxis(what.Gamepad.sThumbLY);
536 				case XBox360Axes.horizontalRightStick:
537 					return normalizeAxis(what.Gamepad.sThumbRX);
538 				case XBox360Axes.verticalRightStick:
539 					return normalizeAxis(what.Gamepad.sThumbRY);
540 				case XBox360Axes.verticalDpad:
541 					return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? cast(short) -digitalFallbackValue :
542 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? cast(short) digitalFallbackValue :
543 					       0;
544 				case XBox360Axes.horizontalDpad:
545 					return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? cast(short) -digitalFallbackValue :
546 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? cast(short) digitalFallbackValue :
547 					       0;
548 				case XBox360Axes.lt:
549 					return normalizeTrigger(what.Gamepad.bLeftTrigger);
550 				case XBox360Axes.rt:
551 					return normalizeTrigger(what.Gamepad.bRightTrigger);
552 			}
553 			else version(ps1_style)
554 			final switch(axis) {
555 				case PS1AnalogAxes.horizontalDpad:
556 				case PS1AnalogAxes.horizontalLeftStick:
557 					short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? cast(short)-cast(int)digitalFallbackValue :
558 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? digitalFallbackValue :
559 					       0;
560 					if(got == 0)
561 						got = what.Gamepad.sThumbLX;
562 
563 					return normalizeAxis(got);
564 				case PS1AnalogAxes.verticalDpad:
565 				case PS1AnalogAxes.verticalLeftStick:
566 					short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? digitalFallbackValue :
567 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? cast(short)-cast(int)digitalFallbackValue :
568 						what.Gamepad.sThumbLY;
569 
570 					if(got == short.min)
571 						got++; // to avoid overflow on the axis inversion below
572 
573 					return normalizeAxis(cast(short)-cast(int)got);
574 				case PS1AnalogAxes.horizontalRightStick:
575 					return normalizeAxis(what.Gamepad.sThumbRX);
576 				case PS1AnalogAxes.verticalRightStick:
577 					return normalizeAxis(what.Gamepad.sThumbRY);
578 			}
579 		}
580 	}
581 
582 	version(Windows)
583 		short normalizeTrigger(BYTE b) {
584 			if(b < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
585 				return 0;
586 			return cast(short)((b << 8)|0xff);
587 		}
588 }
589 
590 ///
591 JoystickUpdate getJoystickUpdate(int player) {
592 	static JoystickState[4] previous;
593 
594 	version(Windows) {
595 		assert(getJoystickOSState !is null);
596 		if(getJoystickOSState(player, &(joystickState[player])))
597 			return JoystickUpdate();
598 			//throw new Exception("wtf");
599 	}
600 
601 	auto it = JoystickUpdate(player, previous[player], joystickState[player]);
602 
603 	previous[player] = joystickState[player];
604 
605 	return it;
606 }
607 
608 // --------------------------------
609 // Low level interface
610 // --------------------------------
611 
612 version(Windows) {
613 
614 	import core.sys.windows.windows;
615 
616 	alias MMRESULT = UINT;
617 
618 	struct JOYINFOEX {
619 		DWORD dwSize;
620 		DWORD dwFlags;
621 		DWORD dwXpos;
622 		DWORD dwYpos;
623 		DWORD dwZpos;
624 		DWORD dwRpos;
625 		DWORD dwUpos;
626 		DWORD dwVpos;
627 		DWORD dwButtons;
628 		DWORD dwButtonNumber;
629 		DWORD dwPOV;
630 		DWORD dwReserved1;
631 		DWORD dwReserved2;
632 	}
633 
634 	enum  : DWORD {
635 		JOY_POVCENTERED = -1,
636 		JOY_POVFORWARD  = 0,
637 		JOY_POVBACKWARD = 18000,
638 		JOY_POVLEFT     = 27000,
639 		JOY_POVRIGHT    = 9000
640 	}
641 
642 	extern(Windows)
643 	MMRESULT joySetCapture(HWND window, UINT stickId, UINT period, BOOL changed);
644 
645 	extern(Windows)
646 	MMRESULT joyGetPosEx(UINT stickId, JOYINFOEX* pji);
647 
648 	extern(Windows)
649 	MMRESULT joyReleaseCapture(UINT stickId);
650 
651 	// SEE ALSO:
652 	// http://msdn.microsoft.com/en-us/library/windows/desktop/dd757105%28v=vs.85%29.aspx
653 
654 	// Windows also provides joyGetThreshold, joySetThreshold
655 
656 	// there's also JOY2 messages
657 	enum MM_JOY1MOVE = 0; // FIXME
658 	enum MM_JOY1BUTTONDOWN = 0; // FIXME
659 	enum MM_JOY1BUTTONUP = 0; // FIXME
660 
661 	pragma(lib, "winmm");
662 
663 	version(arsd_js_test)
664 	void main() {
665 		/*
666 		// winmm test
667 		auto window = new SimpleWindow(500, 500);
668 
669 		joySetCapture(window.impl.hwnd, 0, 0, false);
670 
671 		window.handleNativeEvent = (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
672 			import std.stdio;
673 			writeln(msg, " ", wparam, " ", lparam);
674 			return 1;
675 		};
676 
677 		window.eventLoop(0);
678 
679 		joyReleaseCapture(0);
680 		*/
681 
682 		import std.stdio;
683 
684 		// xinput test
685 
686 		WindowsXInput x;
687 		if(!x.loadDll()) {
688 			writeln("Load DLL failed");
689 			return;
690 		}
691 
692 		writeln("success");
693 
694 		assert(x.XInputSetState !is null);
695 		assert(x.XInputGetState !is null);
696 
697 		XINPUT_STATE state;
698 
699 		XINPUT_VIBRATION vibration;
700 
701 		if(!x.XInputGetState(0, &state)) {
702 			writeln("Player 1 detected");
703 		} else return;
704 		if(!x.XInputGetState(1, &state)) {
705 			writeln("Player 2 detected");
706 		} else writeln("Player 2 not found");
707 
708 		DWORD pn;
709 		foreach(i; 0 .. 60) {
710 			x.XInputGetState(0, &state);
711 			if(pn != state.dwPacketNumber) {
712 				writeln("c: ", state);
713 				pn = state.dwPacketNumber;
714 			}
715 			Sleep(50);
716 			if(i == 20) {
717 				vibration.wLeftMotorSpeed = WORD.max;
718 				vibration.wRightMotorSpeed = WORD.max;
719 				x.XInputSetState(0, &vibration);
720 				vibration = XINPUT_VIBRATION.init;
721 			}
722 
723 			if(i == 40)
724 				x.XInputSetState(0, &vibration);
725 		}
726 	}
727 
728 	struct XINPUT_GAMEPAD {
729 		WORD  wButtons;
730 		BYTE  bLeftTrigger;
731 		BYTE  bRightTrigger;
732 		SHORT sThumbLX;
733 		SHORT sThumbLY;
734 		SHORT sThumbRX;
735 		SHORT sThumbRY;
736 	}
737 
738 	// enum XInputGamepadButtons {
739 	// It is a bitmask of these
740 	enum XINPUT_GAMEPAD_DPAD_UP =	0x0001;
741 	enum XINPUT_GAMEPAD_DPAD_DOWN =	0x0002;
742 	enum XINPUT_GAMEPAD_DPAD_LEFT =	0x0004;
743 	enum XINPUT_GAMEPAD_DPAD_RIGHT =	0x0008;
744 	enum XINPUT_GAMEPAD_START =	0x0010;
745 	enum XINPUT_GAMEPAD_BACK =	0x0020;
746 	enum XINPUT_GAMEPAD_LEFT_THUMB =	0x0040; // pushing on the stick
747 	enum XINPUT_GAMEPAD_RIGHT_THUMB =	0x0080;
748 	enum XINPUT_GAMEPAD_LEFT_SHOULDER =	0x0100;
749 	enum XINPUT_GAMEPAD_RIGHT_SHOULDER =	0x0200;
750 	enum XINPUT_GAMEPAD_A =	0x1000;
751 	enum XINPUT_GAMEPAD_B =	0x2000;
752 	enum XINPUT_GAMEPAD_X =	0x4000;
753 	enum XINPUT_GAMEPAD_Y =	0x8000;
754 
755 	enum XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE =  7849;
756 	enum XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689;
757 	enum XINPUT_GAMEPAD_TRIGGER_THRESHOLD =   30;
758 
759 	struct XINPUT_STATE {
760 		DWORD dwPacketNumber;
761 		XINPUT_GAMEPAD Gamepad;
762 	}
763 
764 	struct XINPUT_VIBRATION {
765 		WORD wLeftMotorSpeed; // low frequency motor. use any value between 0-65535 here
766 		WORD wRightMotorSpeed; // high frequency motor. use any value between 0-65535 here
767 	}
768 
769 	struct XINPUT_KEYSTROKE {
770 		WORD VirtualKey;
771 		WCHAR Unicode;
772 		WORD Flags;
773 		BYTE UserIndex;
774 		BYTE HidCode;
775 	}
776 
777 	enum XUSER_INDEX_ANY = 0xff;
778 
779 	enum VK_PAD_A                        = 0x5800;
780 	enum VK_PAD_B                        = 0x5801;
781 	enum VK_PAD_X                        = 0x5802;
782 	enum VK_PAD_Y                        = 0x5803;
783 	enum VK_PAD_RSHOULDER                = 0x5804;
784 	enum VK_PAD_LSHOULDER                = 0x5805;
785 	enum VK_PAD_LTRIGGER                 = 0x5806;
786 	enum VK_PAD_RTRIGGER                 = 0x5807;
787 
788 	enum VK_PAD_DPAD_UP                  = 0x5810;
789 	enum VK_PAD_DPAD_DOWN                = 0x5811;
790 	enum VK_PAD_DPAD_LEFT                = 0x5812;
791 	enum VK_PAD_DPAD_RIGHT               = 0x5813;
792 	enum VK_PAD_START                    = 0x5814;
793 	enum VK_PAD_BACK                     = 0x5815;
794 	enum VK_PAD_LTHUMB_PRESS             = 0x5816;
795 	enum VK_PAD_RTHUMB_PRESS             = 0x5817;
796 
797 	enum VK_PAD_LTHUMB_UP                = 0x5820;
798 	enum VK_PAD_LTHUMB_DOWN              = 0x5821;
799 	enum VK_PAD_LTHUMB_RIGHT             = 0x5822;
800 	enum VK_PAD_LTHUMB_LEFT              = 0x5823;
801 	enum VK_PAD_LTHUMB_UPLEFT            = 0x5824;
802 	enum VK_PAD_LTHUMB_UPRIGHT           = 0x5825;
803 	enum VK_PAD_LTHUMB_DOWNRIGHT         = 0x5826;
804 	enum VK_PAD_LTHUMB_DOWNLEFT          = 0x5827;
805 
806 	enum VK_PAD_RTHUMB_UP                = 0x5830;
807 	enum VK_PAD_RTHUMB_DOWN              = 0x5831;
808 	enum VK_PAD_RTHUMB_RIGHT             = 0x5832;
809 	enum VK_PAD_RTHUMB_LEFT              = 0x5833;
810 	enum VK_PAD_RTHUMB_UPLEFT            = 0x5834;
811 	enum VK_PAD_RTHUMB_UPRIGHT           = 0x5835;
812 	enum VK_PAD_RTHUMB_DOWNRIGHT         = 0x5836;
813 	enum VK_PAD_RTHUMB_DOWNLEFT          = 0x5837;
814 
815 	enum XINPUT_KEYSTROKE_KEYDOWN        = 0x0001;
816 	enum XINPUT_KEYSTROKE_KEYUP          = 0x0002;
817 	enum XINPUT_KEYSTROKE_REPEAT         = 0x0004;
818 
819 
820 
821 	struct WindowsXInput {
822 		HANDLE dll;
823 		bool loadDll() {
824 			// try Windows 8 first
825 			dll = LoadLibraryA("Xinput1_4.dll");
826 			if(dll is null) // then try Windows Vista
827 				dll = LoadLibraryA("Xinput9_1_0.dll");
828 
829 			if(dll is null)
830 				return false; // couldn't load it, tell user
831 
832 			XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState");
833 			XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState");
834 
835 			XInputGetKeystroke = cast(typeof(XInputGetKeystroke)) GetProcAddress(dll, "XInputGetKeystroke");
836 
837 			return true;
838 		}
839 
840 		~this() {
841 			unloadDll();
842 		}
843 
844 		void unloadDll() {
845 			if(dll !is null) {
846 				FreeLibrary(dll);
847 				dll = null;
848 			}
849 		}
850 
851 		// These are all dynamically loaded from the DLL
852 		extern(Windows) {
853 			DWORD function(DWORD, XINPUT_STATE*) XInputGetState;
854 			DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState;
855 			DWORD function(DWORD, DWORD, XINPUT_KEYSTROKE*) XInputGetKeystroke;
856 		}
857 
858 		// there's other functions but I don't use them; my controllers
859 		// are corded, for example, and I don't have a headset that works
860 		// with them. But if I get ones, I'll add them too.
861 		//
862 		// There's also some Windows 8 and up functions I didn't use, I just
863 		// wanted the basics.
864 	}
865 }
866 
867 version(linux) {
868 
869 	// https://www.kernel.org/doc/Documentation/input/joystick-api.txt
870 	struct js_event {
871 		uint time;
872 		short value;
873 		ubyte type;
874 		ubyte number;
875 	}
876 
877 	enum JS_EVENT_BUTTON = 0x01;
878 	enum JS_EVENT_AXIS   = 0x02;
879 	enum JS_EVENT_INIT   = 0x80;
880 
881 	import core.sys.posix.unistd;
882 	import core.sys.posix.fcntl;
883 
884 	import std.stdio;
885 
886 	struct RawControllerEvent {
887 		int controller;
888 		int type;
889 		int number;
890 		int value;
891 	}
892 
893 	// These values are determined experimentally on my Linux box
894 	// and won't necessarily match what you have. I really don't know.
895 	// TODO: see if these line up on Windows
896 }
897 
898 	// My hardware:
899 	// a Sony PS1 dual shock controller on a PSX to USB adapter from Radio Shack
900 	// and a wired XBox 360 controller from Microsoft.
901 
902 	// FIXME: these are the values based on my linux box, but I also use them as the virtual codes
903 	// I want nicer virtual codes I think.
904 
905 	enum PS1Buttons {
906 		triangle = 0,
907 		circle,
908 		cross,
909 		square,
910 		l2,
911 		r2,
912 		l1,
913 		r1,
914 		select,
915 		start,
916 		l3,
917 		r3
918 	}
919 
920 	// Use if analog is turned off
921 	// Tip: if you just check this OR the analog one it will work in both cases easily enough
922 	enum PS1Axes {
923 		horizontalDpad = 0,
924 		verticalDpad = 1,
925 	}
926 
927 	// Use if analog is turned on
928 	enum PS1AnalogAxes {
929 		horizontalLeftStick = 0,
930 		verticalLeftStick,
931 		verticalRightStick,
932 		horizontalRightStick,
933 		horizontalDpad,
934 		verticalDpad,
935 	}
936 
937 
938 	enum XBox360Buttons {
939 		a = 0,
940 		b,
941 		x,
942 		y,
943 		lb,
944 		rb,
945 		back,
946 		start,
947 		xboxLogo,
948 		leftStick,
949 		rightStick,
950 
951 		dpadLeft,
952 		dpadRight,
953 		dpadUp,
954 		dpadDown,
955 	}
956 
957 	enum XBox360Axes {
958 		horizontalLeftStick = 0,
959 		verticalLeftStick,
960 		lt,
961 		horizontalRightStick,
962 		verticalRightStick,
963 		rt,
964 		horizontalDpad,
965 		verticalDpad
966 	}
967 
968 version(linux) {
969 
970 	version(arsd_js_test)
971 	void main(string[] args) {
972 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js0".ptr, O_RDONLY);
973 		assert(fd > 0);
974 		js_event event;
975 
976 		short[8] axes;
977 		ubyte[16] buttons;
978 
979 		printf("\n");
980 
981 		while(true) {
982 			auto r = read(fd, &event, event.sizeof);
983 			assert(r == event.sizeof);
984 
985 			// writef("\r%12s", event);
986 			if(event.type & JS_EVENT_AXIS) {
987 				axes[event.number] = event.value >> 12;
988 			}
989 			if(event.type & JS_EVENT_BUTTON) {
990 				buttons[event.number] = cast(ubyte) event.value;
991 			}
992 			writef("\r%6s %1s", axes[0..8], buttons[0 .. 16]);
993 			stdout.flush();
994 		}
995 
996 		close(fd);
997 		printf("\n");
998 	}
999 
1000 	version(joystick_demo)
1001 	version(linux)
1002 	void amain(string[] args) {
1003 		import arsd.simpleaudio;
1004 
1005 		AudioOutput audio = AudioOutput(0);
1006 
1007 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js1".ptr, O_RDONLY | O_NONBLOCK);
1008 		assert(fd > 0);
1009 		js_event event;
1010 
1011 		short[512] buffer;
1012 
1013 		short val = short.max / 4;
1014 		int swap = 44100 / 600;
1015 		int swapCount = swap / 2;
1016 
1017 		short val2 = short.max / 4;
1018 		int swap2 = 44100 / 600;
1019 		int swapCount2 = swap / 2;
1020 
1021 		short[8] axes;
1022 		ubyte[16] buttons;
1023 
1024 		while(true) {
1025 			int r = read(fd, &event, event.sizeof);
1026 			while(r >= 0) {
1027 				import std.conv;
1028 				assert(r == event.sizeof, to!string(r));
1029 
1030 				// writef("\r%12s", event);
1031 				if(event.type & JS_EVENT_AXIS) {
1032 					axes[event.number] = event.value; //  >> 12;
1033 				}
1034 				if(event.type & JS_EVENT_BUTTON) {
1035 					buttons[event.number] = cast(ubyte) event.value;
1036 				}
1037 
1038 
1039 				int freq = axes[XBox360Axes.horizontalLeftStick];
1040 				freq += short.max;
1041 				freq /= 100;
1042 				freq += 400;
1043 
1044 				swap = 44100 / freq;
1045 
1046 				val = (cast(int) axes[XBox360Axes.lt] + short.max) / 8;
1047 
1048 
1049 				int freq2 = axes[XBox360Axes.horizontalRightStick];
1050 				freq2 += short.max;
1051 				freq2 /= 1000;
1052 				freq2 += 400;
1053 
1054 				swap2 = 44100 / freq2;
1055 
1056 				val2 = (cast(int) axes[XBox360Axes.rt] + short.max) / 8;
1057 
1058 
1059 				// try to starve the read
1060 				r = read(fd, &event, event.sizeof);
1061 			}
1062 
1063 			for(int i = 0; i < buffer.length / 2; i++) {
1064 			import std.math;
1065 				auto v = cast(ushort) (val * sin(cast(real) swapCount / (2*PI)));
1066 				auto v2 = cast(ushort) (val2 * sin(cast(real) swapCount2 / (2*PI)));
1067 				buffer[i*2] = cast(ushort)(v + v2);
1068 				buffer[i*2+1] = cast(ushort)(v + v2);
1069 				swapCount--;
1070 				swapCount2--;
1071 				if(swapCount == 0) {
1072 					swapCount = swap / 2;
1073 					// val = -val;
1074 				}
1075 				if(swapCount2 == 0) {
1076 					swapCount2 = swap2 / 2;
1077 					// val = -val;
1078 				}
1079 			}
1080 
1081 
1082 			//audio.write(buffer[]);
1083 		}
1084 
1085 		close(fd);
1086 	}
1087 }
1088 
1089 
1090 
1091 	version(joystick_demo)
1092 	version(Windows)
1093 	void amain() {
1094 		import arsd.simpleaudio;
1095 		auto midi = MidiOutput(0);
1096 		ubyte[16] buffer = void;
1097 		ubyte[] where = buffer[];
1098 		midi.writeRawMessageData(where.midiProgramChange(1, 79));
1099 
1100 		auto x = WindowsXInput();
1101 		x.loadDll();
1102 
1103 		XINPUT_STATE state;
1104 		XINPUT_STATE oldstate;
1105 		DWORD pn;
1106 		while(true) {
1107 			oldstate = state;
1108 			x.XInputGetState(0, &state);
1109 			byte note = 72;
1110 			if(state.dwPacketNumber != oldstate.dwPacketNumber) {
1111 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1112 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1113 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1114 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1115 
1116 				note = 75;
1117 
1118 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1119 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1120 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1121 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1122 
1123 				note = 77;
1124 
1125 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1126 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1127 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1128 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1129 
1130 				note = 79;
1131 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1132 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1133 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1134 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1135 
1136 				note = 81;
1137 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1138 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1139 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1140 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1141 
1142 				note = 83;
1143 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1144 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1145 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1146 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1147 			}
1148 
1149 			Sleep(1);
1150 
1151 			where = buffer[];
1152 		}
1153 	}
1154