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, ¤t); 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, ¤t, 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