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