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