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