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