1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2010-2014 - Hans-Kristian Arntzen
3  *  Copyright (C) 2011-2017 - Daniel De Matteis
4  *
5  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
6  *  of the GNU General Public License as published by the Free Software Found-
7  *  ation, either version 3 of the License, or (at your option) any later version.
8  *
9  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11  *  PURPOSE.  See the GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License along with RetroArch.
14  *  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <stdint.h>
18 
19 #include "../../config.def.h"
20 
21 #include "../input_driver.h"
22 #include "../../tasks/tasks_internal.h"
23 
24 typedef struct
25 {
26    HANDLE id;
27    XINPUT_STATE xstate;
28    bool connected;
29 } xinput_joypad_state;
30 
31 /* TODO/FIXME - static globals */
32 static xinput_joypad_state g_xinput_states[DEFAULT_MAX_PADS];
33 
xdk_joypad_name(unsigned pad)34 static const char *xdk_joypad_name(unsigned pad)
35 {
36    static const char* const XBOX_CONTROLLER_NAMES[4] =
37    {
38       "XInput Controller (User 1)",
39       "XInput Controller (User 2)",
40       "XInput Controller (User 3)",
41       "XInput Controller (User 4)"
42    };
43    return XBOX_CONTROLLER_NAMES[pad];
44 }
45 
xdk_joypad_autodetect_add(unsigned autoconf_pad)46 static void xdk_joypad_autodetect_add(unsigned autoconf_pad)
47 {
48    input_autoconfigure_connect(
49          xdk_joypad_name(autoconf_pad),
50          NULL,
51          xdk_joypad.ident,
52          autoconf_pad,
53          0,
54          0);
55 }
56 
xdk_joypad_init(void * data)57 static void *xdk_joypad_init(void *data)
58 {
59    XInitDevices(0, NULL);
60    return (void*)-1;
61 }
62 
xdk_joypad_button_state(XINPUT_GAMEPAD * pad,uint16_t btn_word,unsigned port,uint16_t joykey)63 static int16_t xdk_joypad_button_state(
64       XINPUT_GAMEPAD *pad,
65       uint16_t btn_word,
66       unsigned port, uint16_t joykey)
67 {
68    unsigned hat_dir  = GET_HAT_DIR(joykey);
69 
70    if (hat_dir)
71    {
72       switch (hat_dir)
73       {
74          case HAT_UP_MASK:
75             return (btn_word & XINPUT_GAMEPAD_DPAD_UP);
76          case HAT_DOWN_MASK:
77             return (btn_word & XINPUT_GAMEPAD_DPAD_DOWN);
78          case HAT_LEFT_MASK:
79             return (btn_word & XINPUT_GAMEPAD_DPAD_LEFT);
80          case HAT_RIGHT_MASK:
81             return (btn_word & XINPUT_GAMEPAD_DPAD_RIGHT);
82          default:
83             break;
84       }
85       /* hat requested and no hat button down */
86    }
87    else
88    {
89       switch (joykey)
90       {
91          case RETRO_DEVICE_ID_JOYPAD_A:
92             return (pad->bAnalogButtons[XINPUT_GAMEPAD_B] > XINPUT_GAMEPAD_MAX_CROSSTALK);
93          case RETRO_DEVICE_ID_JOYPAD_B:
94             return (pad->bAnalogButtons[XINPUT_GAMEPAD_A] > XINPUT_GAMEPAD_MAX_CROSSTALK);
95          case RETRO_DEVICE_ID_JOYPAD_Y:
96             return (pad->bAnalogButtons[XINPUT_GAMEPAD_X] > XINPUT_GAMEPAD_MAX_CROSSTALK);
97          case RETRO_DEVICE_ID_JOYPAD_X:
98             return (pad->bAnalogButtons[XINPUT_GAMEPAD_Y] > XINPUT_GAMEPAD_MAX_CROSSTALK)
99          case RETRO_DEVICE_ID_JOYPAD_START:
100                return (pad->wButtons & XINPUT_GAMEPAD_START);
101          case RETRO_DEVICE_ID_JOYPAD_SELECT:
102                return (pad->wButtons & XINPUT_GAMEPAD_BACK);
103          case RETRO_DEVICE_ID_JOYPAD_L3:
104                return (pad->wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
105          case RETRO_DEVICE_ID_JOYPAD_R3:
106                return (pad->wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
107          case RETRO_DEVICE_ID_JOYPAD_L2:
108                return (pad->bAnalogButtons[XINPUT_GAMEPAD_WHITE] > XINPUT_GAMEPAD_MAX_CROSSTALK);
109          case RETRO_DEVICE_ID_JOYPAD_R2:
110                return (pad->bAnalogButtons[XINPUT_GAMEPAD_BLACK] > XINPUT_GAMEPAD_MAX_CROSSTALK);
111          case RETRO_DEVICE_ID_JOYPAD_L:
112                return (pad->bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] > XINPUT_GAMEPAD_MAX_CROSSTALK);
113          case RETRO_DEVICE_ID_JOYPAD_R:
114                return (pad->bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > XINPUT_GAMEPAD_MAX_CROSSTALK);
115          default:
116                break;
117       }
118    }
119    return 0;
120 }
121 
xdk_joypad_button(unsigned port,uint16_t joykey)122 static int16_t xdk_joypad_button(unsigned port, uint16_t joykey)
123 {
124    uint16_t btn_word   = 0;
125    XINPUT_GAMEPAD *pad = NULL;
126    if (port >= DEFAULT_MAX_PADS)
127       return 0;
128    pad                 = &(g_xinput_states[port].xstate.Gamepad);
129    btn_word            = pad->wButtons;
130    return xdk_joypad_button_state(pad, btn_word, port, joykey);
131 }
132 
xdk_joypad_axis_state(XINPUT_GAMEPAD * pad,unsigned port,uint32_t joyaxis)133 static int16_t xdk_joypad_axis_state(XINPUT_GAMEPAD *pad,
134       unsigned port, uint32_t joyaxis)
135 {
136    int val             = 0;
137    int axis            = -1;
138    bool is_neg         = false;
139    bool is_pos         = false;
140 
141    if (AXIS_NEG_GET(joyaxis) <= 3)
142    {
143       axis             = AXIS_NEG_GET(joyaxis);
144       is_neg           = true;
145    }
146    else if (AXIS_POS_GET(joyaxis) <= 5)
147    {
148       axis             = AXIS_POS_GET(joyaxis);
149       is_pos           = true;
150    }
151    else
152       return 0;
153 
154    switch (axis)
155    {
156       case 0:
157          val = pad->sThumbLX;
158          break;
159       case 1:
160          val = pad->sThumbLY;
161          break;
162       case 2:
163          val = pad->sThumbRX;
164          break;
165       case 3:
166          val = pad->sThumbRY;
167          break;
168    }
169 
170    if (is_neg && val > 0)
171       return 0;
172    else if (is_pos && val < 0)
173       return 0;
174    /* Clamp to avoid warnings */
175    else if (val == -32768)
176       return -32767;
177    return val;
178 }
179 
xdk_joypad_axis(unsigned port,uint32_t joyaxis)180 static int16_t xdk_joypad_axis(unsigned port, uint32_t joyaxis)
181 {
182    XINPUT_GAMEPAD *pad = &(g_xinput_states[port].xstate.Gamepad);
183    if (port >= DEFAULT_MAX_PADS)
184       return 0;
185    return xdk_joypad_axis_state(pad, port, joyaxis);
186 }
187 
xdk_joypad_state(rarch_joypad_info_t * joypad_info,const struct retro_keybind * binds,unsigned port)188 static int16_t xdk_joypad_state(
189       rarch_joypad_info_t *joypad_info,
190       const struct retro_keybind *binds,
191       unsigned port)
192 {
193    unsigned i;
194    int16_t ret         = 0;
195    XINPUT_GAMEPAD *pad = NULL;
196    uint16_t btn_word   = 0;
197    uint16_t port_idx   = joypad_info->joy_idx;
198 
199    if (port_idx >= DEFAULT_MAX_PADS)
200       return 0;
201 
202    pad                 = &(g_xinput_states[port_idx].xstate.Gamepad);
203    btn_word            = pad->wButtons;
204 
205    for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
206    {
207       /* Auto-binds are per joypad, not per user. */
208       const uint64_t joykey  = (binds[i].joykey != NO_BTN)
209          ? binds[i].joykey  : joypad_info->auto_binds[i].joykey;
210       const uint32_t joyaxis = (binds[i].joyaxis != AXIS_NONE)
211          ? binds[i].joyaxis : joypad_info->auto_binds[i].joyaxis;
212       if (
213                (uint16_t)joykey != NO_BTN
214             && xdk_joypad_button_state(
215                pad, btn_word, port_idx, (uint16_t)joykey))
216          ret |= ( 1 << i);
217       else if (joyaxis != AXIS_NONE &&
218             ((float)abs(xdk_joypad_axis_state(pad, port_idx, joyaxis))
219              / 0x8000) > joypad_info->axis_threshold)
220          ret |= (1 << i);
221    }
222 
223    return ret;
224 }
225 
xdk_joypad_poll(void)226 static void xdk_joypad_poll(void)
227 {
228    unsigned port;
229    DWORD dwInsertions, dwRemovals;
230 
231 #ifdef __cplusplus
232    XGetDeviceChanges(XDEVICE_TYPE_GAMEPAD,
233          reinterpret_cast<PDWORD>(&dwInsertions),
234          reinterpret_cast<PDWORD>(&dwRemovals));
235 #else
236    XGetDeviceChanges(XDEVICE_TYPE_GAMEPAD,
237          (PDWORD)&dwInsertions,
238          (PDWORD)&dwRemovals);
239 #endif
240 
241    for (port = 0; port < DEFAULT_MAX_PADS; port++)
242    {
243       bool device_removed    = false;
244       bool device_inserted   = false;
245 
246       /* handle inserted devices. */
247       /* handle removed devices. */
248       if (dwRemovals & (1 << port))
249          device_removed = true;
250       if (dwInsertions & (1 << port))
251          device_inserted = true;
252 
253       if (device_removed)
254       {
255          /* if the controller was removed after
256           * XGetDeviceChanges but before
257           * XInputOpen, the device handle will be NULL. */
258          if (g_xinput_states[port].id)
259             XInputClose(g_xinput_states[port].id);
260 
261          g_xinput_states[port].id  = 0;
262 
263          input_autoconfigure_disconnect(port, xdk_joypad.ident);
264       }
265 
266       if (device_inserted)
267       {
268          XINPUT_POLLING_PARAMETERS m_pollingParameters;
269 
270          m_pollingParameters.fAutoPoll       = FALSE;
271          m_pollingParameters.fInterruptOut   = TRUE;
272          m_pollingParameters.bInputInterval  = 8;
273          m_pollingParameters.bOutputInterval = 8;
274 
275          g_xinput_states[port].id            = XInputOpen(
276                XDEVICE_TYPE_GAMEPAD, port,
277                XDEVICE_NO_SLOT, &m_pollingParameters);
278 
279          xdk_joypad_autodetect_add(port);
280       }
281 
282       if (!g_xinput_states[port].id)
283          continue;
284 
285       /* if the controller is removed after
286        * XGetDeviceChanges but before XInputOpen,
287        * the device handle will be NULL. */
288       if (XInputPoll(g_xinput_states[port].id) != ERROR_SUCCESS)
289          continue;
290 
291       memset(&g_xinput_states[port], 0, sizeof(xinput_joypad_state));
292 
293       g_xinput_states[port].connected = !
294       (XInputGetState(
295          g_xinput_states[port].id
296          , &g_xinput_states[port].xstate) == ERROR_DEVICE_NOT_CONNECTED);
297    }
298 }
299 
xdk_joypad_query_pad(unsigned pad)300 static bool xdk_joypad_query_pad(unsigned pad)
301 {
302    return pad < MAX_USERS && g_xinput_states[pad].connected;
303 }
304 
xdk_joypad_destroy(void)305 static void xdk_joypad_destroy(void)
306 {
307    unsigned i;
308 
309    for (i = 0; i < DEFAULT_MAX_PADS; i++)
310    {
311       memset(&g_xinput_states[i], 0, sizeof(xinput_joypad_state));
312       if (g_xinput_states[i].id)
313          XInputClose(g_xinput_states[i].id);
314       g_xinput_states[i].id  = 0;
315    }
316 }
317 
318 input_device_driver_t xdk_joypad = {
319    xdk_joypad_init,
320    xdk_joypad_query_pad,
321    xdk_joypad_destroy,
322    xdk_joypad_button,
323    xdk_joypad_state,
324    NULL,
325    xdk_joypad_axis,
326    xdk_joypad_poll,
327    NULL,
328    xdk_joypad_name,
329    "xdk",
330 };
331