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