directinput.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #pragma once
  2. auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL;
  3. auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
  4. auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
  5. struct InputJoypadDirectInput {
  6. Input& input;
  7. InputJoypadDirectInput(Input& input) : input(input) {}
  8. struct Joypad {
  9. shared_pointer<HID::Joypad> hid{new HID::Joypad};
  10. LPDIRECTINPUTDEVICE8 device = nullptr;
  11. LPDIRECTINPUTEFFECT effect = nullptr;
  12. uint32_t pathID = 0;
  13. uint16_t vendorID = 0;
  14. uint16_t productID = 0;
  15. bool isXInputDevice = false;
  16. };
  17. vector<Joypad> joypads;
  18. uintptr_t handle = 0;
  19. LPDIRECTINPUT8 context = nullptr;
  20. LPDIRECTINPUTDEVICE8 device = nullptr;
  21. bool xinputAvailable = false;
  22. uint effects = 0;
  23. auto assign(shared_pointer<HID::Joypad> hid, uint groupID, uint inputID, int16_t value) -> void {
  24. auto& group = hid->group(groupID);
  25. if(group.input(inputID).value() == value) return;
  26. input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
  27. group.input(inputID).setValue(value);
  28. }
  29. auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
  30. for(auto& jp : joypads) {
  31. if(FAILED(jp.device->Poll())) jp.device->Acquire();
  32. DIJOYSTATE2 state;
  33. if(FAILED(jp.device->GetDeviceState(sizeof(DIJOYSTATE2), &state))) continue;
  34. for(auto n : range(4)) {
  35. assign(jp.hid, HID::Joypad::GroupID::Axis, 0, state.lX);
  36. assign(jp.hid, HID::Joypad::GroupID::Axis, 1, state.lY);
  37. assign(jp.hid, HID::Joypad::GroupID::Axis, 2, state.lZ);
  38. assign(jp.hid, HID::Joypad::GroupID::Axis, 3, state.lRx);
  39. assign(jp.hid, HID::Joypad::GroupID::Axis, 4, state.lRy);
  40. assign(jp.hid, HID::Joypad::GroupID::Axis, 5, state.lRz);
  41. uint pov = state.rgdwPOV[n];
  42. int16_t xaxis = 0;
  43. int16_t yaxis = 0;
  44. if(pov < 36000) {
  45. if(pov >= 31500 || pov <= 4500) yaxis = -32767;
  46. if(pov >= 4500 && pov <= 13500) xaxis = +32767;
  47. if(pov >= 13500 && pov <= 22500) yaxis = +32767;
  48. if(pov >= 22500 && pov <= 31500) xaxis = -32767;
  49. }
  50. assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, xaxis);
  51. assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, yaxis);
  52. }
  53. for(auto n : range(128)) {
  54. assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]);
  55. }
  56. devices.append(jp.hid);
  57. }
  58. }
  59. auto rumble(uint64_t id, bool enable) -> bool {
  60. for(auto& jp : joypads) {
  61. if(jp.hid->id() != id) continue;
  62. if(jp.effect == nullptr) continue;
  63. if(enable) jp.effect->Start(1, 0);
  64. else jp.effect->Stop();
  65. return true;
  66. }
  67. return false;
  68. }
  69. auto initialize(uintptr handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
  70. if(!handle) return false;
  71. this->handle = handle;
  72. this->context = context;
  73. this->xinputAvailable = xinputAvailable;
  74. context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
  75. return true;
  76. }
  77. auto terminate() -> void {
  78. for(auto& jp : joypads) {
  79. jp.device->Unacquire();
  80. if(jp.effect) jp.effect->Release();
  81. jp.device->Release();
  82. }
  83. joypads.reset();
  84. context = nullptr;
  85. }
  86. auto initJoypad(const DIDEVICEINSTANCE* instance) -> bool {
  87. Joypad jp;
  88. jp.vendorID = instance->guidProduct.Data1 >> 0;
  89. jp.productID = instance->guidProduct.Data1 >> 16;
  90. jp.isXInputDevice = false;
  91. if(auto device = rawinput.find(jp.vendorID, jp.productID)) {
  92. jp.isXInputDevice = device().isXInputDevice;
  93. }
  94. //Microsoft has intentionally imposed artificial restrictions on XInput devices when used with DirectInput
  95. //a) the two triggers are merged into a single axis, making uniquely distinguishing them impossible
  96. //b) rumble support is not exposed
  97. //thus, it's always preferred to let the XInput driver handle these joypads
  98. //but if the driver is not available (XInput 1.3 does not ship with stock Windows XP), fall back on DirectInput
  99. if(jp.isXInputDevice && xinputAvailable) return DIENUM_CONTINUE;
  100. if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) return DIENUM_CONTINUE;
  101. jp.device = device;
  102. device->SetDataFormat(&c_dfDIJoystick2);
  103. device->SetCooperativeLevel((HWND)handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
  104. effects = 0;
  105. device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
  106. device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR);
  107. jp.hid->setRumble(effects > 0);
  108. DIPROPGUIDANDPATH property;
  109. memset(&property, 0, sizeof(DIPROPGUIDANDPATH));
  110. property.diph.dwSize = sizeof(DIPROPGUIDANDPATH);
  111. property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
  112. property.diph.dwObj = 0;
  113. property.diph.dwHow = DIPH_DEVICE;
  114. device->GetProperty(DIPROP_GUIDANDPATH, &property.diph);
  115. string devicePath = (const char*)utf8_t(property.wszPath);
  116. jp.pathID = Hash::CRC32(devicePath).value();
  117. jp.hid->setVendorID(jp.vendorID);
  118. jp.hid->setProductID(jp.productID);
  119. jp.hid->setPathID(jp.pathID);
  120. if(jp.hid->rumble()) {
  121. //disable auto-centering spring for rumble support
  122. DIPROPDWORD property;
  123. memset(&property, 0, sizeof(DIPROPDWORD));
  124. property.diph.dwSize = sizeof(DIPROPDWORD);
  125. property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
  126. property.diph.dwObj = 0;
  127. property.diph.dwHow = DIPH_DEVICE;
  128. property.dwData = false;
  129. device->SetProperty(DIPROP_AUTOCENTER, &property.diph);
  130. DWORD dwAxes[2] = {(DWORD)DIJOFS_X, (DWORD)DIJOFS_Y};
  131. LONG lDirection[2] = {0, 0};
  132. DICONSTANTFORCE force;
  133. force.lMagnitude = DI_FFNOMINALMAX; //full force
  134. DIEFFECT effect;
  135. memset(&effect, 0, sizeof(DIEFFECT));
  136. effect.dwSize = sizeof(DIEFFECT);
  137. effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
  138. effect.dwDuration = INFINITE;
  139. effect.dwSamplePeriod = 0;
  140. effect.dwGain = DI_FFNOMINALMAX;
  141. effect.dwTriggerButton = DIEB_NOTRIGGER;
  142. effect.dwTriggerRepeatInterval = 0;
  143. effect.cAxes = 2;
  144. effect.rgdwAxes = dwAxes;
  145. effect.rglDirection = lDirection;
  146. effect.lpEnvelope = 0;
  147. effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
  148. effect.lpvTypeSpecificParams = &force;
  149. effect.dwStartDelay = 0;
  150. device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL);
  151. }
  152. for(auto n : range(6)) jp.hid->axes().append(n);
  153. for(auto n : range(8)) jp.hid->hats().append(n);
  154. for(auto n : range(128)) jp.hid->buttons().append(n);
  155. joypads.append(jp);
  156. return DIENUM_CONTINUE;
  157. }
  158. auto initAxis(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
  159. DIPROPRANGE range;
  160. memset(&range, 0, sizeof(DIPROPRANGE));
  161. range.diph.dwSize = sizeof(DIPROPRANGE);
  162. range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
  163. range.diph.dwHow = DIPH_BYID;
  164. range.diph.dwObj = instance->dwType;
  165. range.lMin = -32768;
  166. range.lMax = +32767;
  167. device->SetProperty(DIPROP_RANGE, &range.diph);
  168. return DIENUM_CONTINUE;
  169. }
  170. auto initEffect(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
  171. effects++;
  172. return DIENUM_CONTINUE;
  173. }
  174. };
  175. auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL {
  176. return ((InputJoypadDirectInput*)p)->initJoypad(instance);
  177. }
  178. auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
  179. return ((InputJoypadDirectInput*)p)->initAxis(instance);
  180. }
  181. auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
  182. return ((InputJoypadDirectInput*)p)->initEffect(instance);
  183. }