File: xinput.cpp

package info (click to toggle)
ares 147%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 35,244 kB
  • sloc: cpp: 334,263; ansic: 98,696; sh: 123; makefile: 31
file content (162 lines) | stat: -rw-r--r-- 6,984 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#pragma once

//documented functionality
#define oXInputGetState  "XInputGetState"
#define oXInputSetState  "XInputSetState"
typedef DWORD (WINAPI *pXInputGetState)(DWORD dwUserIndex, XINPUT_STATE* pState);
typedef DWORD (WINAPI *pXInputSetState)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration);

//undocumented functionality
#define oXInputGetStateEx             (LPCSTR)100
#define oXInputWaitForGuideButton     (LPCSTR)101
#define oXInputCancelGuideButtonWait  (LPCSTR)102
#define oXInputPowerOffController     (LPCSTR)103
typedef DWORD (WINAPI *pXInputGetStateEx)(DWORD dwUserIndex, XINPUT_STATE* pState);
typedef DWORD (WINAPI *pXInputWaitForGuideButton)(DWORD dwUserIndex, DWORD dwFlag, void* pUnknown);
typedef DWORD (WINAPI *pXInputCancelGuideButtonWait)(DWORD dwUserIndex);
typedef DWORD (WINAPI *pXInputPowerOffController)(DWORD dwUserIndex);

#define XINPUT_GAMEPAD_GUIDE  0x0400

struct InputJoypadXInput {
  Input& input;
  InputJoypadXInput(Input& input) : input(input) {}

  HMODULE libxinput = nullptr;
  pXInputGetStateEx XInputGetStateEx = nullptr;
  pXInputSetState XInputSetState = nullptr;

  struct Joypad {
    std::shared_ptr<HID::Joypad> hid = std::make_shared<HID::Joypad>();
    u32 id = 0;
  };
  std::vector<Joypad> joypads;

  auto assign(std::shared_ptr<HID::Joypad> hid, u32 groupID, u32 inputID, s16 value) -> void {
    auto& group = hid->group(groupID);
    if(group.input(inputID).value() == value) return;
    input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
    group.input(inputID).setValue(value);
  }

  auto poll(std::vector<std::shared_ptr<HID::Device>>& devices) -> void {
    for(auto& jp : joypads) {
      XINPUT_STATE state;
      if(XInputGetStateEx(jp.id, &state) != ERROR_SUCCESS) continue;

      //flip vertical axes so that -32768 = up, +32767 = down
      u16 axisLY = 32768 + state.Gamepad.sThumbLY;
      u16 axisRY = 32768 + state.Gamepad.sThumbRY;
      assign(jp.hid, HID::Joypad::GroupID::Axis, 0, (s16)state.Gamepad.sThumbLX);
      assign(jp.hid, HID::Joypad::GroupID::Axis, 1, (s16)(~axisLY - 32768));
      assign(jp.hid, HID::Joypad::GroupID::Axis, 2, (s16)state.Gamepad.sThumbRX);
      assign(jp.hid, HID::Joypad::GroupID::Axis, 3, (s16)(~axisRY - 32768));

      s16 hatX = 0;
      s16 hatY = 0;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP   ) hatY -= 32767;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hatY += 32767;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hatX -= 32767;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) hatX += 32767;

      assign(jp.hid, HID::Joypad::GroupID::Hat, 0, hatX);
      assign(jp.hid, HID::Joypad::GroupID::Hat, 1, hatY);

      //scale trigger ranges for (not-pressed to pressed) from (0 to 255) to (0 to +32767)
      s16 triggerL = state.Gamepad.bLeftTrigger;
      s16 triggerR = state.Gamepad.bRightTrigger;
      triggerL = triggerL << 7 | triggerL >> 1;
      triggerR = triggerR << 7 | triggerR >> 1;

      assign(jp.hid, HID::Joypad::GroupID::Trigger, 0, triggerL);
      assign(jp.hid, HID::Joypad::GroupID::Trigger, 1, triggerR);

