1 /**
2  * @file
3  * @brief Client input handling - bindable commands.
4  * @note Continuous button event tracking is complicated by the fact that two different
5  * input sources (say, mouse button 1 and the control key) can both press the
6  * same button, but the button should only be released when both of the
7  * pressing key have been released.
8  *
9  * When a key event issues a button command (+forward, +attack, etc), it appends
10  * its key number as a parameter to the command so it can be matched up with
11  * the release.
12  *
13  * Key_Event(unsigned int key, unsigned short unicode, bool down, unsigned time);
14  *
15  *  +mlook src time
16  */
17 
18 /*
19 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
20 
21 Original file from Quake 2 v3.21: quake2-2.31/client/cl_input.c
22 Copyright (C) 1997-2001 Id Software, Inc.
23 
24 This program is free software; you can redistribute it and/or
25 modify it under the terms of the GNU General Public License
26 as published by the Free Software Foundation; either version 2
27 of the License, or (at your option) any later version.
28 
29 This program is distributed in the hope that it will be useful,
30 but WITHOUT ANY WARRANTY; without even the implied warranty of
31 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
32 
33 See the GNU General Public License for more details.
34 
35 You should have received a copy of the GNU General Public License
36 along with this program; if not, write to the Free Software
37 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
38 
39 */
40 
41 #include "../client.h"
42 #include "cl_input.h"
43 #include "cl_keys.h"
44 #include "cl_joystick.h"
45 #include "../battlescape/cl_localentity.h"
46 #include "../battlescape/cl_hud.h"
47 #include "../cl_console.h"
48 #include "../cl_screen.h"
49 #include "../battlescape/cl_actor.h"
50 #include "../battlescape/cl_view.h"
51 #include "../battlescape/cl_parse.h"
52 #include "../ui/ui_main.h"
53 #include "../ui/ui_input.h"
54 #include "../ui/node/ui_node_abstractnode.h"
55 #include "../../shared/utf8.h"
56 
57 #include "../../common/tracing.h"
58 #include "../renderer/r_misc.h"
59 
60 /* power of two please */
61 #define MAX_KEYQ 64
62 
63 #if SDL_VERSION_ATLEAST(2,0,0)
64 #define SDL_keysym SDL_Keysym
65 #endif
66 
67 static struct {
68 	unsigned int key;
69 	unsigned short unicode;
70 	int down;
71 } keyq[MAX_KEYQ];
72 
73 static int keyq_head = 0;
74 static int keyq_tail = 0;
75 
76 static cvar_t* in_debug;
77 cvar_t* cl_isometric;
78 
79 mouseSpace_t mouseSpace;
80 int mousePosX, mousePosY;
81 static int oldMousePosX, oldMousePosY;
82 
83 static int battlescapeMouseDraggingX;
84 static int battlescapeMouseDraggingY;
85 static bool battlescapeMouseDraggingPossible, battlescapeMouseDraggingActive;
86 enum {
87 	BATTLESCAPE_MOUSE_DRAGGING_TRIGGER_X = VID_NORM_WIDTH / 10,
88 	BATTLESCAPE_MOUSE_DRAGGING_TRIGGER_Y = VID_NORM_HEIGHT / 10
89 };
90 
91 /*
92 ===============================================================================
93 KEY BUTTONS
94 ===============================================================================
95 */
96 
97 typedef struct {
98 	int down[2];				/**< key nums holding it down */
99 	unsigned downtime;			/**< msec timestamp when the key was pressed down */
100 	unsigned msec;				/**< downtime for this key in msec (delta between pressed and released) */
101 	int state;					/**< 1 if down, 0 if not down */
102 } kbutton_t;
103 
104 static kbutton_t in_turnleft, in_turnright, in_shiftleft, in_shiftright;
105 static kbutton_t in_shiftup, in_shiftdown;
106 static kbutton_t in_zoomin, in_zoomout;
107 static kbutton_t in_turnup, in_turndown;
108 static kbutton_t in_pantilt;
109 
110 /**
111  * @brief Handles the catch of a @c kbutton_t state
112  * @sa IN_KeyUp
113  * @sa CL_GetKeyMouseState
114  * @note Called from console callbacks with two parameters, the
115  * key and the milliseconds when the key was released
116  */
IN_KeyDown(kbutton_t * b)117 static void IN_KeyDown (kbutton_t* b)
118 {
119 	int k;
120 	const char* c = Cmd_Argv(1);
121 
122 	if (c[0])
123 		k = atoi(c);
124 	else
125 		/* typed manually at the console for continuous down */
126 		k = -1;
127 
128 	/* repeating key */
129 	if (k == b->down[0] || k == b->down[1])
130 		return;
131 
132 	if (!b->down[0])
133 		b->down[0] = k;
134 	else if (!b->down[1])
135 		b->down[1] = k;
136 	else {
137 		Com_Printf("Three keys down for a button!\n");
138 		return;
139 	}
140 
141 	/* still down */
142 	if (b->state)
143 		return;
144 
145 	/* save timestamp */
146 	c = Cmd_Argv(2);
147 	b->downtime = atoi(c);
148 	if (!b->downtime)
149 		b->downtime = CL_Milliseconds() - 100;
150 
151 	/* down */
152 	b->state = 1;
153 }
154 
155 /**
156  * @brief Handles the release of a @c kbutton_t state
157  * @sa IN_KeyDown
158  * @sa CL_GetKeyMouseState
159  * @note Called from console callbacks with two parameters, the
160  * key and the milliseconds when the key was released
161  * @param[in,out] b the button state to
162  */
IN_KeyUp(kbutton_t * b)163 static void IN_KeyUp (kbutton_t* b)
164 {
165 	int k;
166 	unsigned uptime;
167 	const char* c = Cmd_Argv(1);
168 
169 	if (c[0])
170 		k = atoi(c);
171 	/* typed manually at the console, assume for unsticking, so clear all */
172 	else {
173 		b->down[0] = b->down[1] = 0;
174 		return;
175 	}
176 
177 	if (b->down[0] == k)
178 		b->down[0] = 0;
179 	else if (b->down[1] == k)
180 		b->down[1] = 0;
181 	/* key up without corresponding down (menu pass through) */
182 	else
183 		return;
184 
185 	/* some other key is still holding it down */
186 	if (b->down[0] || b->down[1])
187 		return;
188 
189 	/* still up (this should not happen) */
190 	if (!b->state)
191 		return;
192 
193 	/* save timestamp */
194 	c = Cmd_Argv(2);
195 	uptime = atoi(c);
196 	if (uptime)
197 		b->msec = uptime - b->downtime;
198 	else
199 		b->msec = 10;
200 
201 	/* now up */
202 	b->state = 0;
203 }
204 
IN_TurnLeftDown_f(void)205 static void IN_TurnLeftDown_f (void)
206 {
207 	IN_KeyDown(&in_turnleft);
208 }
IN_TurnLeftUp_f(void)209 static void IN_TurnLeftUp_f (void)
210 {
211 	IN_KeyUp(&in_turnleft);
212 }
IN_TurnRightDown_f(void)213 static void IN_TurnRightDown_f (void)
214 {
215 	IN_KeyDown(&in_turnright);
216 }
IN_TurnRightUp_f(void)217 static void IN_TurnRightUp_f (void)
218 {
219 	IN_KeyUp(&in_turnright);
220 }
IN_TurnUpDown_f(void)221 static void IN_TurnUpDown_f (void)
222 {
223 	IN_KeyDown(&in_turnup);
224 }
IN_TurnUpUp_f(void)225 static void IN_TurnUpUp_f (void)
226 {
227 	IN_KeyUp(&in_turnup);
228 }
IN_TurnDownDown_f(void)229 static void IN_TurnDownDown_f (void)
230 {
231 	IN_KeyDown(&in_turndown);
232 }
IN_TurnDownUp_f(void)233 static void IN_TurnDownUp_f (void)
234 {
235 	IN_KeyUp(&in_turndown);
236 }
IN_PanTiltDown_f(void)237 static void IN_PanTiltDown_f (void)
238 {
239 	if (IN_GetMouseSpace() != MS_WORLD)
240 		return;
241 	IN_KeyDown(&in_pantilt);
242 }
IN_PanTiltUp_f(void)243 static void IN_PanTiltUp_f (void)
244 {
245 	IN_KeyUp(&in_pantilt);
246 }
IN_ShiftLeftDown_f(void)247 static void IN_ShiftLeftDown_f (void)
248 {
249 	IN_KeyDown(&in_shiftleft);
250 }
IN_ShiftLeftUp_f(void)251 static void IN_ShiftLeftUp_f (void)
252 {
253 	IN_KeyUp(&in_shiftleft);
254 }
IN_ShiftLeftUpDown_f(void)255 static void IN_ShiftLeftUpDown_f (void)
256 {
257 	IN_KeyDown(&in_shiftleft);
258 	IN_KeyDown(&in_shiftup);
259 }
IN_ShiftLeftUpUp_f(void)260 static void IN_ShiftLeftUpUp_f (void)
261 {
262 	IN_KeyUp(&in_shiftleft);
263 	IN_KeyUp(&in_shiftup);
264 }
IN_ShiftLeftDownDown_f(void)265 static void IN_ShiftLeftDownDown_f (void)
266 {
267 	IN_KeyDown(&in_shiftleft);
268 	IN_KeyDown(&in_shiftdown);
269 }
IN_ShiftLeftDownUp_f(void)270 static void IN_ShiftLeftDownUp_f (void)
271 {
272 	IN_KeyUp(&in_shiftleft);
273 	IN_KeyUp(&in_shiftdown);
274 }
IN_ShiftRightDown_f(void)275 static void IN_ShiftRightDown_f (void)
276 {
277 	IN_KeyDown(&in_shiftright);
278 }
IN_ShiftRightUp_f(void)279 static void IN_ShiftRightUp_f (void)
280 {
281 	IN_KeyUp(&in_shiftright);
282 }
IN_ShiftRightUpDown_f(void)283 static void IN_ShiftRightUpDown_f (void)
284 {
285 	IN_KeyDown(&in_shiftright);
286 	IN_KeyDown(&in_shiftup);
287 }
IN_ShiftRightUpUp_f(void)288 static void IN_ShiftRightUpUp_f (void)
289 {
290 	IN_KeyUp(&in_shiftright);
291 	IN_KeyUp(&in_shiftup);
292 }
IN_ShiftRightDownDown_f(void)293 static void IN_ShiftRightDownDown_f (void)
294 {
295 	IN_KeyDown(&in_shiftright);
296 	IN_KeyDown(&in_shiftdown);
297 }
IN_ShiftRightDownUp_f(void)298 static void IN_ShiftRightDownUp_f (void)
299 {
300 	IN_KeyUp(&in_shiftright);
301 	IN_KeyUp(&in_shiftdown);
302 }
IN_ShiftUpDown_f(void)303 static void IN_ShiftUpDown_f (void)
304 {
305 	IN_KeyDown(&in_shiftup);
306 }
IN_ShiftUpUp_f(void)307 static void IN_ShiftUpUp_f (void)
308 {
309 	IN_KeyUp(&in_shiftup);
310 }
IN_ShiftDownDown_f(void)311 static void IN_ShiftDownDown_f (void)
312 {
313 	IN_KeyDown(&in_shiftdown);
314 }
IN_ShiftDownUp_f(void)315 static void IN_ShiftDownUp_f (void)
316 {
317 	IN_KeyUp(&in_shiftdown);
318 }
IN_ZoomInDown_f(void)319 static void IN_ZoomInDown_f (void)
320 {
321 	IN_KeyDown(&in_zoomin);
322 }
IN_ZoomInUp_f(void)323 static void IN_ZoomInUp_f (void)
324 {
325 	IN_KeyUp(&in_zoomin);
326 }
IN_ZoomOutDown_f(void)327 static void IN_ZoomOutDown_f (void)
328 {
329 	IN_KeyDown(&in_zoomout);
330 }
IN_ZoomOutUp_f(void)331 static void IN_ZoomOutUp_f (void)
332 {
333 	IN_KeyUp(&in_zoomout);
334 }
335 
336 
337 /**
338  * @brief Switch one worldlevel up
339  */
CL_LevelUp_f(void)340 static void CL_LevelUp_f (void)
341 {
342 	if (!CL_OnBattlescape())
343 		return;
344 	Cvar_SetValue("cl_worldlevel", (cl_worldlevel->integer < cl.mapMaxLevel - 1) ? cl_worldlevel->integer + 1 : cl.mapMaxLevel - 1);
345 }
346 
347 /**
348  * @brief Switch one worldlevel down
349  */
CL_LevelDown_f(void)350 static void CL_LevelDown_f (void)
351 {
352 	if (!CL_OnBattlescape())
353 		return;
354 	Cvar_SetValue("cl_worldlevel", (cl_worldlevel->integer > 0) ? cl_worldlevel->integer - 1 : 0);
355 }
356 
CL_ZoomInQuant_f(void)357 static void CL_ZoomInQuant_f (void)
358 {
359 	CL_CameraZoomIn();
360 }
361 
CL_ZoomOutQuant_f(void)362 static void CL_ZoomOutQuant_f (void)
363 {
364 	CL_CameraZoomOut();
365 }
366 
CL_WheelDown_f(void)367 static void CL_WheelDown_f (void)
368 {
369 	UI_MouseScroll(0, 1);
370 }
371 
CL_WheelUp_f(void)372 static void CL_WheelUp_f (void)
373 {
374 	UI_MouseScroll(0, -1);
375 }
376 
377 /**
378  * @brief Left mouse click
379  */
CL_SelectDown_f(void)380 static void CL_SelectDown_f (void)
381 {
382 	battlescapeMouseDraggingPossible = (CL_BattlescapeRunning() && IN_GetMouseSpace() != MS_UI);
383 	if (!battlescapeMouseDraggingPossible)
384 		return;
385 	battlescapeMouseDraggingX = mousePosX;
386 	battlescapeMouseDraggingY = mousePosY;
387 	battlescapeMouseDraggingActive = false;
388 	CL_InitBattlescapeMouseDragging();
389 }
390 
CL_SelectUp_f(void)391 static void CL_SelectUp_f (void)
392 {
393 #ifdef ANDROID
394 	/* Android input quirk - when user tries to zoom/rotate, and touches the screen with a second finger,
395 	 * SDL will send left mouse button up event, and right mouse button down event immediately after that,
396 	 * so we need to cancel the mouse click action, and let the user zoom/rotate as she wants */
397 	if ((SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_RIGHT)) == 0)
398 #endif
399 	if (IN_GetMouseSpace() == MS_WORLD && !battlescapeMouseDraggingActive)
400 		CL_ActorSelectMouse();
401 	battlescapeMouseDraggingPossible = false;
402 	battlescapeMouseDraggingActive = false;
403 	if (IN_GetMouseSpace() == MS_UI)
404 		return;
405 	IN_SetMouseSpace(MS_NULL);
406 }
407 
CL_ProcessMouseDragging(void)408 static void CL_ProcessMouseDragging (void)
409 {
410 	if (!battlescapeMouseDraggingPossible)
411 		return;
412 
413 	if (!battlescapeMouseDraggingActive &&
414 		(abs(battlescapeMouseDraggingX - mousePosX) > BATTLESCAPE_MOUSE_DRAGGING_TRIGGER_X ||
415 		 abs(battlescapeMouseDraggingY - mousePosY) > BATTLESCAPE_MOUSE_DRAGGING_TRIGGER_Y))
416 		battlescapeMouseDraggingActive = true;
417 
418 	if (battlescapeMouseDraggingActive)
419 		CL_BattlescapeMouseDragging();
420 }
421 
422 /**
423  * @brief Middle mouse click
424  */
CL_ActionDown_f(void)425 static void CL_ActionDown_f (void)
426 {
427 	if (!CL_OnBattlescape())
428 		return;
429 	IN_KeyDown(&in_pantilt);
430 }
431 
CL_ActionUp_f(void)432 static void CL_ActionUp_f (void)
433 {
434 	IN_KeyUp(&in_pantilt);
435 	if (IN_GetMouseSpace() == MS_UI)
436 		return;
437 	if (in_pantilt.msec < 250)
438 		CL_ActorActionMouse();
439 	IN_SetMouseSpace(MS_NULL);
440 }
441 
442 /**
443  * @brief Turn button is hit
444  */
CL_TurnDown_f(void)445 static void CL_TurnDown_f (void)
446 {
447 	if (IN_GetMouseSpace() == MS_UI)
448 		return;
449 	if (IN_GetMouseSpace() == MS_WORLD)
450 		CL_ActorTurnMouse();
451 }
452 
CL_TurnUp_f(void)453 static void CL_TurnUp_f (void)
454 {
455 	if (IN_GetMouseSpace() == MS_UI)
456 		return;
457 	IN_SetMouseSpace(MS_NULL);
458 }
459 
460 /**
461  * @todo only call/register it when we are on the battlescape
462  */
CL_HudRadarDown_f(void)463 static void CL_HudRadarDown_f (void)
464 {
465 	if (!CL_BattlescapeRunning())
466 		return;
467 	UI_PushWindow("radarmenu");
468 }
469 
470 /**
471  * @todo only call/register it when we are on the battlescape
472  */
CL_HudRadarUp_f(void)473 static void CL_HudRadarUp_f (void)
474 {
475 	if (!CL_BattlescapeRunning())
476 		return;
477 	UI_CloseWindow("radarmenu");
478 }
479 
480 /**
481  * @brief Right mouse button is hit in menu
482  */
CL_RightClickDown_f(void)483 static void CL_RightClickDown_f (void)
484 {
485 	if (IN_GetMouseSpace() == MS_UI) {
486 		UI_MouseDown(mousePosX, mousePosY, K_MOUSE2);
487 	}
488 }
489 
490 /**
491  * @brief Right mouse button is freed in menu
492  */
CL_RightClickUp_f(void)493 static void CL_RightClickUp_f (void)
494 {
495 	if (IN_GetMouseSpace() == MS_UI) {
496 		UI_MouseUp(mousePosX, mousePosY, K_MOUSE2);
497 	}
498 }
499 
500 /**
501  * @brief Middle mouse button is hit in menu
502  */
CL_MiddleClickDown_f(void)503 static void CL_MiddleClickDown_f (void)
504 {
505 	if (IN_GetMouseSpace() == MS_UI) {
506 		UI_MouseDown(mousePosX, mousePosY, K_MOUSE3);
507 	}
508 }
509 
510 /**
511  * @brief Middle mouse button is freed in menu
512  */
CL_MiddleClickUp_f(void)513 static void CL_MiddleClickUp_f (void)
514 {
515 	if (IN_GetMouseSpace() == MS_UI) {
516 		UI_MouseUp(mousePosX, mousePosY, K_MOUSE3);
517 	}
518 }
519 
520 /**
521  * @brief Left mouse button is hit in menu
522  */
CL_LeftClickDown_f(void)523 static void CL_LeftClickDown_f (void)
524 {
525 	if (IN_GetMouseSpace() == MS_UI) {
526 		UI_MouseDown(mousePosX, mousePosY, K_MOUSE1);
527 	}
528 }
529 
530 /**
531  * @brief Left mouse button is freed in menu
532  */
CL_LeftClickUp_f(void)533 static void CL_LeftClickUp_f (void)
534 {
535 	if (IN_GetMouseSpace() == MS_UI) {
536 		UI_MouseUp(mousePosX, mousePosY, K_MOUSE1);
537 	}
538 }
539 
540 #define SCROLL_BORDER	4
541 #define MOUSE_YAW_SCALE		0.1
542 #define MOUSE_PITCH_SCALE	0.1
543 
544 /**
545  * @note see SCROLL_BORDER define
546  */
CL_GetKeyMouseState(int dir)547 float CL_GetKeyMouseState (int dir)
548 {
549 	float value;
550 
551 	switch (dir) {
552 	case STATE_FORWARD:
553 		/* sum directions, 'true' is use as '1' */
554 		value = (in_shiftup.state & 1) + (mousePosY <= (viddef.y / viddef.ry) + SCROLL_BORDER) - (in_shiftdown.state & 1) - (mousePosY >= ((viddef.y + viddef.viewHeight) / viddef.ry) - SCROLL_BORDER);
555 		break;
556 	case STATE_RIGHT:
557 		/* sum directions, 'true' is use as '1' */
558 		value = (in_shiftright.state & 1) + (mousePosX >= ((viddef.x + viddef.viewWidth) / viddef.rx) - SCROLL_BORDER) - (in_shiftleft.state & 1) - (mousePosX <= (viddef.x / viddef.rx) + SCROLL_BORDER);
559 		break;
560 	case STATE_ZOOM:
561 		value = (in_zoomin.state & 1) - (in_zoomout.state & 1);
562 		break;
563 	case STATE_ROT:
564 		value = (in_turnleft.state & 1) - (in_turnright.state & 1);
565 		if (in_pantilt.state)
566 			value -= (float) (mousePosX - oldMousePosX) * MOUSE_YAW_SCALE;
567 		break;
568 	case STATE_TILT:
569 		value = (in_turnup.state & 1) - (in_turndown.state & 1);
570 		if (in_pantilt.state)
571 			value += (float) (mousePosY - oldMousePosY) * MOUSE_PITCH_SCALE;
572 		break;
573 	default:
574 		value = 0.0;
575 		break;
576 	}
577 
578 	return value;
579 }
580 
581 /**
582  * @brief Called every frame to parse the input
583  * @sa CL_Frame
584  */
IN_Parse(void)585 static void IN_Parse (void)
586 {
587 	IN_SetMouseSpace(MS_NULL);
588 
589 	/* standard menu and world mouse handling */
590 	if (UI_IsMouseOnWindow()) {
591 		IN_SetMouseSpace(MS_UI);
592 		return;
593 	}
594 
595 	if (cls.state != ca_active)
596 		return;
597 
598 	if (!viddef.viewWidth || !viddef.viewHeight)
599 		return;
600 
601 	if (CL_ActorMouseTrace()) {
602 		/* mouse is in the world */
603 		IN_SetMouseSpace(MS_WORLD);
604 	}
605 }
606 
607 /**
608  * @brief Debug function to print sdl key events
609  */
IN_PrintKey(const SDL_Event * event,int down)610 static inline void IN_PrintKey (const SDL_Event* event, int down)
611 {
612 	if (in_debug->integer) {
613 		Com_Printf("key name: %s (down: %i)", SDL_GetKeyName(event->key.keysym.sym), down);
614 		int unicode;
615 #if SDL_VERSION_ATLEAST(2,0,0)
616 		unicode = event->key.keysym.sym;
617 #else
618 		unicode = event->key.keysym.unicode;
619 #endif
620 		if (unicode) {
621 			Com_Printf(" unicode: %x", unicode);
622 			if (unicode >= '0' && unicode <= '~')  /* printable? */
623 				Com_Printf(" (%c)", (unsigned char)(unicode));
624 		}
625 		Com_Printf("\n");
626 	}
627 }
628 
629 /**
630  * @brief Translate the keys to ufo keys
631  */
IN_TranslateKey(const unsigned int keycode,unsigned int * ascii)632 static void IN_TranslateKey (const unsigned int keycode, unsigned int* ascii)
633 {
634 	switch (keycode) {
635 	case SDLK_PAGEUP:
636 		*ascii = K_PGUP;
637 		break;
638 #if SDL_VERSION_ATLEAST(2,0,0)
639 	case SDLK_KP_0:
640 		*ascii = K_KP_INS;
641 		break;
642 	case SDLK_KP_1:
643 		*ascii = K_KP_END;
644 		break;
645 	case SDLK_KP_2:
646 		*ascii = K_KP_DOWNARROW;
647 		break;
648 	case SDLK_KP_3:
649 		*ascii = K_KP_PGDN;
650 		break;
651 	case SDLK_KP_4:
652 		*ascii = K_KP_LEFTARROW;
653 		break;
654 	case SDLK_KP_5:
655 		*ascii = K_KP_5;
656 		break;
657 	case SDLK_KP_6:
658 		*ascii = K_KP_RIGHTARROW;
659 		break;
660 	case SDLK_KP_7:
661 		*ascii = K_KP_HOME;
662 		break;
663 	case SDLK_KP_8:
664 		*ascii = K_KP_UPARROW;
665 		break;
666 	case SDLK_KP_9:
667 		*ascii = K_KP_PGUP;
668 		break;
669 	case SDLK_PRINTSCREEN:
670 		*ascii = K_PRINT;
671 		break;
672 	case SDLK_SCROLLLOCK:
673 		*ascii = K_SCROLLOCK;
674 		break;
675 #else
676 	case SDLK_KP0:
677 		*ascii = K_KP_INS;
678 		break;
679 	case SDLK_KP1:
680 		*ascii = K_KP_END;
681 		break;
682 	case SDLK_KP2:
683 		*ascii = K_KP_DOWNARROW;
684 		break;
685 	case SDLK_KP3:
686 		*ascii = K_KP_PGDN;
687 		break;
688 	case SDLK_KP4:
689 		*ascii = K_KP_LEFTARROW;
690 		break;
691 	case SDLK_KP5:
692 		*ascii = K_KP_5;
693 		break;
694 	case SDLK_KP6:
695 		*ascii = K_KP_RIGHTARROW;
696 		break;
697 	case SDLK_KP7:
698 		*ascii = K_KP_HOME;
699 		break;
700 	case SDLK_KP8:
701 		*ascii = K_KP_UPARROW;
702 		break;
703 	case SDLK_KP9:
704 		*ascii = K_KP_PGUP;
705 		break;
706 	case SDLK_LSUPER:
707 	case SDLK_RSUPER:
708 		*ascii = K_SUPER;
709 		break;
710 	case SDLK_COMPOSE:
711 		*ascii = K_COMPOSE;
712 		break;
713 	case SDLK_PRINT:
714 		*ascii = K_PRINT;
715 		break;
716 	case SDLK_BREAK:
717 		*ascii = K_BREAK;
718 		break;
719 	case SDLK_EURO:
720 		*ascii = K_EURO;
721 		break;
722 	case SDLK_SCROLLOCK:
723 		*ascii = K_SCROLLOCK;
724 		break;
725 	case SDLK_NUMLOCK:
726 		*ascii = K_KP_NUMLOCK;
727 		break;
728 #endif
729 	case SDLK_PAGEDOWN:
730 		*ascii = K_PGDN;
731 		break;
732 	case SDLK_HOME:
733 		*ascii = K_HOME;
734 		break;
735 	case SDLK_END:
736 		*ascii = K_END;
737 		break;
738 	case SDLK_LEFT:
739 		*ascii = K_LEFTARROW;
740 		break;
741 	case SDLK_RIGHT:
742 		*ascii = K_RIGHTARROW;
743 		break;
744 	case SDLK_DOWN:
745 		*ascii = K_DOWNARROW;
746 		break;
747 	case SDLK_UP:
748 		*ascii = K_UPARROW;
749 		break;
750 	case SDLK_ESCAPE:
751 		*ascii = K_ESCAPE;
752 		break;
753 	case SDLK_KP_ENTER:
754 		*ascii = K_KP_ENTER;
755 		break;
756 	case SDLK_RETURN:
757 		*ascii = K_ENTER;
758 		break;
759 	case SDLK_TAB:
760 		*ascii = K_TAB;
761 		break;
762 	case SDLK_F1:
763 		*ascii = K_F1;
764 		break;
765 	case SDLK_F2:
766 		*ascii = K_F2;
767 		break;
768 	case SDLK_F3:
769 		*ascii = K_F3;
770 		break;
771 	case SDLK_F4:
772 		*ascii = K_F4;
773 		break;
774 	case SDLK_F5:
775 		*ascii = K_F5;
776 		break;
777 	case SDLK_F6:
778 		*ascii = K_F6;
779 		break;
780 	case SDLK_F7:
781 		*ascii = K_F7;
782 		break;
783 	case SDLK_F8:
784 		*ascii = K_F8;
785 		break;
786 	case SDLK_F9:
787 		*ascii = K_F9;
788 		break;
789 	case SDLK_F10:
790 		*ascii = K_F10;
791 		break;
792 	case SDLK_F11:
793 		*ascii = K_F11;
794 		break;
795 	case SDLK_F12:
796 		*ascii = K_F12;
797 		break;
798 	case SDLK_F13:
799 		*ascii = K_F13;
800 		break;
801 	case SDLK_F14:
802 		*ascii = K_F14;
803 		break;
804 	case SDLK_F15:
805 		*ascii = K_F15;
806 		break;
807 	case SDLK_BACKSPACE:
808 		*ascii = K_BACKSPACE;
809 		break;
810 	case SDLK_KP_PERIOD:
811 		*ascii = K_KP_DEL;
812 		break;
813 	case SDLK_DELETE:
814 		*ascii = K_DEL;
815 		break;
816 	case SDLK_PAUSE:
817 		*ascii = K_PAUSE;
818 		break;
819 	case SDLK_LSHIFT:
820 	case SDLK_RSHIFT:
821 		*ascii = K_SHIFT;
822 		break;
823 	case SDLK_LCTRL:
824 	case SDLK_RCTRL:
825 		*ascii = K_CTRL;
826 		break;
827 	case SDLK_LALT:
828 	case SDLK_RALT:
829 		*ascii = K_ALT;
830 		break;
831 	case SDLK_INSERT:
832 		*ascii = K_INS;
833 		break;
834 	case SDLK_KP_PLUS:
835 		*ascii = K_KP_PLUS;
836 		break;
837 	case SDLK_KP_MINUS:
838 		*ascii = K_KP_MINUS;
839 		break;
840 	case SDLK_KP_DIVIDE:
841 		*ascii = K_KP_SLASH;
842 		break;
843 	case SDLK_KP_MULTIPLY:
844 		*ascii = K_KP_MULTIPLY;
845 		break;
846 	case SDLK_MODE:
847 		*ascii = K_MODE;
848 		break;
849 	case SDLK_HELP:
850 		*ascii = K_HELP;
851 		break;
852 	case SDLK_SYSREQ:
853 		*ascii = K_SYSREQ;
854 		break;
855 	case SDLK_MENU:
856 		*ascii = K_MENU;
857 		break;
858 	case SDLK_POWER:
859 		*ascii = K_POWER;
860 		break;
861 	case SDLK_UNDO:
862 		*ascii = K_UNDO;
863 		break;
864 	case SDLK_CAPSLOCK:
865 		*ascii = K_CAPSLOCK;
866 		break;
867 	case SDLK_SPACE:
868 		*ascii = K_SPACE;
869 		break;
870 	default:
871 		if (UTF8_encoded_len(keycode) == 1 && isprint(keycode))
872 			*ascii = keycode;
873 		else
874 			*ascii = 0;
875 		break;
876 	}
877 }
878 
IN_EventEnqueue(unsigned int keyNum,unsigned short keyUnicode,bool keyDown)879 void IN_EventEnqueue (unsigned int keyNum, unsigned short keyUnicode, bool keyDown)
880 {
881 	if (keyNum > 0 || keyUnicode > 0) {
882 		if (in_debug->integer)
883 			Com_Printf("Enqueue: %s (%i) (down: %i)\n", Key_KeynumToString(keyNum), keyNum, keyDown);
884 		keyq[keyq_head].down = keyDown;
885 		keyq[keyq_head].unicode = keyUnicode;
886 		keyq[keyq_head].key = keyNum;
887 		keyq_head = (keyq_head + 1) & (MAX_KEYQ - 1);
888 	}
889 }
890 
IN_ToggleFullscreen(void)891 static bool IN_ToggleFullscreen (void)
892 {
893 #if SDL_VERSION_ATLEAST(2,0,0)
894 	const int mask = SDL_WINDOW_FULLSCREEN_DESKTOP;
895 	const bool isFullScreen = SDL_GetWindowFlags(cls.window) & mask;
896 	SDL_SetWindowFullscreen(cls.window, isFullScreen ? 0 : SDL_WINDOW_FULLSCREEN);
897 	return SDL_GetWindowFlags(cls.window) & mask;
898 #else
899 #ifdef _WIN32
900 	return false;
901 #else
902 	SDL_Surface* surface = SDL_GetVideoSurface();
903 	if (!SDL_WM_ToggleFullScreen(surface)) {
904 		const int flags = surface->flags ^= SDL_FULLSCREEN;
905 		SDL_SetVideoMode(surface->w, surface->h, 0, flags);
906 	}
907 
908 	return surface->flags & SDL_FULLSCREEN;
909 #endif
910 #endif
911 }
912 
913 /**
914  * @brief Handle input events like key presses and joystick movement as well
915  * as window events
916  * @sa CL_Frame
917  * @sa IN_Parse
918  * @sa IN_JoystickMove
919  */
IN_Frame(void)920 void IN_Frame (void)
921 {
922 	int mouse_buttonstate;
923 	unsigned short unicode;
924 	unsigned int key;
925 	SDL_Event event;
926 
927 	IN_Parse();
928 
929 	IN_JoystickMove();
930 
931 	CL_ProcessMouseDragging();
932 
933 	if (vid_grabmouse->modified) {
934 		vid_grabmouse->modified = false;
935 
936 		if (!vid_grabmouse->integer) {
937 			/* ungrab the pointer */
938 			Com_Printf("Switch grab input off\n");
939 #if SDL_VERSION_ATLEAST(2,0,0)
940 			SDL_SetWindowGrab(cls.window, SDL_FALSE);
941 #else
942 			SDL_WM_GrabInput(SDL_GRAB_OFF);
943 #endif
944 		} else  {
945 			/* grab the pointer */
946 			Com_Printf("Switch grab input on\n");
947 #if SDL_VERSION_ATLEAST(2,0,0)
948 			SDL_SetWindowGrab(cls.window, SDL_TRUE);
949 #else
950 			SDL_WM_GrabInput(SDL_GRAB_ON);
951 #endif
952 		}
953 	}
954 
955 	oldMousePosX = mousePosX;
956 	oldMousePosY = mousePosY;
957 
958 	while (SDL_PollEvent(&event)) {
959 		switch (event.type) {
960 #if SDL_VERSION_ATLEAST(2,0,0)
961 		case SDL_MOUSEWHEEL:
962 			mouse_buttonstate = event.wheel.y < 0 ? K_MWHEELDOWN : K_MWHEELUP;
963 			IN_EventEnqueue(mouse_buttonstate, 0, true);
964 			break;
965 		case SDL_TEXTEDITING:
966 			/** @todo */
967 			break;
968 		case SDL_TEXTINPUT: {
969 			const char* text = event.text.text;
970 			const char** str = &text;
971 			for (;;) {
972 				const int characterUnicode = UTF8_next(str);
973 				if (characterUnicode == -1) {
974 					break;
975 				}
976 				unicode = characterUnicode;
977 				IN_TranslateKey(characterUnicode, &key);
978 				IN_EventEnqueue(key, unicode, true);
979 				IN_EventEnqueue(key, unicode, false);
980 			}
981 			break;
982 		}
983 #endif
984 		case SDL_MOUSEBUTTONDOWN:
985 		case SDL_MOUSEBUTTONUP:
986 			switch (event.button.button) {
987 			case SDL_BUTTON_LEFT:
988 				mouse_buttonstate = K_MOUSE1;
989 				break;
990 			case SDL_BUTTON_MIDDLE:
991 				mouse_buttonstate = K_MOUSE3;
992 				break;
993 			case SDL_BUTTON_RIGHT:
994 				mouse_buttonstate = K_MOUSE2;
995 				break;
996 #if SDL_VERSION_ATLEAST(2,0,0)
997 			case SDL_BUTTON_X1:
998 				mouse_buttonstate = K_MOUSE4;
999 				break;
1000 			case SDL_BUTTON_X2:
1001 				mouse_buttonstate = K_MOUSE5;
1002 				break;
1003 #else
1004 			case SDL_BUTTON_WHEELUP:
1005 				mouse_buttonstate = K_MWHEELUP;
1006 				break;
1007 			case SDL_BUTTON_WHEELDOWN:
1008 				mouse_buttonstate = K_MWHEELDOWN;
1009 				break;
1010 			case 6:
1011 				mouse_buttonstate = K_MOUSE4;
1012 				break;
1013 			case 7:
1014 				mouse_buttonstate = K_MOUSE5;
1015 				break;
1016 #endif
1017 			default:
1018 				mouse_buttonstate = K_AUX1 + (event.button.button - 8) % 16;
1019 				break;
1020 			}
1021 			IN_EventEnqueue(mouse_buttonstate, 0, (event.type == SDL_MOUSEBUTTONDOWN));
1022 			break;
1023 
1024 		case SDL_MOUSEMOTION:
1025 			SDL_GetMouseState(&mousePosX, &mousePosY);
1026 			mousePosX /= viddef.rx;
1027 			mousePosY /= viddef.ry;
1028 			break;
1029 
1030 		case SDL_KEYDOWN:
1031 			IN_PrintKey(&event, 1);
1032 			if ((event.key.keysym.mod & KMOD_ALT) && event.key.keysym.sym == SDLK_RETURN) {
1033 				Com_Printf("try to toggle fullscreen\n");
1034 				if (IN_ToggleFullscreen()) {
1035 					Cvar_SetValue("vid_fullscreen", 1);
1036 					/* make sure, that input grab is deactivated in fullscreen mode */
1037 					Cvar_SetValue("vid_grabmouse", 0);
1038 				} else {
1039 					Cvar_SetValue("vid_fullscreen", 0);
1040 				}
1041 				vid_fullscreen->modified = false; /* we just changed it with SDL. */
1042 				break; /* ignore this key */
1043 			}
1044 
1045 			if ((event.key.keysym.mod & KMOD_CTRL) && event.key.keysym.sym == SDLK_g) {
1046 #if SDL_VERSION_ATLEAST(2,0,0)
1047 				const bool grab = SDL_GetWindowGrab(cls.window);
1048 				Cvar_SetValue("vid_grabmouse", grab ? 0 : 1);
1049 #else
1050 				SDL_GrabMode gm = SDL_WM_GrabInput(SDL_GRAB_QUERY);
1051 				Cvar_SetValue("vid_grabmouse", (gm == SDL_GRAB_ON) ? 0 : 1);
1052 #endif
1053 				Com_Printf("toggled mouse grab (%s)\n", vid_grabmouse->integer == 1 ? "true" : "false");
1054 				break; /* ignore this key */
1055 			}
1056 
1057 			/* console key is hardcoded, so the user can never unbind it */
1058 			if ((event.key.keysym.mod & KMOD_SHIFT) && event.key.keysym.sym == SDLK_ESCAPE) {
1059 				Con_ToggleConsole_f();
1060 				break;
1061 			}
1062 
1063 #if SDL_VERSION_ATLEAST(2,0,0)
1064 			unicode = 0;
1065 #else
1066 			unicode = event.key.keysym.unicode;
1067 #endif
1068 			IN_TranslateKey(event.key.keysym.sym, &key);
1069 			IN_EventEnqueue(key, unicode, true);
1070 			break;
1071 
1072 		case SDL_KEYUP:
1073 			IN_PrintKey(&event, 0);
1074 #if SDL_VERSION_ATLEAST(2,0,0)
1075 			unicode = 0;
1076 #else
1077 			unicode = event.key.keysym.unicode;
1078 #endif
1079 			IN_TranslateKey(event.key.keysym.sym, &key);
1080 			IN_EventEnqueue(key, unicode, false);
1081 			break;
1082 
1083 #if SDL_VERSION_ATLEAST(2,0,0)
1084 		/** @todo implement missing events for sdl2 */
1085 		case SDL_WINDOWEVENT:
1086 			switch (event.window.type) {
1087 			case SDL_WINDOWEVENT_FOCUS_LOST:
1088 				UI_ReleaseInput();
1089 				break;
1090 			case SDL_WINDOWEVENT_RESIZED:
1091 				/* make sure that SDL_SetVideoMode is called again after we changed the size
1092 				 * otherwise the mouse will make problems */
1093 				vid_mode->modified = true;
1094 				break;
1095 			default:
1096 				break;
1097 			}
1098 			break;
1099 #else
1100 		case SDL_VIDEOEXPOSE:
1101 			break;
1102 
1103 		case SDL_ACTIVEEVENT:
1104 			/* make sure the menu no more captures the input when the game window loses focus */
1105 			if (event.active.state == SDL_APPINPUTFOCUS && event.active.gain == 0)
1106 				UI_ReleaseInput();
1107 			break;
1108 
1109 		case SDL_VIDEORESIZE:
1110 			/* make sure that SDL_SetVideoMode is called again after we changed the size
1111 			 * otherwise the mouse will make problems */
1112 			vid_mode->modified = true;
1113 #ifdef ANDROID
1114 			/* On Android the OpenGL context is destroyed after we've received a resize event,
1115 			 * so wee need to re-init OpenGL state machine and re-upload all textures */
1116 			R_ReinitOpenglContext();
1117 #endif
1118 			break;
1119 #endif
1120 
1121 		case SDL_QUIT:
1122 			Cmd_ExecuteString("quit");
1123 			break;
1124 		}
1125 	}
1126 }
1127 
1128 /**
1129  * Simulate press of a key with a command.
1130  */
CL_PressKey_f(void)1131 static void CL_PressKey_f (void)
1132 {
1133 	unsigned int keyNum;
1134 
1135 	if (Cmd_Argc() != 2) {
1136 		Com_Printf("Usage: %s <key> : simulate press of a key\n", Cmd_Argv(0));
1137 		return;
1138 	}
1139 
1140 	keyNum = Key_StringToKeynum(Cmd_Argv(1));
1141 	/* @todo unicode value is wrong */
1142 	IN_EventEnqueue(keyNum, '?', true);
1143 	IN_EventEnqueue(keyNum, '?', false);
1144 }
1145 
1146 typedef struct cursorChange_s {
1147 	mouseSpace_t prevSpace;
1148 	int cursor;
1149 } cursorChange_t;
1150 
1151 static cursorChange_t cursorChange;
1152 
IN_SetMouseSpace(mouseSpace_t mspace)1153 void IN_SetMouseSpace (mouseSpace_t mspace)
1154 {
1155 	if (mspace != MS_NULL) {
1156 		if (mspace != cursorChange.prevSpace) {
1157 			SCR_ChangeCursor(cursorChange.cursor);
1158 		}
1159 	}
1160 	if (mouseSpace != MS_NULL && mouseSpace != cursorChange.prevSpace) {
1161 		cursorChange.prevSpace = mouseSpace;
1162 		cursorChange.cursor = Cvar_GetValue("cursor");
1163 	}
1164 	mouseSpace = mspace;
1165 }
1166 
1167 /**
1168  * @sa CL_InitLocal
1169  */
IN_Init(void)1170 void IN_Init (void)
1171 {
1172 	Com_Printf("\n------- input initialization -------\n");
1173 
1174 	/* cvars */
1175 	in_debug = Cvar_Get("in_debug", "0", 0, "Show input key codes on game console");
1176 	cl_isometric = Cvar_Get("r_isometric", "0", CVAR_ARCHIVE, "Draw the world in isometric mode");
1177 
1178 	/* commands */
1179 	Cmd_AddCommand("+turnleft", IN_TurnLeftDown_f, N_("Rotate battlescape camera anti-clockwise"));
1180 	Cmd_AddCommand("-turnleft", IN_TurnLeftUp_f);
1181 	Cmd_AddCommand("+turnright", IN_TurnRightDown_f, N_("Rotate battlescape camera clockwise"));
1182 	Cmd_AddCommand("-turnright", IN_TurnRightUp_f);
1183 	Cmd_AddCommand("+turnup", IN_TurnUpDown_f, N_("Tilt battlescape camera up"));
1184 	Cmd_AddCommand("-turnup", IN_TurnUpUp_f);
1185 	Cmd_AddCommand("+turndown", IN_TurnDownDown_f, N_("Tilt battlescape camera down"));
1186 	Cmd_AddCommand("-turndown", IN_TurnDownUp_f);
1187 	Cmd_AddCommand("+pantilt", IN_PanTiltDown_f, N_("Move battlescape camera"));
1188 	Cmd_AddCommand("-pantilt", IN_PanTiltUp_f);
1189 	Cmd_AddCommand("+shiftleft", IN_ShiftLeftDown_f, N_("Move battlescape camera left"));
1190 	Cmd_AddCommand("-shiftleft", IN_ShiftLeftUp_f);
1191 	Cmd_AddCommand("+shiftleftup", IN_ShiftLeftUpDown_f, N_("Move battlescape camera top left"));
1192 	Cmd_AddCommand("-shiftleftup", IN_ShiftLeftUpUp_f);
1193 	Cmd_AddCommand("+shiftleftdown", IN_ShiftLeftDownDown_f, N_("Move battlescape camera bottom left"));
1194 	Cmd_AddCommand("-shiftleftdown", IN_ShiftLeftDownUp_f);
1195 	Cmd_AddCommand("+shiftright", IN_ShiftRightDown_f, N_("Move battlescape camera right"));
1196 	Cmd_AddCommand("-shiftright", IN_ShiftRightUp_f);
1197 	Cmd_AddCommand("+shiftrightup", IN_ShiftRightUpDown_f, N_("Move battlescape camera top right"));
1198 	Cmd_AddCommand("-shiftrightup", IN_ShiftRightUpUp_f);
1199 	Cmd_AddCommand("+shiftrightdown", IN_ShiftRightDownDown_f, N_("Move battlescape camera bottom right"));
1200 	Cmd_AddCommand("-shiftrightdown", IN_ShiftRightDownUp_f);
1201 	Cmd_AddCommand("+shiftup", IN_ShiftUpDown_f, N_("Move battlescape camera forward"));
1202 	Cmd_AddCommand("-shiftup", IN_ShiftUpUp_f);
1203 	Cmd_AddCommand("+shiftdown", IN_ShiftDownDown_f, N_("Move battlescape camera backward"));
1204 	Cmd_AddCommand("-shiftdown", IN_ShiftDownUp_f);
1205 	Cmd_AddCommand("+zoomin", IN_ZoomInDown_f, N_("Zoom in"));
1206 	Cmd_AddCommand("-zoomin", IN_ZoomInUp_f);
1207 	Cmd_AddCommand("+zoomout", IN_ZoomOutDown_f, N_("Zoom out"));
1208 	Cmd_AddCommand("-zoomout", IN_ZoomOutUp_f);
1209 
1210 	Cmd_AddCommand("+leftmouse", CL_LeftClickDown_f, N_("Left mouse button click (menu)"));
1211 	Cmd_AddCommand("-leftmouse", CL_LeftClickUp_f);
1212 	Cmd_AddCommand("+middlemouse", CL_MiddleClickDown_f, N_("Middle mouse button click (menu)"));
1213 	Cmd_AddCommand("-middlemouse", CL_MiddleClickUp_f);
1214 	Cmd_AddCommand("+rightmouse", CL_RightClickDown_f, N_("Right mouse button click (menu)"));
1215 	Cmd_AddCommand("-rightmouse", CL_RightClickUp_f);
1216 	Cmd_AddCommand("wheelupmouse", CL_WheelUp_f, N_("Mouse wheel up"));
1217 	Cmd_AddCommand("wheeldownmouse", CL_WheelDown_f, N_("Mouse wheel down"));
1218 	Cmd_AddCommand("+select", CL_SelectDown_f, N_("Select objects/Walk to a square/In fire mode, fire etc"));
1219 	Cmd_AddCommand("-select", CL_SelectUp_f);
1220 	Cmd_AddCommand("+action", CL_ActionDown_f, N_("Rotate Battlescape/In fire mode, cancel action"));
1221 	Cmd_AddCommand("-action", CL_ActionUp_f);
1222 	Cmd_AddCommand("+turn", CL_TurnDown_f, N_("Turn soldier toward mouse pointer"));
1223 	Cmd_AddCommand("-turn", CL_TurnUp_f);
1224 	Cmd_AddCommand("+hudradar", CL_HudRadarDown_f, N_("Toggles the hud radar mode"));
1225 	Cmd_AddCommand("-hudradar", CL_HudRadarUp_f);
1226 
1227 	Cmd_AddCommand("levelup", CL_LevelUp_f, N_("Slice through terrain at a higher level"));
1228 	Cmd_AddCommand("leveldown", CL_LevelDown_f, N_("Slice through terrain at a lower level"));
1229 	Cmd_AddCommand("zoominquant", CL_ZoomInQuant_f, N_("Zoom in"));
1230 	Cmd_AddCommand("zoomoutquant", CL_ZoomOutQuant_f, N_("Zoom out"));
1231 
1232 	Cmd_AddCommand("press", CL_PressKey_f, "Press a key from a command");
1233 
1234 	mousePosX = mousePosY = 0.0;
1235 
1236 	IN_StartupJoystick();
1237 }
1238 
1239 /**
1240  * @sa CL_SendCommand
1241  */
IN_SendKeyEvents(void)1242 void IN_SendKeyEvents (void)
1243 {
1244 	while (keyq_head != keyq_tail) {
1245 		Key_Event(keyq[keyq_tail].key, keyq[keyq_tail].unicode, keyq[keyq_tail].down, CL_Milliseconds());
1246 		keyq_tail = (keyq_tail + 1) & (MAX_KEYQ - 1);
1247 	}
1248 }
1249