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 WindowsXInput {
770 		HANDLE dll;
771 		bool loadDll() {
772 			// try Windows 8 first
773 			dll = LoadLibraryA("Xinput1_4.dll");
774 			if(dll is null) // then try Windows Vista
775 				dll = LoadLibraryA("Xinput9_1_0.dll");
776 
777 			if(dll is null)
778 				return false; // couldn't load it, tell user
779 
780 			XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState");
781 			XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState");
782 
783 			return true;
784 		}
785 
786 		~this() {
787 			unloadDll();
788 		}
789 
790 		void unloadDll() {
791 			if(dll !is null) {
792 				FreeLibrary(dll);
793 				dll = null;
794 			}
795 		}
796 
797 		// These are all dynamically loaded from the DLL
798 		extern(Windows) {
799 			DWORD function(DWORD, XINPUT_STATE*) XInputGetState;
800 			DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState;
801 		}
802 
803 		// there's other functions but I don't use them; my controllers
804 		// are corded, for example, and I don't have a headset that works
805 		// with them. But if I get ones, I'll add them too.
806 		//
807 		// There's also some Windows 8 and up functions I didn't use, I just
808 		// wanted the basics.
809 	}
810 }
811 
812 version(linux) {
813 
814 	// https://www.kernel.org/doc/Documentation/input/joystick-api.txt
815 	struct js_event {
816 		uint time;
817 		short value;
818 		ubyte type;
819 		ubyte number;
820 	}
821 
822 	enum JS_EVENT_BUTTON = 0x01;
823 	enum JS_EVENT_AXIS   = 0x02;
824 	enum JS_EVENT_INIT   = 0x80;
825 
826 	import core.sys.posix.unistd;
827 	import core.sys.posix.fcntl;
828 
829 	import std.stdio;
830 
831 	struct RawControllerEvent {
832 		int controller;
833 		int type;
834 		int number;
835 		int value;
836 	}
837 
838 	// These values are determined experimentally on my Linux box
839 	// and won't necessarily match what you have. I really don't know.
840 	// TODO: see if these line up on Windows
841 }
842 
843 	// My hardware:
844 	// a Sony PS1 dual shock controller on a PSX to USB adapter from Radio Shack
845 	// and a wired XBox 360 controller from Microsoft.
846 
847 	// FIXME: these are the values based on my linux box, but I also use them as the virtual codes
848 	// I want nicer virtual codes I think.
849 
850 	enum PS1Buttons {
851 		triangle = 0,
852 		circle,
853 		cross,
854 		square,
855 		l2,
856 		r2,
857 		l1,
858 		r1,
859 		select,
860 		start,
861 		l3,
862 		r3
863 	}
864 
865 	// Use if analog is turned off
866 	// Tip: if you just check this OR the analog one it will work in both cases easily enough
867 	enum PS1Axes {
868 		horizontalDpad = 0,
869 		verticalDpad = 1,
870 	}
871 
872 	// Use if analog is turned on
873 	enum PS1AnalogAxes {
874 		horizontalLeftStick = 0,
875 		verticalLeftStick,
876 		verticalRightStick,
877 		horizontalRightStick,
878 		horizontalDpad,
879 		verticalDpad,
880 	}
881 
882 
883 	enum XBox360Buttons {
884 		a = 0,
885 		b,
886 		x,
887 		y,
888 		lb,
889 		rb,
890 		back,
891 		start,
892 		xboxLogo,
893 		leftStick,
894 		rightStick,
895 
896 		dpadLeft,
897 		dpadRight,
898 		dpadUp,
899 		dpadDown,
900 	}
901 
902 	enum XBox360Axes {
903 		horizontalLeftStick = 0,
904 		verticalLeftStick,
905 		lt,
906 		horizontalRightStick,
907 		verticalRightStick,
908 		rt,
909 		horizontalDpad,
910 		verticalDpad
911 	}
912 
913 version(linux) {
914 
915 	version(arsd_js_test)
916 	void main(string[] args) {
917 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js0".ptr, O_RDONLY);
918 		assert(fd > 0);
919 		js_event event;
920 
921 		short[8] axes;
922 		ubyte[16] buttons;
923 
924 		printf("\n");
925 
926 		while(true) {
927 			auto r = read(fd, &event, event.sizeof);
928 			assert(r == event.sizeof);
929 
930 			// writef("\r%12s", event);
931 			if(event.type & JS_EVENT_AXIS) {
932 				axes[event.number] = event.value >> 12;
933 			}
934 			if(event.type & JS_EVENT_BUTTON) {
935 				buttons[event.number] = cast(ubyte) event.value;
936 			}
937 			writef("\r%6s %1s", axes[0..8], buttons[0 .. 16]);
938 			stdout.flush();
939 		}
940 
941 		close(fd);
942 		printf("\n");
943 	}
944 
945 	version(joystick_demo)
946 	version(linux)
947 	void amain(string[] args) {
948 		import arsd.simpleaudio;
949 
950 		AudioOutput audio = AudioOutput(0);
951 
952 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js1".ptr, O_RDONLY | O_NONBLOCK);
953 		assert(fd > 0);
954 		js_event event;
955 
956 		short[512] buffer;
957 
958 		short val = short.max / 4;
959 		int swap = 44100 / 600;
960 		int swapCount = swap / 2;
961 
962 		short val2 = short.max / 4;
963 		int swap2 = 44100 / 600;
964 		int swapCount2 = swap / 2;
965 
966 		short[8] axes;
967 		ubyte[16] buttons;
968 
969 		while(true) {
970 			int r = read(fd, &event, event.sizeof);
971 			while(r >= 0) {
972 				import std.conv;
973 				assert(r == event.sizeof, to!string(r));
974 
975 				// writef("\r%12s", event);
976 				if(event.type & JS_EVENT_AXIS) {
977 					axes[event.number] = event.value; //  >> 12;
978 				}
979 				if(event.type & JS_EVENT_BUTTON) {
980 					buttons[event.number] = cast(ubyte) event.value;
981 				}
982 
983 
984 				int freq = axes[XBox360Axes.horizontalLeftStick];
985 				freq += short.max;
986 				freq /= 100;
987 				freq += 400;
988 
989 				swap = 44100 / freq;
990 
991 				val = (cast(int) axes[XBox360Axes.lt] + short.max) / 8;
992 
993 
994 				int freq2 = axes[XBox360Axes.horizontalRightStick];
995 				freq2 += short.max;
996 				freq2 /= 1000;
997 				freq2 += 400;
998 
999 				swap2 = 44100 / freq2;
1000 
1001 				val2 = (cast(int) axes[XBox360Axes.rt] + short.max) / 8;
1002 
1003 
1004 				// try to starve the read
1005 				r = read(fd, &event, event.sizeof);
1006 			}
1007 
1008 			for(int i = 0; i < buffer.length / 2; i++) {
1009 			import std.math;
1010 				auto v = cast(ushort) (val * sin(cast(real) swapCount / (2*PI)));
1011 				auto v2 = cast(ushort) (val2 * sin(cast(real) swapCount2 / (2*PI)));
1012 				buffer[i*2] = cast(ushort)(v + v2);
1013 				buffer[i*2+1] = cast(ushort)(v + v2);
1014 				swapCount--;
1015 				swapCount2--;
1016 				if(swapCount == 0) {
1017 					swapCount = swap / 2;
1018 					// val = -val;
1019 				}
1020 				if(swapCount2 == 0) {
1021 					swapCount2 = swap2 / 2;
1022 					// val = -val;
1023 				}
1024 			}
1025 
1026 
1027 			//audio.write(buffer[]);
1028 		}
1029 
1030 		close(fd);
1031 	}
1032 }
1033 
1034 
1035 
1036 	version(joystick_demo)
1037 	version(Windows)
1038 	void amain() {
1039 		import arsd.simpleaudio;
1040 		auto midi = MidiOutput(0);
1041 		ubyte[16] buffer = void;
1042 		ubyte[] where = buffer[];
1043 		midi.writeRawMessageData(where.midiProgramChange(1, 79));
1044 
1045 		auto x = WindowsXInput();
1046 		x.loadDll();
1047 
1048 		XINPUT_STATE state;
1049 		XINPUT_STATE oldstate;
1050 		DWORD pn;
1051 		while(true) {
1052 			oldstate = state;
1053 			x.XInputGetState(0, &state);
1054 			byte note = 72;
1055 			if(state.dwPacketNumber != oldstate.dwPacketNumber) {
1056 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1057 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1058 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1059 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1060 
1061 				note = 75;
1062 
1063 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1064 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1065 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1066 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1067 
1068 				note = 77;
1069 
1070 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1071 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1072 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1073 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1074 
1075 				note = 79;
1076 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1077 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1078 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1079 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1080 
1081 				note = 81;
1082 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1083 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1084 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1085 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1086 
1087 				note = 83;
1088 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1089 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1090 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1091 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1092 			}
1093 
1094 			Sleep(1);
1095 
1096 			where = buffer[];
1097 		}
1098 	}
1099