1 /**
2 * @file
3 */
4
5 /*
6 Copyright (C) 2002-2007 ioQuake3 team.
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18 See the GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 */
25
26 #include "cl_joystick.h"
27 #include "../client.h"
28 #include "cl_input.h"
29 #include "../ui/ui_main.h"
30 #include "../ui/ui_nodes.h"
31 #include "../ui/node/ui_node_abstractoption.h"
32
33 static SDL_Joystick* stick = nullptr;
34 static cvar_t* in_joystick;
35 static cvar_t* in_joystickNo;
36 static cvar_t* in_joystickThreshold;
37 static cvar_t* in_joystickSpeed;
38
39 #if SDL_VERSION_ATLEAST(2,0,0)
40 #define SDL_JoystickName SDL_JoystickNameForIndex
41 #endif
42
43 static struct {
44 bool buttons[16];
45 unsigned int oldaxes;
46 unsigned int oldhats;
47 } stick_state;
48
49 /* We translate axes movement into keypresses */
50 static const int joy_keys[16] = {
51 K_LEFTARROW, K_RIGHTARROW,
52 K_UPARROW, K_DOWNARROW,
53 K_JOY16, K_JOY17,
54 K_JOY18, K_JOY19,
55 K_JOY20, K_JOY21,
56 K_JOY22, K_JOY23,
57
58 K_JOY24, K_JOY25,
59 K_JOY26, K_JOY27
60 };
61
62 /* translate hat events into keypresses
63 * the 4 highest buttons are used for the first hat ... */
64 static const int hat_keys[16] = {
65 K_JOY29, K_JOY30,
66 K_JOY31, K_JOY32,
67 K_JOY25, K_JOY26,
68 K_JOY27, K_JOY28,
69 K_JOY21, K_JOY22,
70 K_JOY23, K_JOY24,
71 K_JOY17, K_JOY18,
72 K_JOY19, K_JOY20
73 };
74
IN_JoystickMove(void)75 void IN_JoystickMove (void)
76 {
77 bool joy_pressed[lengthof(joy_keys)];
78 unsigned int axes = 0;
79 unsigned int hats = 0;
80 int total = 0;
81 int i = 0;
82
83 /* check whether a user has changed the joystick number */
84 if (in_joystickNo->modified)
85 IN_StartupJoystick();
86 /* check whether joysticks are disabled */
87 if (!in_joystick->integer)
88 return;
89
90 if (!stick)
91 return;
92
93 SDL_JoystickUpdate();
94
95 OBJZERO(joy_pressed);
96
97 /* update the ball state */
98 total = SDL_JoystickNumBalls(stick);
99 if (total > 0) {
100 int balldx = 0;
101 int balldy = 0;
102 for (i = 0; i < total; i++) {
103 int dx = 0;
104 int dy = 0;
105 SDL_JoystickGetBall(stick, i, &dx, &dy);
106 balldx += dx;
107 balldy += dy;
108 }
109 if (balldx || balldy) {
110 mousePosX = balldx / viddef.rx;
111 mousePosY = balldy / viddef.ry;
112 }
113 }
114
115 /* now query the stick buttons... */
116 total = SDL_JoystickNumButtons(stick);
117 if (total > 0) {
118 if (total > lengthof(stick_state.buttons))
119 total = lengthof(stick_state.buttons);
120 for (i = 0; i < total; i++) {
121 const bool pressed = (SDL_JoystickGetButton(stick, i) != 0);
122 if (pressed != stick_state.buttons[i]) {
123 IN_EventEnqueue(K_JOY1 + i, 0, pressed);
124 stick_state.buttons[i] = pressed;
125 }
126 }
127 }
128
129 /* look at the hats... */
130 total = SDL_JoystickNumHats(stick);
131 if (total > 0) {
132 if (total > 4)
133 total = 4;
134 for (i = 0; i < total; i++)
135 ((Uint8 *)&hats)[i] = SDL_JoystickGetHat(stick, i);
136 }
137
138 /* update hat state */
139 if (hats != stick_state.oldhats) {
140 for (i = 0; i < 4; i++) {
141 if (((Uint8 *)&hats)[i] != ((Uint8 *)&stick_state.oldhats)[i]) {
142 /* release event */
143 switch (((Uint8 *)&stick_state.oldhats)[i]) {
144 case SDL_HAT_UP:
145 IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
146 break;
147 case SDL_HAT_RIGHT:
148 IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
149 break;
150 case SDL_HAT_DOWN:
151 IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
152 break;
153 case SDL_HAT_LEFT:
154 IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
155 break;
156 case SDL_HAT_RIGHTUP:
157 IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
158 IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
159 break;
160 case SDL_HAT_RIGHTDOWN:
161 IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
162 IN_EventEnqueue(hat_keys[4 * i + 1], 0, false);
163 break;
164 case SDL_HAT_LEFTUP:
165 IN_EventEnqueue(hat_keys[4 * i + 0], 0, false);
166 IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
167 break;
168 case SDL_HAT_LEFTDOWN:
169 IN_EventEnqueue(hat_keys[4 * i + 2], 0, false);
170 IN_EventEnqueue(hat_keys[4 * i + 3], 0, false);
171 break;
172 default:
173 break;
174 }
175 /* press event */
176 switch (((Uint8 *)&hats)[i]) {
177 case SDL_HAT_UP:
178 IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
179 break;
180 case SDL_HAT_RIGHT:
181 IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
182 break;
183 case SDL_HAT_DOWN:
184 IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
185 break;
186 case SDL_HAT_LEFT:
187 IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
188 break;
189 case SDL_HAT_RIGHTUP:
190 IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
191 IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
192 break;
193 case SDL_HAT_RIGHTDOWN:
194 IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
195 IN_EventEnqueue(hat_keys[4 * i + 1], 0, true);
196 break;
197 case SDL_HAT_LEFTUP:
198 IN_EventEnqueue(hat_keys[4 * i + 0], 0, true);
199 IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
200 break;
201 case SDL_HAT_LEFTDOWN:
202 IN_EventEnqueue(hat_keys[4 * i + 2], 0, true);
203 IN_EventEnqueue(hat_keys[4 * i + 3], 0, true);
204 break;
205 default:
206 break;
207 }
208 }
209 }
210 }
211
212 /* save hat state */
213 stick_state.oldhats = hats;
214
215 /* finally, look at the axes... */
216 total = SDL_JoystickNumAxes(stick);
217 if (total >= 2) {
218 /* the first two axes are used for the cursor movement */
219 for (i = 0; i < 2; i++) {
220 const Sint16 axis = SDL_JoystickGetAxis(stick, i);
221 const float velocity = ((float) axis) / 32767.0f;
222 if (velocity > -in_joystickThreshold->value && velocity < in_joystickThreshold->value)
223 continue;
224
225 if (i & 1) {
226 mousePosY += in_joystickSpeed->value * velocity;
227 if (mousePosY > (int)viddef.context.height)
228 mousePosY = (int)viddef.context.height;
229 else if (mousePosY < 0)
230 mousePosY = 0;
231 } else {
232 mousePosX += in_joystickSpeed->value * velocity;
233 if (mousePosX > (int)viddef.context.width)
234 mousePosX = (int)viddef.context.width;
235 else if (mousePosX < 0)
236 mousePosX = 0;
237 }
238 }
239 }
240
241
242 if (total > 2) {
243 if (total > 16)
244 total = 16;
245 /* every axis except the first two can be normally bound to an action */
246 for (i = 2; i < total; i++) {
247 const Sint16 axis = SDL_JoystickGetAxis(stick, i);
248 const float f = ((float) axis) / 32767.0f;
249 if (f < -in_joystickThreshold->value) {
250 axes |= (1 << (i * 2));
251 } else if (f > in_joystickThreshold->value) {
252 axes |= (1 << ((i * 2) + 1));
253 }
254 }
255 }
256
257
258 /* Time to update axes state based on old vs. new. */
259 if (axes != stick_state.oldaxes) {
260 for (i = 2; i < 16; i++) {
261 if ((axes & (1 << i)) && !(stick_state.oldaxes & (1 << i)))
262 IN_EventEnqueue(joy_keys[i], 0, true);
263
264 if (!(axes & (1 << i)) && (stick_state.oldaxes & (1 << i)))
265 IN_EventEnqueue(joy_keys[i], 0, false);
266 }
267 }
268
269 /* Save for future generations. */
270 stick_state.oldaxes = axes;
271 }
272
273 /**
274 * @brief Adds joysticks to the options menu
275 */
IN_JoystickInitMenu(void)276 void IN_JoystickInitMenu (void)
277 {
278 uiNode_t* joystickOptions = nullptr;
279 const int total = SDL_NumJoysticks();
280
281 if (total == 0) {
282 UI_AddOption(&joystickOptions, "", _("None"), "0");
283 } else {
284 for (int i = 0; i < total; i++)
285 UI_AddOption(&joystickOptions, "", SDL_JoystickName(i), va("%i", i));
286 }
287 UI_RegisterOption(OPTION_JOYSTICKS, joystickOptions);
288 }
289
290 /**
291 * @brief Init available joysticks
292 */
IN_StartupJoystick(void)293 void IN_StartupJoystick (void)
294 {
295 int i = 0;
296 int total = 0;
297
298 in_joystick = Cvar_Get("in_joystick", "0", CVAR_ARCHIVE, "Activate or deactivate the use of a joystick");
299 in_joystickNo = Cvar_Get("in_joystickNo", "0", CVAR_ARCHIVE, "Joystick to use - 0 is the first - 1 is the second ...");
300 in_joystickThreshold = Cvar_Get("in_joystickThreshold", "0.05", CVAR_ARCHIVE, "The threshold for the joystick axes");
301 in_joystickSpeed = Cvar_Get("in_joystickSpeed", "20", CVAR_ARCHIVE, "The joystick speed for the cursor");
302
303 if (stick != nullptr) {
304 Com_Printf("... closing already initialized joystick\n");
305 SDL_JoystickClose(stick);
306 }
307
308 stick = nullptr;
309 OBJZERO(stick_state);
310
311 if (!SDL_WasInit(SDL_INIT_JOYSTICK)) {
312 Com_DPrintf(DEBUG_CLIENT, "Calling SDL_Init(SDL_INIT_JOYSTICK)...\n");
313 if (SDL_Init(SDL_INIT_JOYSTICK) == -1) {
314 Com_DPrintf(DEBUG_CLIENT, "SDL_Init(SDL_INIT_JOYSTICK) failed: %s\n", SDL_GetError());
315 return;
316 }
317 Com_DPrintf(DEBUG_CLIENT, "SDL_Init(SDL_INIT_JOYSTICK) passed.\n");
318 }
319
320 total = SDL_NumJoysticks();
321 Com_Printf("%d possible joysticks\n", total);
322 for (i = 0; i < total; i++)
323 Com_DPrintf(DEBUG_CLIENT, "[%d] %s\n", i, SDL_JoystickName(i));
324
325 if (in_joystickNo->integer < 0 || in_joystickNo->integer >= total)
326 Cvar_Set("in_joystickNo", "0");
327 in_joystickNo->modified = false;
328
329 stick = SDL_JoystickOpen(in_joystickNo->integer);
330
331 if (stick == nullptr) {
332 Com_Printf("no joystick found.\n");
333 return;
334 }
335
336 Com_Printf("joystick %d opened - set cvar in_joystickNo to change this\n", in_joystickNo->integer);
337 Com_Printf("... name: %s\n", SDL_JoystickName(in_joystickNo->integer));
338 Com_Printf("... axes: %d\n", SDL_JoystickNumAxes(stick));
339 Com_Printf("... hats: %d\n", SDL_JoystickNumHats(stick));
340 Com_Printf("... buttons: %d\n", SDL_JoystickNumButtons(stick));
341 Com_Printf("... balls: %d\n", SDL_JoystickNumBalls(stick));
342
343 SDL_JoystickEventState(SDL_QUERY);
344 }
345