File: xinput.cpp

package info (click to toggle)
higan 094-5
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 9,780 kB
  • ctags: 15,643
  • sloc: cpp: 103,963; ansic: 659; makefile: 531; sh: 25
file content (162 lines) | stat: -rwxr-xr-x 6,899 bytes parent folder | download | duplicates (5)
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
#ifndef RUBY_INPUT_JOYPAD_XINPUT
#define RUBY_INPUT_JOYPAD_XINPUT

//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

namespace ruby {

struct InputJoypadXInput {
  HMODULE libxinput = nullptr;
  pXInputGetStateEx XInputGetStateEx = nullptr;
  pXInputSetState XInputSetState = nullptr;

  struct Joypad {
    HID::Joypad hid;
    unsigned id;
  };
  vector<Joypad> joypads;

  void assign(HID::Joypad& hid, unsigned groupID, unsigned inputID, int16_t value) {
    auto& group = hid.group[groupID];
    if(group.input[inputID].value == value) return;
    if(input.onChange) input.onChange(hid, groupID, inputID, group.input[inputID].value, value);
    group.input[inputID].value = value;
  }

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

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

      int16_t hatX = 0;
      int16_t hatY = 0;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP   ) hatY = -32768;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) hatY = +32767;
      if(state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) hatX = -32768;
      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 up to down from (0 to 255) to (-32768 to +32767)
      uint16_t triggerL = state.Gamepad.bLeftTrigger;
      uint16_t triggerR = state.Gamepad.bRightTrigger;
      triggerL = triggerL << 8 | triggerL << 0;
      triggerR = triggerR << 8 | triggerR << 0;

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

      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.append(&jp.hid);
    }
  }

  bool rumble(uint64_t id, bool enable) {
    for(auto& jp : joypads) {
      if(jp.hid.id != id) continue;

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

    return false;
  }

  bool init() {
    if(!libxinput) libxinput = LoadLibraryA("xinput1_3.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 term(), 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(unsigned id = 0; id < 4; id++) {
      Joypad jp;
      jp.id = id;
      jp.hid.id = (uint64_t)(1 + id) << 32 | 0x045e << 16 | 0x028e << 0;  //Xbox 360 Player# + VendorID + ProductID

      jp.hid.axis().append({"LeftThumbX"});
      jp.hid.axis().append({"LeftThumbY"});
      jp.hid.axis().append({"RightThumbX"});
      jp.hid.axis().append({"RightThumbY"});

      jp.hid.hat().append({"HatX"});
      jp.hid.hat().append({"HatY"});

      jp.hid.trigger().append({"LeftTrigger"});
      jp.hid.trigger().append({"RightTrigger"});

      jp.hid.button().append({"A"});
      jp.hid.button().append({"B"});
      jp.hid.button().append({"X"});
      jp.hid.button().append({"Y"});
      jp.hid.button().append({"Back"});
      jp.hid.button().append({"Start"});
      jp.hid.button().append({"LeftShoulder"});
      jp.hid.button().append({"RightShoulder"});
      jp.hid.button().append({"LeftThumb"});
      jp.hid.button().append({"RightThumb"});
      jp.hid.button().append({"Guide"});

      joypads.append(jp);
    }

    return true;
  }

  void term() {
    if(!libxinput) return;

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

}

#endif