      assign(jp.hid, HID::Joypad::GroupID::Button,  0, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_A));
      assign(jp.hid, HID::Joypad::GroupID::Button,  1, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_B));
      assign(jp.hid, HID::Joypad::GroupID::Button,  2, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_X));
      assign(jp.hid, HID::Joypad::GroupID::Button,  3, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y));
      assign(jp.hid, HID::Joypad::GroupID::Button,  4, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK));
      assign(jp.hid, HID::Joypad::GroupID::Button,  5, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_START));
      assign(jp.hid, HID::Joypad::GroupID::Button,  6, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER));
      assign(jp.hid, HID::Joypad::GroupID::Button,  7, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER));
      assign(jp.hid, HID::Joypad::GroupID::Button,  8, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB));
      assign(jp.hid, HID::Joypad::GroupID::Button,  9, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB));
      assign(jp.hid, HID::Joypad::GroupID::Button, 10, (bool)(state.Gamepad.wButtons & XINPUT_GAMEPAD_GUIDE));

      devices.push_back(jp.hid);
    }
  }

  auto rumble(u64 id, u16 strong, u16 weak) -> bool {
    for(auto& jp : joypads) {
      if(jp.hid->id() != id) continue;

      XINPUT_VIBRATION vibration;
      memset(&vibration, 0, sizeof(XINPUT_VIBRATION));
      vibration.wLeftMotorSpeed  = strong;  //low-frequency motor  (0 = off, 65535 = max)
      vibration.wRightMotorSpeed = weak;    //high-frequency motor (0 = off, 65535 = max)
      XInputSetState(jp.id, &vibration);
      return true;
    }

    return false;
  }

  auto initialize() -> bool {
    if(!libxinput) libxinput = LoadLibraryA("xinput1_3.dll");
    if(!libxinput) libxinput = LoadLibraryA("xinput1_4.dll");
    if(!libxinput) return false;

    //XInputGetStateEx is an undocumented function; but is required to get the state of the guide button
    //if for some reason it is not available, fall back on XInputGetState, which takes the same parameters
    XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetStateEx);
    XInputSetState = (pXInputSetState)GetProcAddress(libxinput, oXInputSetState);
    if(!XInputGetStateEx) XInputGetStateEx = (pXInputGetStateEx)GetProcAddress(libxinput, oXInputGetState);
    if(!XInputGetStateEx || !XInputSetState) return terminate(), false;

    //XInput supports a maximum of four controllers
    //add all four to devices list now. If they are not connected, they will not show up in poll() results
    for(u32 id : range(4)) {
      Joypad jp;
      jp.id = id;
      jp.hid->setVendorID(0x045e);
      jp.hid->setProductID(0x028e);
      jp.hid->setPathID(id);
      jp.hid->setRumble(true);

      jp.hid->axes().append("LeftThumbX");
      jp.hid->axes().append("LeftThumbY");
      jp.hid->axes().append("RightThumbX");
      jp.hid->axes().append("RightThumbY");

      jp.hid->hats().append("HatX");
      jp.hid->hats().append("HatY");

      jp.hid->triggers().append("LeftTrigger");
      jp.hid->triggers().append("RightTrigger");

      jp.hid->buttons().append("A");
      jp.hid->buttons().append("B");
      jp.hid->buttons().append("X");
      jp.hid->buttons().append("Y");
      jp.hid->buttons().append("Back");
      jp.hid->buttons().append("Start");
      jp.hid->buttons().append("LeftShoulder");
      jp.hid->buttons().append("RightShoulder");
      jp.hid->buttons().append("LeftThumb");
      jp.hid->buttons().append("RightThumb");
      jp.hid->buttons().append("Guide");

      joypads.push_back(jp);
    }

    return true;
  }

  auto terminate() -> void {
    if(!libxinput) return;

    FreeLibrary(libxinput);
    libxinput = nullptr;
  }
};