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