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 			if(r != event.sizeof)
203 				throw new Exception("Read something weird off the joystick event fd");
204 				//import std.stdio; writeln(event);
205 
206 			ptrdiff_t player = -1;
207 			foreach(i, f; joystickFds)
208 				if(f == fd) {
209 					player = i;
210 					break;
211 				}
212 
213 			assert(player >= 0 && player < joystickState.length);
214 
215 			if(event.type & JS_EVENT_AXIS) {
216 				joystickState[player].axes[event.number] = event.value;
217 
218 				if(event.type & JS_EVENT_INIT) {
219 					if(event.number == 5) {
220 						// After being initialized, if axes[6] == 32767, it seems to be my PS1 controller
221 						// If axes[5] is -32767, it might be an Xbox controller.
222 
223 						if(event.value == -32767 && joystickMapping[player] is null) {
224 							joystickMapping[player] = &xbox360Mapping;
225 						}
226 					} else if(event.number == 6) {
227 						if((event.value == 32767 || event.value == -32767) && joystickMapping[player] is null) {
228 							joystickMapping[player] = &ps1Mapping;
229 						}
230 					}
231 				}
232 			}
233 			if(event.type & JS_EVENT_BUTTON) {
234 				joystickState[player].buttons[event.number] = event.value ? 255 : 0;
235 				//writeln(player, " ", event.number, " ", event.value, " ", joystickState[player].buttons[event.number]);//, " != ", event.value ? 255 : 0);
236 			}
237 		}
238 	}
239 }
240 
241 version(Windows) {
242 	extern(Windows)
243 	DWORD function(DWORD, XINPUT_STATE*) getJoystickOSState;
244 
245 	extern(Windows)
246 	DWORD winMMFallback(DWORD id, XINPUT_STATE* state) {
247 		JOYINFOEX info;
248 		auto result = joyGetPosEx(id, &info);
249 		if(result == 0) {
250 			// FIXME
251 
252 		}
253 		return result;
254 	}
255 
256 	alias JoystickState = XINPUT_STATE;
257 }
258 
259 /// Returns the number of players actually connected
260 ///
261 /// The controller ID
262 int enableJoystickInput(
263 	int player1ControllerId = 0,
264 	int player2ControllerId = 1,
265 	int player3ControllerId = 2,
266 	int player4ControllerId = 3)
267 {
268 	version(linux) {
269 		bool preparePlayer(int player, int id) {
270 			if(id < 0)
271 				return false;
272 
273 			assert(player >= 0 && player < joystickFds.length);
274 			assert(id < 10);
275 			assert(id >= 0);
276 			char[] filename = "/dev/input/js0\0".dup;
277 			filename[$-2] = cast(char) (id + '0');
278 
279 			int fd = open(filename.ptr, O_RDONLY);
280 			if(fd > 0) {
281 				joystickFds[player] = fd;
282 
283 				version(with_eventloop) {
284 					import arsd.eventloop;
285 					makeNonBlocking(fd);
286 					addFileEventListeners(fd, &readJoystickEvents, null, null);
287 				} else {
288 					// for polling, we will set nonblocking mode anyway,
289 					// the readJoystickEvents function will handle this fine
290 					// so we can call it when needed even on like a game timer.
291 					auto flags = fcntl(fd, F_GETFL, 0);
292 					if(flags == -1)
293 						throw new Exception("fcntl get");
294 					flags |= O_NONBLOCK;
295 					auto s = fcntl(fd, F_SETFL, flags);
296 					if(s == -1)
297 						throw new Exception("fcntl set");
298 				}
299 
300 				return true;
301 			}
302 			return false;
303 		}
304 
305 		if(!preparePlayer(0, player1ControllerId) ? 1 : 0)
306 			return 0;
307 		if(!preparePlayer(1, player2ControllerId) ? 1 : 0)
308 			return 1;
309 		if(!preparePlayer(2, player3ControllerId) ? 1 : 0)
310 			return 2;
311 		if(!preparePlayer(3, player4ControllerId) ? 1 : 0)
312 			return 3;
313 		return 4; // all players successfully initialized
314 	} else version(Windows) {
315 		if(wxi.loadDll()) {
316 			getJoystickOSState = wxi.XInputGetState;
317 		} else {
318 			// WinMM fallback
319 			getJoystickOSState = &winMMFallback;
320 		}
321 
322 		assert(getJoystickOSState !is null);
323 
324 		if(getJoystickOSState(player1ControllerId, &(joystickState[0])))
325 			return 0;
326 		if(getJoystickOSState(player2ControllerId, &(joystickState[1])))
327 			return 1;
328 		if(getJoystickOSState(player3ControllerId, &(joystickState[2])))
329 			return 2;
330 		if(getJoystickOSState(player4ControllerId, &(joystickState[3])))
331 			return 3;
332 
333 		return 4;
334 	} else static assert(0, "Unsupported OS");
335 
336 	// return 0;
337 }
338 
339 ///
340 void closeJoysticks() {
341 	version(linux) {
342 		foreach(ref fd; joystickFds) {
343 			if(fd > 0) {
344 				version(with_eventloop) {
345 					import arsd.eventloop;
346 					removeFileEventListeners(fd);
347 				}
348 				close(fd);
349 			}
350 			fd = -1;
351 		}
352 	} else version(Windows) {
353 		getJoystickOSState = null;
354 		wxi.unloadDll();
355 	} else static assert(0);
356 }
357 
358 ///
359 struct JoystickUpdate {
360 	///
361 	int player;
362 
363 	JoystickState old;
364 	JoystickState current;
365 
366 	/// changes from last update
367 	bool buttonWasJustPressed(Button button) {
368 		return buttonIsPressed(button) && !oldButtonIsPressed(button);
369 	}
370 
371 	/// ditto
372 	bool buttonWasJustReleased(Button button) {
373 		return !buttonIsPressed(button) && oldButtonIsPressed(button);
374 	}
375 
376 	/// this is normalized down to a 16 step change
377 	/// and ignores a dead zone near the middle
378 	short axisChange(Axis axis) {
379 		return cast(short) (axisPosition(axis) - oldAxisPosition(axis));
380 	}
381 
382 	/// current state
383 	bool buttonIsPressed(Button button) {
384 		return buttonIsPressedHelper(button, &current);
385 	}
386 
387 	/// Note: UP is negative!
388 	/// Value will actually be -16 to 16 ish.
389 	short axisPosition(Axis axis, short digitalFallbackValue = short.max) {
390 		return axisPositionHelper(axis, &current, digitalFallbackValue);
391 	}
392 
393 	/* private */
394 
395 	// old state
396 	bool oldButtonIsPressed(Button button) {
397 		return buttonIsPressedHelper(button, &old);
398 	}
399 
400 	short oldAxisPosition(Axis axis, short digitalFallbackValue = short.max) {
401 		return axisPositionHelper(axis, &old, digitalFallbackValue);
402 	}
403 
404 	short axisPositionHelper(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
405 		version(ps1_style) {
406 			// on PS1, the d-pad and left stick are synonyms for each other
407 			// the dpad takes precedence, if it is pressed
408 
409 			if(axis == PS1AnalogAxes.horizontalDpad || axis == PS1AnalogAxes.horizontalLeftStick) {
410 				auto it = axisPositionHelperRaw(PS1AnalogAxes.horizontalDpad, what, digitalFallbackValue);
411 				if(!it)
412 					it = axisPositionHelperRaw(PS1AnalogAxes.horizontalLeftStick, what, digitalFallbackValue);
413 				return it;
414 			}
415 
416 			if(axis == PS1AnalogAxes.verticalDpad || axis == PS1AnalogAxes.verticalLeftStick) {
417 				auto it = axisPositionHelperRaw(PS1AnalogAxes.verticalDpad, what, digitalFallbackValue);
418 				if(!it)
419 					it = axisPositionHelperRaw(PS1AnalogAxes.verticalLeftStick, what, digitalFallbackValue);
420 				return it;
421 			}
422 		}
423 
424 		return axisPositionHelperRaw(axis, what, digitalFallbackValue);
425 	}
426 
427 	static short normalizeAxis(short value) {
428 	/+
429 		auto v = normalizeAxisHack(value);
430 		import std.stdio;
431 		writeln(value, " :: ", v);
432 		return v;
433 	}
434 	static short normalizeAxisHack(short value) {
435 	+/
436 		if(value > -1600 && value < 1600)
437 			return 0; // the deadzone gives too much useless junk
438 		return cast(short) (value >>> 11);
439 	}
440 
441 	bool buttonIsPressedHelper(Button button, JoystickState* what) {
442 		version(linux) {
443 			int mapping = -1;
444 			if(auto ptr = joystickMapping[player])
445 				mapping = ptr.buttonOffsets[button];
446 			if(mapping != -1)
447 				return what.buttons[mapping] ? true : false;
448 			// otherwise what do we do?
449 			// FIXME
450 			return false; // the button isn't mapped, figure it isn't there and thus can't be pushed
451 		} else version(Windows) {
452 			// on Windows, I'm always assuming it is an XBox 360 controller
453 			// because that's what I have and the OS supports it so well
454 			version(xbox_style)
455 			final switch(button) {
456 				case XBox360Buttons.a: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
457 				case XBox360Buttons.b: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
458 				case XBox360Buttons.x: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
459 				case XBox360Buttons.y: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
460 
461 				case XBox360Buttons.lb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
462 				case XBox360Buttons.rb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
463 
464 				case XBox360Buttons.back: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
465 				case XBox360Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
466 
467 				case XBox360Buttons.leftStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
468 				case XBox360Buttons.rightStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
469 
470 				case XBox360Buttons.xboxLogo: return false;
471 			}
472 			else version(ps1_style)
473 			final switch(button) {
474 				case PS1Buttons.triangle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
475 				case PS1Buttons.square: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
476 				case PS1Buttons.cross: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
477 				case PS1Buttons.circle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
478 
479 				case PS1Buttons.select: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
480 				case PS1Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
481 
482 				case PS1Buttons.l1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
483 				case PS1Buttons.r1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
484 
485 				case PS1Buttons.l2: return (what.Gamepad.bLeftTrigger > 100);
486 				case PS1Buttons.r2: return (what.Gamepad.bRightTrigger > 100);
487 
488 				case PS1Buttons.l3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
489 				case PS1Buttons.r3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
490 			}
491 		}
492 	}
493 
494 	short axisPositionHelperRaw(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
495 		version(linux) {
496 			int mapping = -1;
497 			if(auto ptr = joystickMapping[player])
498 				mapping = ptr.axisOffsets[axis];
499 			if(mapping != -1)
500 				return normalizeAxis(what.axes[mapping]);
501 			return 0; // no such axis apparently, let the cooked one do something if it can
502 		} else version(Windows) {
503 			// on Windows, assuming it is an XBox 360 controller
504 			version(xbox_style)
505 			final switch(axis) {
506 				case XBox360Axes.horizontalLeftStick:
507 					return normalizeAxis(what.Gamepad.sThumbLX);
508 				case XBox360Axes.verticalLeftStick:
509 					return normalizeAxis(what.Gamepad.sThumbLY);
510 				case XBox360Axes.horizontalRightStick:
511 					return normalizeAxis(what.Gamepad.sThumbRX);
512 				case XBox360Axes.verticalRightStick:
513 					return normalizeAxis(what.Gamepad.sThumbRY);
514 				case XBox360Axes.verticalDpad:
515 					return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? -digitalFallbackValue :
516 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? digitalFallbackValue :
517 					       0;
518 				case XBox360Axes.horizontalDpad:
519 					return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? -digitalFallbackValue :
520 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? digitalFallbackValue :
521 					       0;
522 				case XBox360Axes.lt:
523 					return normalizeTrigger(what.Gamepad.bLeftTrigger);
524 				case XBox360Axes.rt:
525 					return normalizeTrigger(what.Gamepad.bRightTrigger);
526 			}
527 			else version(ps1_style)
528 			final switch(axis) {
529 				case PS1AnalogAxes.horizontalDpad:
530 				case PS1AnalogAxes.horizontalLeftStick:
531 					short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? cast(short)-cast(int)digitalFallbackValue :
532 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? digitalFallbackValue :
533 					       0;
534 					if(got == 0)
535 						got = what.Gamepad.sThumbLX;
536 
537 					return normalizeAxis(got);
538 				case PS1AnalogAxes.verticalDpad:
539 				case PS1AnalogAxes.verticalLeftStick:
540 					short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? digitalFallbackValue :
541 					       (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? cast(short)-cast(int)digitalFallbackValue :
542 						what.Gamepad.sThumbLY;
543 
544 					if(got == short.min)
545 						got++; // to avoid overflow on the axis inversion below
546 
547 					return normalizeAxis(cast(short)-cast(int)got);
548 				case PS1AnalogAxes.horizontalRightStick:
549 					return normalizeAxis(what.Gamepad.sThumbRX);
550 				case PS1AnalogAxes.verticalRightStick:
551 					return normalizeAxis(what.Gamepad.sThumbRY);
552 			}
553 		}
554 	}
555 
556 	version(Windows)
557 		short normalizeTrigger(BYTE b) {
558 			if(b < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
559 				return 0;
560 			return cast(short)((b << 8)|0xff);
561 		}
562 }
563 
564 ///
565 JoystickUpdate getJoystickUpdate(int player) {
566 	static JoystickState[4] previous;
567 
568 	version(Windows) {
569 		assert(getJoystickOSState !is null);
570 		if(getJoystickOSState(player, &(joystickState[player])))
571 			return JoystickUpdate();
572 			//throw new Exception("wtf");
573 	}
574 
575 	auto it = JoystickUpdate(player, previous[player], joystickState[player]);
576 
577 	previous[player] = joystickState[player];
578 
579 	return it;
580 }
581 
582 // --------------------------------
583 // Low level interface
584 // --------------------------------
585 
586 version(Windows) {
587 
588 	import core.sys.windows.windows;
589 
590 	alias MMRESULT = UINT;
591 
592 	struct JOYINFOEX {
593 		DWORD dwSize;
594 		DWORD dwFlags;
595 		DWORD dwXpos;
596 		DWORD dwYpos;
597 		DWORD dwZpos;
598 		DWORD dwRpos;
599 		DWORD dwUpos;
600 		DWORD dwVpos;
601 		DWORD dwButtons;
602 		DWORD dwButtonNumber;
603 		DWORD dwPOV;
604 		DWORD dwReserved1;
605 		DWORD dwReserved2;
606 	}
607 
608 	enum  : DWORD {
609 		JOY_POVCENTERED = -1,
610 		JOY_POVFORWARD  = 0,
611 		JOY_POVBACKWARD = 18000,
612 		JOY_POVLEFT     = 27000,
613 		JOY_POVRIGHT    = 9000
614 	}
615 
616 	extern(Windows)
617 	MMRESULT joySetCapture(HWND window, UINT stickId, UINT period, BOOL changed);
618 
619 	extern(Windows)
620 	MMRESULT joyGetPosEx(UINT stickId, JOYINFOEX* pji);
621 
622 	extern(Windows)
623 	MMRESULT joyReleaseCapture(UINT stickId);
624 
625 	// SEE ALSO:
626 	// http://msdn.microsoft.com/en-us/library/windows/desktop/dd757105%28v=vs.85%29.aspx
627 
628 	// Windows also provides joyGetThreshold, joySetThreshold
629 
630 	// there's also JOY2 messages
631 	enum MM_JOY1MOVE = 0; // FIXME
632 	enum MM_JOY1BUTTONDOWN = 0; // FIXME
633 	enum MM_JOY1BUTTONUP = 0; // FIXME
634 
635 	pragma(lib, "winmm");
636 
637 	version(arsd_js_test)
638 	void main() {
639 		/*
640 		// winmm test
641 		auto window = new SimpleWindow(500, 500);
642 
643 		joySetCapture(window.impl.hwnd, 0, 0, false);
644 
645 		window.handleNativeEvent = (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
646 			import std.stdio;
647 			writeln(msg, " ", wparam, " ", lparam);
648 			return 1;
649 		};
650 
651 		window.eventLoop(0);
652 
653 		joyReleaseCapture(0);
654 		*/
655 
656 		import std.stdio;
657 
658 		// xinput test
659 
660 		WindowsXInput x;
661 		if(!x.loadDll()) {
662 			writeln("Load DLL failed");
663 			return;
664 		}
665 
666 		writeln("success");
667 
668 		assert(x.XInputSetState !is null);
669 		assert(x.XInputGetState !is null);
670 
671 		XINPUT_STATE state;
672 
673 		XINPUT_VIBRATION vibration;
674 
675 		if(!x.XInputGetState(0, &state)) {
676 			writeln("Player 1 detected");
677 		} else return;
678 		if(!x.XInputGetState(1, &state)) {
679 			writeln("Player 2 detected");
680 		} else writeln("Player 2 not found");
681 
682 		DWORD pn;
683 		foreach(i; 0 .. 60) {
684 			x.XInputGetState(0, &state);
685 			if(pn != state.dwPacketNumber) {
686 				writeln("c: ", state);
687 				pn = state.dwPacketNumber;
688 			}
689 			Sleep(50);
690 			if(i == 20) {
691 				vibration.wLeftMotorSpeed = WORD.max;
692 				vibration.wRightMotorSpeed = WORD.max;
693 				x.XInputSetState(0, &vibration);
694 				vibration = XINPUT_VIBRATION.init;
695 			}
696 
697 			if(i == 40)
698 				x.XInputSetState(0, &vibration);
699 		}
700 	}
701 
702 	struct XINPUT_GAMEPAD {
703 		WORD  wButtons;
704 		BYTE  bLeftTrigger;
705 		BYTE  bRightTrigger;
706 		SHORT sThumbLX;
707 		SHORT sThumbLY;
708 		SHORT sThumbRX;
709 		SHORT sThumbRY;
710 	}
711 
712 	// enum XInputGamepadButtons {
713 	// It is a bitmask of these
714 	enum XINPUT_GAMEPAD_DPAD_UP =	0x0001;
715 	enum XINPUT_GAMEPAD_DPAD_DOWN =	0x0002;
716 	enum XINPUT_GAMEPAD_DPAD_LEFT =	0x0004;
717 	enum XINPUT_GAMEPAD_DPAD_RIGHT =	0x0008;
718 	enum XINPUT_GAMEPAD_START =	0x0010;
719 	enum XINPUT_GAMEPAD_BACK =	0x0020;
720 	enum XINPUT_GAMEPAD_LEFT_THUMB =	0x0040; // pushing on the stick
721 	enum XINPUT_GAMEPAD_RIGHT_THUMB =	0x0080;
722 	enum XINPUT_GAMEPAD_LEFT_SHOULDER =	0x0100;
723 	enum XINPUT_GAMEPAD_RIGHT_SHOULDER =	0x0200;
724 	enum XINPUT_GAMEPAD_A =	0x1000;
725 	enum XINPUT_GAMEPAD_B =	0x2000;
726 	enum XINPUT_GAMEPAD_X =	0x4000;
727 	enum XINPUT_GAMEPAD_Y =	0x8000;
728 
729 	enum XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE =  7849;
730 	enum XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689;
731 	enum XINPUT_GAMEPAD_TRIGGER_THRESHOLD =   30;
732 
733 	struct XINPUT_STATE {
734 		DWORD dwPacketNumber;
735 		XINPUT_GAMEPAD Gamepad;
736 	}
737 
738 	struct XINPUT_VIBRATION {
739 		WORD wLeftMotorSpeed; // low frequency motor. use any value between 0-65535 here
740 		WORD wRightMotorSpeed; // high frequency motor. use any value between 0-65535 here
741 	}
742 
743 	struct WindowsXInput {
744 		HANDLE dll;
745 		bool loadDll() {
746 			// try Windows 8 first
747 			dll = LoadLibraryA("Xinput1_4.dll");
748 			if(dll is null) // then try Windows Vista
749 				dll = LoadLibraryA("Xinput9_1_0.dll");
750 
751 			if(dll is null)
752 				return false; // couldn't load it, tell user
753 
754 			XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState");
755 			XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState");
756 
757 			return true;
758 		}
759 
760 		~this() {
761 			unloadDll();
762 		}
763 
764 		void unloadDll() {
765 			if(dll !is null) {
766 				FreeLibrary(dll);
767 				dll = null;
768 			}
769 		}
770 
771 		// These are all dynamically loaded from the DLL
772 		extern(Windows) {
773 			DWORD function(DWORD, XINPUT_STATE*) XInputGetState;
774 			DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState;
775 		}
776 
777 		// there's other functions but I don't use them; my controllers
778 		// are corded, for example, and I don't have a headset that works
779 		// with them. But if I get ones, I'll add them too.
780 		//
781 		// There's also some Windows 8 and up functions I didn't use, I just
782 		// wanted the basics.
783 	}
784 }
785 
786 version(linux) {
787 
788 	// https://www.kernel.org/doc/Documentation/input/joystick-api.txt
789 	struct js_event {
790 		uint time;
791 		short value;
792 		ubyte type;
793 		ubyte number;
794 	}
795 
796 	enum JS_EVENT_BUTTON = 0x01;
797 	enum JS_EVENT_AXIS   = 0x02;
798 	enum JS_EVENT_INIT   = 0x80;
799 
800 	import core.sys.posix.unistd;
801 	import core.sys.posix.fcntl;
802 
803 	import std.stdio;
804 
805 	struct RawControllerEvent {
806 		int controller;
807 		int type;
808 		int number;
809 		int value;
810 	}
811 
812 	// These values are determined experimentally on my Linux box
813 	// and won't necessarily match what you have. I really don't know.
814 	// TODO: see if these line up on Windows
815 }
816 
817 	// My hardware:
818 	// a Sony PS1 dual shock controller on a PSX to USB adapter from Radio Shack
819 	// and a wired XBox 360 controller from Microsoft.
820 
821 	// FIXME: these are the values based on my linux box, but I also use them as the virtual codes
822 	// I want nicer virtual codes I think.
823 
824 	enum PS1Buttons {
825 		triangle = 0,
826 		circle,
827 		cross,
828 		square,
829 		l2,
830 		r2,
831 		l1,
832 		r1,
833 		select,
834 		start,
835 		l3,
836 		r3
837 	}
838 
839 	// Use if analog is turned off
840 	// Tip: if you just check this OR the analog one it will work in both cases easily enough
841 	enum PS1Axes {
842 		horizontalDpad = 0,
843 		verticalDpad = 1,
844 	}
845 
846 	// Use if analog is turned on
847 	enum PS1AnalogAxes {
848 		horizontalLeftStick = 0,
849 		verticalLeftStick,
850 		verticalRightStick,
851 		horizontalRightStick,
852 		horizontalDpad,
853 		verticalDpad,
854 	}
855 
856 
857 	enum XBox360Buttons {
858 		a = 0,
859 		b,
860 		x,
861 		y,
862 		lb,
863 		rb,
864 		back,
865 		start,
866 		xboxLogo,
867 		leftStick,
868 		rightStick
869 	}
870 
871 	enum XBox360Axes {
872 		horizontalLeftStick = 0,
873 		verticalLeftStick,
874 		lt,
875 		horizontalRightStick,
876 		verticalRightStick,
877 		rt,
878 		horizontalDpad,
879 		verticalDpad
880 	}
881 
882 version(linux) {
883 
884 	version(arsd_js_test)
885 	void main(string[] args) {
886 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js0".ptr, O_RDONLY);
887 		assert(fd > 0);
888 		js_event event;
889 
890 		short[8] axes;
891 		ubyte[16] buttons;
892 
893 		printf("\n");
894 
895 		while(true) {
896 			auto r = read(fd, &event, event.sizeof);
897 			assert(r == event.sizeof);
898 
899 			// writef("\r%12s", event);
900 			if(event.type & JS_EVENT_AXIS) {
901 				axes[event.number] = event.value >> 12;
902 			}
903 			if(event.type & JS_EVENT_BUTTON) {
904 				buttons[event.number] = cast(ubyte) event.value;
905 			}
906 			writef("\r%6s %1s", axes[0..8], buttons[0 .. 16]);
907 			stdout.flush();
908 		}
909 
910 		close(fd);
911 		printf("\n");
912 	}
913 
914 	version(joystick_demo)
915 	version(linux)
916 	void amain(string[] args) {
917 		import arsd.simpleaudio;
918 
919 		AudioOutput audio = AudioOutput(0);
920 
921 		int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js1".ptr, O_RDONLY | O_NONBLOCK);
922 		assert(fd > 0);
923 		js_event event;
924 
925 		short[512] buffer;
926 
927 		short val = short.max / 4;
928 		int swap = 44100 / 600;
929 		int swapCount = swap / 2;
930 
931 		short val2 = short.max / 4;
932 		int swap2 = 44100 / 600;
933 		int swapCount2 = swap / 2;
934 
935 		short[8] axes;
936 		ubyte[16] buttons;
937 
938 		while(true) {
939 			int r = read(fd, &event, event.sizeof);
940 			while(r >= 0) {
941 				import std.conv;
942 				assert(r == event.sizeof, to!string(r));
943 
944 				// writef("\r%12s", event);
945 				if(event.type & JS_EVENT_AXIS) {
946 					axes[event.number] = event.value; //  >> 12;
947 				}
948 				if(event.type & JS_EVENT_BUTTON) {
949 					buttons[event.number] = cast(ubyte) event.value;
950 				}
951 
952 
953 				int freq = axes[XBox360Axes.horizontalLeftStick];
954 				freq += short.max;
955 				freq /= 100;
956 				freq += 400;
957 
958 				swap = 44100 / freq;
959 
960 				val = (cast(int) axes[XBox360Axes.lt] + short.max) / 8;
961 
962 
963 				int freq2 = axes[XBox360Axes.horizontalRightStick];
964 				freq2 += short.max;
965 				freq2 /= 1000;
966 				freq2 += 400;
967 
968 				swap2 = 44100 / freq2;
969 
970 				val2 = (cast(int) axes[XBox360Axes.rt] + short.max) / 8;
971 
972 
973 				// try to starve the read
974 				r = read(fd, &event, event.sizeof);
975 			}
976 
977 			for(int i = 0; i < buffer.length / 2; i++) {
978 			import std.math;
979 				auto v = cast(ushort) (val * sin(cast(real) swapCount / (2*PI)));
980 				auto v2 = cast(ushort) (val2 * sin(cast(real) swapCount2 / (2*PI)));
981 				buffer[i*2] = cast(ushort)(v + v2);
982 				buffer[i*2+1] = cast(ushort)(v + v2);
983 				swapCount--;
984 				swapCount2--;
985 				if(swapCount == 0) {
986 					swapCount = swap / 2;
987 					// val = -val;
988 				}
989 				if(swapCount2 == 0) {
990 					swapCount2 = swap2 / 2;
991 					// val = -val;
992 				}
993 			}
994 
995 
996 			//audio.write(buffer[]);
997 		}
998 
999 		close(fd);
1000 	}
1001 }
1002 
1003 
1004 
1005 	version(joystick_demo)
1006 	version(Windows)
1007 	void amain() {
1008 		import arsd.simpleaudio;
1009 		auto midi = MidiOutput(0);
1010 		ubyte[16] buffer = void;
1011 		ubyte[] where = buffer[];
1012 		midi.writeRawMessageData(where.midiProgramChange(1, 79));
1013 
1014 		auto x = WindowsXInput();
1015 		x.loadDll();
1016 
1017 		XINPUT_STATE state;
1018 		XINPUT_STATE oldstate;
1019 		DWORD pn;
1020 		while(true) {
1021 			oldstate = state;
1022 			x.XInputGetState(0, &state);
1023 			byte note = 72;
1024 			if(state.dwPacketNumber != oldstate.dwPacketNumber) {
1025 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1026 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1027 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
1028 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1029 
1030 				note = 75;
1031 
1032 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1033 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1034 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
1035 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1036 
1037 				note = 77;
1038 
1039 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1040 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1041 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
1042 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1043 
1044 				note = 79;
1045 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1046 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1047 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
1048 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1049 
1050 				note = 81;
1051 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1052 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1053 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
1054 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1055 
1056 				note = 83;
1057 				if((state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1058 					midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
1059 				if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
1060 					midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
1061 			}
1062 
1063 			Sleep(1);
1064 
1065 			where = buffer[];
1066 		}
1067 	}
1068