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