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 WindowsXInput { 770 HANDLE dll; 771 bool loadDll() { 772 // try Windows 8 first 773 dll = LoadLibraryA("Xinput1_4.dll"); 774 if(dll is null) // then try Windows Vista 775 dll = LoadLibraryA("Xinput9_1_0.dll"); 776 777 if(dll is null) 778 return false; // couldn't load it, tell user 779 780 XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState"); 781 XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState"); 782 783 return true; 784 } 785 786 ~this() { 787 unloadDll(); 788 } 789 790 void unloadDll() { 791 if(dll !is null) { 792 FreeLibrary(dll); 793 dll = null; 794 } 795 } 796 797 // These are all dynamically loaded from the DLL 798 extern(Windows) { 799 DWORD function(DWORD, XINPUT_STATE*) XInputGetState; 800 DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState; 801 } 802 803 // there's other functions but I don't use them; my controllers 804 // are corded, for example, and I don't have a headset that works 805 // with them. But if I get ones, I'll add them too. 806 // 807 // There's also some Windows 8 and up functions I didn't use, I just 808 // wanted the basics. 809 } 810 } 811 812 version(linux) { 813 814 // https://www.kernel.org/doc/Documentation/input/joystick-api.txt 815 struct js_event { 816 uint time; 817 short value; 818 ubyte type; 819 ubyte number; 820 } 821 822 enum JS_EVENT_BUTTON = 0x01; 823 enum JS_EVENT_AXIS = 0x02; 824 enum JS_EVENT_INIT = 0x80; 825 826 import core.sys.posix.unistd; 827 import core.sys.posix.fcntl; 828 829 import std.stdio; 830 831 struct RawControllerEvent { 832 int controller; 833 int type; 834 int number; 835 int value; 836 } 837 838 // These values are determined experimentally on my Linux box 839 // and won't necessarily match what you have. I really don't know. 840 // TODO: see if these line up on Windows 841 } 842 843 // My hardware: 844 // a Sony PS1 dual shock controller on a PSX to USB adapter from Radio Shack 845 // and a wired XBox 360 controller from Microsoft. 846 847 // FIXME: these are the values based on my linux box, but I also use them as the virtual codes 848 // I want nicer virtual codes I think. 849 850 enum PS1Buttons { 851 triangle = 0, 852 circle, 853 cross, 854 square, 855 l2, 856 r2, 857 l1, 858 r1, 859 select, 860 start, 861 l3, 862 r3 863 } 864 865 // Use if analog is turned off 866 // Tip: if you just check this OR the analog one it will work in both cases easily enough 867 enum PS1Axes { 868 horizontalDpad = 0, 869 verticalDpad = 1, 870 } 871 872 // Use if analog is turned on 873 enum PS1AnalogAxes { 874 horizontalLeftStick = 0, 875 verticalLeftStick, 876 verticalRightStick, 877 horizontalRightStick, 878 horizontalDpad, 879 verticalDpad, 880 } 881 882 883 enum XBox360Buttons { 884 a = 0, 885 b, 886 x, 887 y, 888 lb, 889 rb, 890 back, 891 start, 892 xboxLogo, 893 leftStick, 894 rightStick, 895 896 dpadLeft, 897 dpadRight, 898 dpadUp, 899 dpadDown, 900 } 901 902 enum XBox360Axes { 903 horizontalLeftStick = 0, 904 verticalLeftStick, 905 lt, 906 horizontalRightStick, 907 verticalRightStick, 908 rt, 909 horizontalDpad, 910 verticalDpad 911 } 912 913 version(linux) { 914 915 version(arsd_js_test) 916 void main(string[] args) { 917 int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js0".ptr, O_RDONLY); 918 assert(fd > 0); 919 js_event event; 920 921 short[8] axes; 922 ubyte[16] buttons; 923 924 printf("\n"); 925 926 while(true) { 927 auto r = read(fd, &event, event.sizeof); 928 assert(r == event.sizeof); 929 930 // writef("\r%12s", event); 931 if(event.type & JS_EVENT_AXIS) { 932 axes[event.number] = event.value >> 12; 933 } 934 if(event.type & JS_EVENT_BUTTON) { 935 buttons[event.number] = cast(ubyte) event.value; 936 } 937 writef("\r%6s %1s", axes[0..8], buttons[0 .. 16]); 938 stdout.flush(); 939 } 940 941 close(fd); 942 printf("\n"); 943 } 944 945 version(joystick_demo) 946 version(linux) 947 void amain(string[] args) { 948 import arsd.simpleaudio; 949 950 AudioOutput audio = AudioOutput(0); 951 952 int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js1".ptr, O_RDONLY | O_NONBLOCK); 953 assert(fd > 0); 954 js_event event; 955 956 short[512] buffer; 957 958 short val = short.max / 4; 959 int swap = 44100 / 600; 960 int swapCount = swap / 2; 961 962 short val2 = short.max / 4; 963 int swap2 = 44100 / 600; 964 int swapCount2 = swap / 2; 965 966 short[8] axes; 967 ubyte[16] buttons; 968 969 while(true) { 970 int r = read(fd, &event, event.sizeof); 971 while(r >= 0) { 972 import std.conv; 973 assert(r == event.sizeof, to!string(r)); 974 975 // writef("\r%12s", event); 976 if(event.type & JS_EVENT_AXIS) { 977 axes[event.number] = event.value; // >> 12; 978 } 979 if(event.type & JS_EVENT_BUTTON) { 980 buttons[event.number] = cast(ubyte) event.value; 981 } 982 983 984 int freq = axes[XBox360Axes.horizontalLeftStick]; 985 freq += short.max; 986 freq /= 100; 987 freq += 400; 988 989 swap = 44100 / freq; 990 991 val = (cast(int) axes[XBox360Axes.lt] + short.max) / 8; 992 993 994 int freq2 = axes[XBox360Axes.horizontalRightStick]; 995 freq2 += short.max; 996 freq2 /= 1000; 997 freq2 += 400; 998 999 swap2 = 44100 / freq2; 1000 1001 val2 = (cast(int) axes[XBox360Axes.rt] + short.max) / 8; 1002 1003 1004 // try to starve the read 1005 r = read(fd, &event, event.sizeof); 1006 } 1007 1008 for(int i = 0; i < buffer.length / 2; i++) { 1009 import std.math; 1010 auto v = cast(ushort) (val * sin(cast(real) swapCount / (2*PI))); 1011 auto v2 = cast(ushort) (val2 * sin(cast(real) swapCount2 / (2*PI))); 1012 buffer[i*2] = cast(ushort)(v + v2); 1013 buffer[i*2+1] = cast(ushort)(v + v2); 1014 swapCount--; 1015 swapCount2--; 1016 if(swapCount == 0) { 1017 swapCount = swap / 2; 1018 // val = -val; 1019 } 1020 if(swapCount2 == 0) { 1021 swapCount2 = swap2 / 2; 1022 // val = -val; 1023 } 1024 } 1025 1026 1027 //audio.write(buffer[]); 1028 } 1029 1030 close(fd); 1031 } 1032 } 1033 1034 1035 1036 version(joystick_demo) 1037 version(Windows) 1038 void amain() { 1039 import arsd.simpleaudio; 1040 auto midi = MidiOutput(0); 1041 ubyte[16] buffer = void; 1042 ubyte[] where = buffer[]; 1043 midi.writeRawMessageData(where.midiProgramChange(1, 79)); 1044 1045 auto x = WindowsXInput(); 1046 x.loadDll(); 1047 1048 XINPUT_STATE state; 1049 XINPUT_STATE oldstate; 1050 DWORD pn; 1051 while(true) { 1052 oldstate = state; 1053 x.XInputGetState(0, &state); 1054 byte note = 72; 1055 if(state.dwPacketNumber != oldstate.dwPacketNumber) { 1056 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A)) 1057 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1058 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A)) 1059 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1060 1061 note = 75; 1062 1063 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B)) 1064 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1065 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B)) 1066 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1067 1068 note = 77; 1069 1070 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X)) 1071 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1072 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X)) 1073 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1074 1075 note = 79; 1076 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y)) 1077 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1078 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y)) 1079 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1080 1081 note = 81; 1082 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)) 1083 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1084 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)) 1085 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1086 1087 note = 83; 1088 if((state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)) 1089 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 1090 if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)) 1091 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 1092 } 1093 1094 Sleep(1); 1095 1096 where = buffer[]; 1097 } 1098 } 1099