improved code to type player name in main menu
[rocksndiamonds.git] / src / events.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // events.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "events.h"
15 #include "init.h"
16 #include "screens.h"
17 #include "tools.h"
18 #include "game.h"
19 #include "editor.h"
20 #include "files.h"
21 #include "tape.h"
22 #include "anim.h"
23 #include "network.h"
24
25
26 #define DEBUG_EVENTS            0
27
28 #define DEBUG_EVENTS_BUTTON     (DEBUG_EVENTS   * 0)
29 #define DEBUG_EVENTS_MOTION     (DEBUG_EVENTS   * 0)
30 #define DEBUG_EVENTS_WHEEL      (DEBUG_EVENTS   * 1)
31 #define DEBUG_EVENTS_WINDOW     (DEBUG_EVENTS   * 0)
32 #define DEBUG_EVENTS_FINGER     (DEBUG_EVENTS   * 0)
33 #define DEBUG_EVENTS_TEXT       (DEBUG_EVENTS   * 1)
34 #define DEBUG_EVENTS_KEY        (DEBUG_EVENTS   * 1)
35
36
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
41
42 static boolean stop_processing_events = FALSE;
43
44
45 // forward declarations for internal use
46 static void ClearTouchInfo(void);
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
49
50
51 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
55
56 int FilterMouseMotionEvents(void *userdata, Event *event)
57 {
58   if (event->type == EVENT_MOTIONNOTIFY)
59   {
60     int mouse_x = ((MotionEvent *)event)->x;
61     int mouse_y = ((MotionEvent *)event)->y;
62
63     UpdateRawMousePosition(mouse_x, mouse_y);
64   }
65
66   return 1;
67 }
68
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
72
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
75
76 static int FilterEvents(const Event *event)
77 {
78   MotionEvent *motion;
79
80   // skip repeated key press events if keyboard auto-repeat is disabled
81   if (event->type == EVENT_KEYPRESS &&
82       event->key.repeat &&
83       !keyrepeat_status)
84     return 0;
85
86   if (event->type == EVENT_BUTTONPRESS ||
87       event->type == EVENT_BUTTONRELEASE)
88   {
89     ((ButtonEvent *)event)->x -= video.screen_xoffset;
90     ((ButtonEvent *)event)->y -= video.screen_yoffset;
91   }
92   else if (event->type == EVENT_MOTIONNOTIFY)
93   {
94     ((MotionEvent *)event)->x -= video.screen_xoffset;
95     ((MotionEvent *)event)->y -= video.screen_yoffset;
96   }
97
98   if (event->type == EVENT_BUTTONPRESS ||
99       event->type == EVENT_BUTTONRELEASE ||
100       event->type == EVENT_MOTIONNOTIFY)
101   {
102     // do not reset mouse cursor before all pending events have been processed
103     if (gfx.cursor_mode == cursor_mode_last &&
104         ((game_status == GAME_MODE_TITLE &&
105           gfx.cursor_mode == CURSOR_NONE) ||
106          (game_status == GAME_MODE_PLAYING &&
107           gfx.cursor_mode == CURSOR_PLAYFIELD)))
108     {
109       SetMouseCursor(CURSOR_DEFAULT);
110
111       DelayReached(&special_cursor_delay, 0);
112
113       cursor_mode_last = CURSOR_DEFAULT;
114     }
115   }
116
117   // non-motion events are directly passed to event handler functions
118   if (event->type != EVENT_MOTIONNOTIFY)
119     return 1;
120
121   motion = (MotionEvent *)event;
122   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
123                              motion->y >= SY && motion->y < SY + SYSIZE);
124
125   // set correct mouse x/y position (for pointer class global animations)
126   // (this is required in rare cases where the mouse x/y position calculated
127   // from raw values (to apply logical screen size scaling corrections) does
128   // not match the final mouse event x/y position -- this may happen because
129   // the SDL renderer's viewport position is internally represented as float,
130   // but only accessible as integer, which may lead to rounding errors)
131   gfx.mouse_x = motion->x;
132   gfx.mouse_y = motion->y;
133
134   // skip mouse motion events without pressed button outside level editor
135   if (button_status == MB_RELEASED &&
136       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
137     return 0;
138
139   return 1;
140 }
141
142 // to prevent delay problems, skip mouse motion events if the very next
143 // event is also a mouse motion event (and therefore effectively only
144 // handling the last of a row of mouse motion events in the event queue)
145
146 static boolean SkipPressedMouseMotionEvent(const Event *event)
147 {
148   // nothing to do if the current event is not a mouse motion event
149   if (event->type != EVENT_MOTIONNOTIFY)
150     return FALSE;
151
152   // only skip motion events with pressed button outside the game
153   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
154     return FALSE;
155
156   if (PendingEvent())
157   {
158     Event next_event;
159
160     PeekEvent(&next_event);
161
162     // if next event is also a mouse motion event, skip the current one
163     if (next_event.type == EVENT_MOTIONNOTIFY)
164       return TRUE;
165   }
166
167   return FALSE;
168 }
169
170 static boolean WaitValidEvent(Event *event)
171 {
172   WaitEvent(event);
173
174   if (!FilterEvents(event))
175     return FALSE;
176
177   if (SkipPressedMouseMotionEvent(event))
178     return FALSE;
179
180   return TRUE;
181 }
182
183 /* this is especially needed for event modifications for the Android target:
184    if mouse coordinates should be modified in the event filter function,
185    using a properly installed SDL event filter does not work, because in
186    the event filter, mouse coordinates in the event structure are still
187    physical pixel positions, not logical (scaled) screen positions, so this
188    has to be handled at a later stage in the event processing functions
189    (when device pixel positions are already converted to screen positions) */
190
191 boolean NextValidEvent(Event *event)
192 {
193   while (PendingEvent())
194     if (WaitValidEvent(event))
195       return TRUE;
196
197   return FALSE;
198 }
199
200 void StopProcessingEvents(void)
201 {
202   stop_processing_events = TRUE;
203 }
204
205 static void HandleEvents(void)
206 {
207   Event event;
208   unsigned int event_frame_delay = 0;
209   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
210
211   ResetDelayCounter(&event_frame_delay);
212
213   stop_processing_events = FALSE;
214
215   while (NextValidEvent(&event))
216   {
217     switch (event.type)
218     {
219       case EVENT_BUTTONPRESS:
220       case EVENT_BUTTONRELEASE:
221         HandleButtonEvent((ButtonEvent *) &event);
222         break;
223
224       case EVENT_MOTIONNOTIFY:
225         HandleMotionEvent((MotionEvent *) &event);
226         break;
227
228       case EVENT_WHEELMOTION:
229         HandleWheelEvent((WheelEvent *) &event);
230         break;
231
232       case SDL_WINDOWEVENT:
233         HandleWindowEvent((WindowEvent *) &event);
234         break;
235
236       case EVENT_FINGERPRESS:
237       case EVENT_FINGERRELEASE:
238       case EVENT_FINGERMOTION:
239         HandleFingerEvent((FingerEvent *) &event);
240         break;
241
242       case EVENT_TEXTINPUT:
243         HandleTextEvent((TextEvent *) &event);
244         break;
245
246       case SDL_APP_WILLENTERBACKGROUND:
247       case SDL_APP_DIDENTERBACKGROUND:
248       case SDL_APP_WILLENTERFOREGROUND:
249       case SDL_APP_DIDENTERFOREGROUND:
250         HandlePauseResumeEvent((PauseResumeEvent *) &event);
251         break;
252
253       case EVENT_KEYPRESS:
254       case EVENT_KEYRELEASE:
255         HandleKeyEvent((KeyEvent *) &event);
256         break;
257
258       case EVENT_USER:
259         HandleUserEvent((UserEvent *) &event);
260         break;
261
262       default:
263         HandleOtherEvents(&event);
264         break;
265     }
266
267     // do not handle events for longer than standard frame delay period
268     if (DelayReached(&event_frame_delay, event_frame_delay_value))
269       break;
270
271     // do not handle any further events if triggered by a special flag
272     if (stop_processing_events)
273       break;
274   }
275 }
276
277 void HandleOtherEvents(Event *event)
278 {
279   switch (event->type)
280   {
281     case SDL_CONTROLLERBUTTONDOWN:
282     case SDL_CONTROLLERBUTTONUP:
283       // for any game controller button event, disable overlay buttons
284       SetOverlayEnabled(FALSE);
285
286       HandleSpecialGameControllerButtons(event);
287
288       // FALL THROUGH
289     case SDL_CONTROLLERDEVICEADDED:
290     case SDL_CONTROLLERDEVICEREMOVED:
291     case SDL_CONTROLLERAXISMOTION:
292     case SDL_JOYAXISMOTION:
293     case SDL_JOYBUTTONDOWN:
294     case SDL_JOYBUTTONUP:
295       HandleJoystickEvent(event);
296       break;
297
298     case SDL_DROPBEGIN:
299     case SDL_DROPCOMPLETE:
300     case SDL_DROPFILE:
301     case SDL_DROPTEXT:
302       HandleDropEvent(event);
303       break;
304
305     case EVENT_QUIT:
306       CloseAllAndExit(0);
307       break;
308
309     default:
310       break;
311   }
312 }
313
314 static void HandleMouseCursor(void)
315 {
316   if (game_status == GAME_MODE_TITLE)
317   {
318     // when showing title screens, hide mouse pointer (if not moved)
319
320     if (gfx.cursor_mode != CURSOR_NONE &&
321         DelayReached(&special_cursor_delay, special_cursor_delay_value))
322     {
323       SetMouseCursor(CURSOR_NONE);
324     }
325   }
326   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
327                                                 tape.single_step))
328   {
329     // when playing, display a special mouse pointer inside the playfield
330
331     // display normal pointer if mouse pressed
332     if (button_status != MB_RELEASED)
333       DelayReached(&special_cursor_delay, 0);
334
335     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
336         cursor_inside_playfield &&
337         DelayReached(&special_cursor_delay, special_cursor_delay_value))
338     {
339       if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
340           tile_cursor.enabled)
341         SetMouseCursor(CURSOR_PLAYFIELD);
342     }
343   }
344   else if (gfx.cursor_mode != CURSOR_DEFAULT)
345   {
346     SetMouseCursor(CURSOR_DEFAULT);
347   }
348
349   // this is set after all pending events have been processed
350   cursor_mode_last = gfx.cursor_mode;
351 }
352
353 void EventLoop(void)
354 {
355   while (1)
356   {
357     if (PendingEvent())
358       HandleEvents();
359     else
360       HandleNoEvent();
361
362     // execute event related actions after pending events have been processed
363     HandleEventActions();
364
365     // don't use all CPU time when idle; the main loop while playing
366     // has its own synchronization and is CPU friendly, too
367
368     if (game_status == GAME_MODE_PLAYING)
369       HandleGameActions();
370
371     // always copy backbuffer to visible screen for every video frame
372     BackToFront();
373
374     // reset video frame delay to default (may change again while playing)
375     SetVideoFrameDelay(MenuFrameDelay);
376
377     if (game_status == GAME_MODE_QUIT)
378       return;
379   }
380 }
381
382 void ClearAutoRepeatKeyEvents(void)
383 {
384   while (PendingEvent())
385   {
386     Event next_event;
387
388     PeekEvent(&next_event);
389
390     // if event is repeated key press event, remove it from event queue
391     if (next_event.type == EVENT_KEYPRESS &&
392         next_event.key.repeat)
393       WaitEvent(&next_event);
394     else
395       break;
396   }
397 }
398
399 void ClearEventQueue(void)
400 {
401   Event event;
402
403   while (NextValidEvent(&event))
404   {
405     switch (event.type)
406     {
407       case EVENT_BUTTONRELEASE:
408         button_status = MB_RELEASED;
409         break;
410
411       case EVENT_FINGERRELEASE:
412       case EVENT_KEYRELEASE:
413         ClearPlayerAction();
414         break;
415
416       case SDL_CONTROLLERBUTTONUP:
417         HandleJoystickEvent(&event);
418         ClearPlayerAction();
419         break;
420
421       default:
422         HandleOtherEvents(&event);
423         break;
424     }
425   }
426 }
427
428 static void ClearPlayerMouseAction(void)
429 {
430   local_player->mouse_action.lx = 0;
431   local_player->mouse_action.ly = 0;
432   local_player->mouse_action.button = 0;
433 }
434
435 void ClearPlayerAction(void)
436 {
437   int i;
438
439   // simulate key release events for still pressed keys
440   key_joystick_mapping = 0;
441   for (i = 0; i < MAX_PLAYERS; i++)
442   {
443     stored_player[i].action = 0;
444     stored_player[i].snap_action = 0;
445   }
446
447   // simulate finger release events for still pressed virtual buttons
448   overlay.grid_button_action = JOY_NO_ACTION;
449
450   ClearTouchInfo();
451   ClearJoystickState();
452   ClearPlayerMouseAction();
453 }
454
455 static void SetPlayerMouseAction(int mx, int my, int button)
456 {
457   int lx = getLevelFromScreenX(mx);
458   int ly = getLevelFromScreenY(my);
459   int new_button = (!local_player->mouse_action.button && button);
460
461   if (local_player->mouse_action.button_hint)
462     button = local_player->mouse_action.button_hint;
463
464   ClearPlayerMouseAction();
465
466   if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
467     return;
468
469   local_player->mouse_action.lx = lx;
470   local_player->mouse_action.ly = ly;
471   local_player->mouse_action.button = button;
472
473   if (tape.recording && tape.pausing && tape.use_mouse_actions)
474   {
475     // un-pause a paused game only if mouse button was newly pressed down
476     if (new_button)
477       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
478   }
479
480   SetTileCursorXY(lx, ly);
481 }
482
483 static Key GetKeyFromGridButton(int grid_button)
484 {
485   return (grid_button == CHAR_GRID_BUTTON_LEFT  ? setup.input[0].key.left :
486           grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
487           grid_button == CHAR_GRID_BUTTON_UP    ? setup.input[0].key.up :
488           grid_button == CHAR_GRID_BUTTON_DOWN  ? setup.input[0].key.down :
489           grid_button == CHAR_GRID_BUTTON_SNAP  ? setup.input[0].key.snap :
490           grid_button == CHAR_GRID_BUTTON_DROP  ? setup.input[0].key.drop :
491           KSYM_UNDEFINED);
492 }
493
494 #if defined(PLATFORM_ANDROID)
495 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
496 {
497   float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
498   float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
499   int x = touch_x * overlay.grid_xsize;
500   int y = touch_y * overlay.grid_ysize;
501   int grid_button = overlay.grid_button[x][y];
502   Key key = GetKeyFromGridButton(grid_button);
503   int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
504
505   return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
506 }
507 #endif
508
509 void HandleButtonEvent(ButtonEvent *event)
510 {
511 #if DEBUG_EVENTS_BUTTON
512   Debug("event:button", "button %d %s, x/y %d/%d\n",
513         event->button,
514         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
515         event->x, event->y);
516 #endif
517
518   // for any mouse button event, disable playfield tile cursor
519   SetTileCursorEnabled(FALSE);
520
521 #if defined(HAS_SCREEN_KEYBOARD)
522   if (video.shifted_up)
523     event->y += video.shifted_up_pos;
524 #endif
525
526   motion_status = FALSE;
527
528   if (event->type == EVENT_BUTTONPRESS)
529     button_status = event->button;
530   else
531     button_status = MB_RELEASED;
532
533   HandleButton(event->x, event->y, button_status, event->button);
534 }
535
536 void HandleMotionEvent(MotionEvent *event)
537 {
538   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
539     return;
540
541   motion_status = TRUE;
542
543 #if DEBUG_EVENTS_MOTION
544   Debug("event:motion", "button %d moved, x/y %d/%d\n",
545         button_status, event->x, event->y);
546 #endif
547
548   HandleButton(event->x, event->y, button_status, button_status);
549 }
550
551 void HandleWheelEvent(WheelEvent *event)
552 {
553   int button_nr;
554
555 #if DEBUG_EVENTS_WHEEL
556 #if 1
557   Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
558         event->which, event->x, event->y);
559 #else
560   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
561   Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
562         event->which, event->x, event->y,
563         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
564          "SDL_MOUSEWHEEL_FLIPPED"));
565 #endif
566 #endif
567
568   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
569                event->x > 0 ? MB_WHEEL_RIGHT :
570                event->y < 0 ? MB_WHEEL_DOWN :
571                event->y > 0 ? MB_WHEEL_UP : 0);
572
573 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
574   // accelerated mouse wheel available on Mac and Windows
575   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
576 #else
577   // no accelerated mouse wheel available on Unix/Linux
578   wheel_steps = DEFAULT_WHEEL_STEPS;
579 #endif
580
581   motion_status = FALSE;
582
583   button_status = button_nr;
584   HandleButton(0, 0, button_status, -button_nr);
585
586   button_status = MB_RELEASED;
587   HandleButton(0, 0, button_status, -button_nr);
588 }
589
590 void HandleWindowEvent(WindowEvent *event)
591 {
592 #if DEBUG_EVENTS_WINDOW
593   int subtype = event->event;
594
595   char *event_name =
596     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
597      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
598      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
599      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
600      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
601      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
602      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
603      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
604      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
605      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
606      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
607      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
608      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
609      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
610      "(UNKNOWN)");
611
612   Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
613         event_name, event->data1, event->data2);
614 #endif
615
616 #if 0
617   // (not needed, as the screen gets redrawn every 20 ms anyway)
618   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
619       event->event == SDL_WINDOWEVENT_RESIZED ||
620       event->event == SDL_WINDOWEVENT_EXPOSED)
621     SDLRedrawWindow();
622 #endif
623
624   if (event->event == SDL_WINDOWEVENT_RESIZED)
625   {
626     if (!video.fullscreen_enabled)
627     {
628       int new_window_width  = event->data1;
629       int new_window_height = event->data2;
630
631       // if window size has changed after resizing, calculate new scaling factor
632       if (new_window_width  != video.window_width ||
633           new_window_height != video.window_height)
634       {
635         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
636         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
637
638         // (extreme window scaling allowed, but cannot be saved permanently)
639         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
640         setup.window_scaling_percent =
641           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
642               MAX_WINDOW_SCALING_PERCENT);
643
644         video.window_width  = new_window_width;
645         video.window_height = new_window_height;
646
647         if (game_status == GAME_MODE_SETUP)
648           RedrawSetupScreenAfterFullscreenToggle();
649
650         UpdateMousePosition();
651
652         SetWindowTitle();
653       }
654     }
655 #if defined(PLATFORM_ANDROID)
656     else
657     {
658       int new_display_width  = event->data1;
659       int new_display_height = event->data2;
660
661       // if fullscreen display size has changed, device has been rotated
662       if (new_display_width  != video.display_width ||
663           new_display_height != video.display_height)
664       {
665         int nr = GRID_ACTIVE_NR();      // previous screen orientation
666
667         video.display_width  = new_display_width;
668         video.display_height = new_display_height;
669
670         SDLSetScreenProperties();
671         SetGadgetsPosition_OverlayTouchButtons();
672
673         // check if screen orientation has changed (should always be true here)
674         if (nr != GRID_ACTIVE_NR())
675         {
676           if (game_status == GAME_MODE_SETUP)
677             RedrawSetupScreenAfterScreenRotation(nr);
678
679           SetOverlayGridSizeAndButtons();
680         }
681       }
682     }
683 #endif
684   }
685 }
686
687 #define NUM_TOUCH_FINGERS               3
688
689 static struct
690 {
691   boolean touched;
692   SDL_FingerID finger_id;
693   int counter;
694   Key key;
695   byte action;
696 } touch_info[NUM_TOUCH_FINGERS];
697
698 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
699                          Key key, byte action)
700 {
701   touch_info[pos].touched = (action != JOY_NO_ACTION);
702   touch_info[pos].finger_id = finger_id;
703   touch_info[pos].counter = counter;
704   touch_info[pos].key = key;
705   touch_info[pos].action = action;
706 }
707
708 static void ClearTouchInfo(void)
709 {
710   int i;
711
712   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
713     SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
714 }
715
716 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
717 {
718   int x = event->x * overlay.grid_xsize;
719   int y = event->y * overlay.grid_ysize;
720   int grid_button = overlay.grid_button[x][y];
721   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
722   Key key = GetKeyFromGridButton(grid_button);
723   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
724                     KEY_PRESSED);
725   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
726                            "KEY_PRESSED");
727   int i;
728
729   // for any touch input event, enable overlay buttons (if activated)
730   SetOverlayEnabled(TRUE);
731
732   Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
733         getKeyNameFromKey(key), key_status_name, event->fingerId);
734
735   if (key_status == KEY_PRESSED)
736     overlay.grid_button_action |= grid_button_action;
737   else
738     overlay.grid_button_action &= ~grid_button_action;
739
740   // check if we already know this touch event's finger id
741   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
742   {
743     if (touch_info[i].touched &&
744         touch_info[i].finger_id == event->fingerId)
745     {
746       // Debug("event:finger", "MARK 1: %d", i);
747
748       break;
749     }
750   }
751
752   if (i >= NUM_TOUCH_FINGERS)
753   {
754     if (key_status == KEY_PRESSED)
755     {
756       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
757
758       // unknown finger id -- get new, empty slot, if available
759       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
760       {
761         if (touch_info[i].counter < oldest_counter)
762         {
763           oldest_pos = i;
764           oldest_counter = touch_info[i].counter;
765
766           // Debug("event:finger", "MARK 2: %d", i);
767         }
768
769         if (!touch_info[i].touched)
770         {
771           // Debug("event:finger", "MARK 3: %d", i);
772
773           break;
774         }
775       }
776
777       if (i >= NUM_TOUCH_FINGERS)
778       {
779         // all slots allocated -- use oldest slot
780         i = oldest_pos;
781
782         // Debug("event:finger", "MARK 4: %d", i);
783       }
784     }
785     else
786     {
787       // release of previously unknown key (should not happen)
788
789       if (key != KSYM_UNDEFINED)
790       {
791         HandleKey(key, KEY_RELEASED);
792
793         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
794               getKeyNameFromKey(key), "KEY_RELEASED", i);
795       }
796     }
797   }
798
799   if (i < NUM_TOUCH_FINGERS)
800   {
801     if (key_status == KEY_PRESSED)
802     {
803       if (touch_info[i].key != key)
804       {
805         if (touch_info[i].key != KSYM_UNDEFINED)
806         {
807           HandleKey(touch_info[i].key, KEY_RELEASED);
808
809           // undraw previous grid button when moving finger away
810           overlay.grid_button_action &= ~touch_info[i].action;
811
812           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
813                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
814         }
815
816         if (key != KSYM_UNDEFINED)
817         {
818           HandleKey(key, KEY_PRESSED);
819
820           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
821                 getKeyNameFromKey(key), "KEY_PRESSED", i);
822         }
823       }
824
825       SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
826     }
827     else
828     {
829       if (touch_info[i].key != KSYM_UNDEFINED)
830       {
831         HandleKey(touch_info[i].key, KEY_RELEASED);
832
833         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
834               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
835       }
836
837       SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
838     }
839   }
840 }
841
842 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
843 {
844   static Key motion_key_x = KSYM_UNDEFINED;
845   static Key motion_key_y = KSYM_UNDEFINED;
846   static Key button_key = KSYM_UNDEFINED;
847   static float motion_x1, motion_y1;
848   static float button_x1, button_y1;
849   static SDL_FingerID motion_id = -1;
850   static SDL_FingerID button_id = -1;
851   int move_trigger_distance_percent = setup.touch.move_distance;
852   int drop_trigger_distance_percent = setup.touch.drop_distance;
853   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
854   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
855   float event_x = event->x;
856   float event_y = event->y;
857
858   if (event->type == EVENT_FINGERPRESS)
859   {
860     if (event_x > 1.0 / 3.0)
861     {
862       // motion area
863
864       motion_id = event->fingerId;
865
866       motion_x1 = event_x;
867       motion_y1 = event_y;
868
869       motion_key_x = KSYM_UNDEFINED;
870       motion_key_y = KSYM_UNDEFINED;
871
872       Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
873     }
874     else
875     {
876       // button area
877
878       button_id = event->fingerId;
879
880       button_x1 = event_x;
881       button_y1 = event_y;
882
883       button_key = setup.input[0].key.snap;
884
885       HandleKey(button_key, KEY_PRESSED);
886
887       Debug("event:finger", "---------- SNAP STARTED ----------");
888     }
889   }
890   else if (event->type == EVENT_FINGERRELEASE)
891   {
892     if (event->fingerId == motion_id)
893     {
894       motion_id = -1;
895
896       if (motion_key_x != KSYM_UNDEFINED)
897         HandleKey(motion_key_x, KEY_RELEASED);
898       if (motion_key_y != KSYM_UNDEFINED)
899         HandleKey(motion_key_y, KEY_RELEASED);
900
901       motion_key_x = KSYM_UNDEFINED;
902       motion_key_y = KSYM_UNDEFINED;
903
904       Debug("event:finger", "---------- MOVE STOPPED ----------");
905     }
906     else if (event->fingerId == button_id)
907     {
908       button_id = -1;
909
910       if (button_key != KSYM_UNDEFINED)
911         HandleKey(button_key, KEY_RELEASED);
912
913       button_key = KSYM_UNDEFINED;
914
915       Debug("event:finger", "---------- SNAP STOPPED ----------");
916     }
917   }
918   else if (event->type == EVENT_FINGERMOTION)
919   {
920     if (event->fingerId == motion_id)
921     {
922       float distance_x = ABS(event_x - motion_x1);
923       float distance_y = ABS(event_y - motion_y1);
924       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
925                               event_x > motion_x1 ? setup.input[0].key.right :
926                               KSYM_UNDEFINED);
927       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
928                               event_y > motion_y1 ? setup.input[0].key.down :
929                               KSYM_UNDEFINED);
930
931       if (distance_x < move_trigger_distance / 2 ||
932           distance_x < distance_y)
933         new_motion_key_x = KSYM_UNDEFINED;
934
935       if (distance_y < move_trigger_distance / 2 ||
936           distance_y < distance_x)
937         new_motion_key_y = KSYM_UNDEFINED;
938
939       if (distance_x > move_trigger_distance ||
940           distance_y > move_trigger_distance)
941       {
942         if (new_motion_key_x != motion_key_x)
943         {
944           if (motion_key_x != KSYM_UNDEFINED)
945             HandleKey(motion_key_x, KEY_RELEASED);
946           if (new_motion_key_x != KSYM_UNDEFINED)
947             HandleKey(new_motion_key_x, KEY_PRESSED);
948         }
949
950         if (new_motion_key_y != motion_key_y)
951         {
952           if (motion_key_y != KSYM_UNDEFINED)
953             HandleKey(motion_key_y, KEY_RELEASED);
954           if (new_motion_key_y != KSYM_UNDEFINED)
955             HandleKey(new_motion_key_y, KEY_PRESSED);
956         }
957
958         motion_x1 = event_x;
959         motion_y1 = event_y;
960
961         motion_key_x = new_motion_key_x;
962         motion_key_y = new_motion_key_y;
963
964         Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
965       }
966     }
967     else if (event->fingerId == button_id)
968     {
969       float distance_x = ABS(event_x - button_x1);
970       float distance_y = ABS(event_y - button_y1);
971
972       if (distance_x < drop_trigger_distance / 2 &&
973           distance_y > drop_trigger_distance)
974       {
975         if (button_key == setup.input[0].key.snap)
976           HandleKey(button_key, KEY_RELEASED);
977
978         button_x1 = event_x;
979         button_y1 = event_y;
980
981         button_key = setup.input[0].key.drop;
982
983         HandleKey(button_key, KEY_PRESSED);
984
985         Debug("event:finger", "---------- DROP STARTED ----------");
986       }
987     }
988   }
989 }
990
991 void HandleFingerEvent(FingerEvent *event)
992 {
993 #if DEBUG_EVENTS_FINGER
994   Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
995         event->type == EVENT_FINGERPRESS ? "pressed" :
996         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
997         event->touchId,
998         event->fingerId,
999         event->x, event->y,
1000         event->dx, event->dy,
1001         event->pressure);
1002 #endif
1003
1004   runtime.uses_touch_device = TRUE;
1005
1006   if (game_status != GAME_MODE_PLAYING)
1007     return;
1008
1009   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1010   {
1011     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1012       local_player->mouse_action.button_hint =
1013         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1014          event->x < 0.5                     ? MB_LEFTBUTTON  :
1015          event->x > 0.5                     ? MB_RIGHTBUTTON :
1016          MB_NOT_PRESSED);
1017
1018     return;
1019   }
1020
1021   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1022     HandleFingerEvent_VirtualButtons(event);
1023   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1024     HandleFingerEvent_WipeGestures(event);
1025 }
1026
1027 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1028 {
1029   static int old_mx = 0, old_my = 0;
1030   static int last_button = MB_LEFTBUTTON;
1031   static boolean touched = FALSE;
1032   static boolean tapped = FALSE;
1033
1034   // screen tile was tapped (but finger not touching the screen anymore)
1035   // (this point will also be reached without receiving a touch event)
1036   if (tapped && !touched)
1037   {
1038     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1039
1040     tapped = FALSE;
1041   }
1042
1043   // stop here if this function was not triggered by a touch event
1044   if (button == -1)
1045     return;
1046
1047   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1048   {
1049     // finger started touching the screen
1050
1051     touched = TRUE;
1052     tapped = TRUE;
1053
1054     if (!motion_status)
1055     {
1056       old_mx = mx;
1057       old_my = my;
1058
1059       ClearPlayerMouseAction();
1060
1061       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1062     }
1063   }
1064   else if (button == MB_RELEASED && touched)
1065   {
1066     // finger stopped touching the screen
1067
1068     touched = FALSE;
1069
1070     if (tapped)
1071       SetPlayerMouseAction(old_mx, old_my, last_button);
1072     else
1073       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1074
1075     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1076   }
1077
1078   if (touched)
1079   {
1080     // finger moved while touching the screen
1081
1082     int old_x = getLevelFromScreenX(old_mx);
1083     int old_y = getLevelFromScreenY(old_my);
1084     int new_x = getLevelFromScreenX(mx);
1085     int new_y = getLevelFromScreenY(my);
1086
1087     if (new_x != old_x || new_y != old_y)
1088       tapped = FALSE;
1089
1090     if (new_x != old_x)
1091     {
1092       // finger moved left or right from (horizontal) starting position
1093
1094       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1095
1096       SetPlayerMouseAction(old_mx, old_my, button_nr);
1097
1098       last_button = button_nr;
1099
1100       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1101     }
1102     else
1103     {
1104       // finger stays at or returned to (horizontal) starting position
1105
1106       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1107
1108       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1109     }
1110   }
1111 }
1112
1113 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1114 {
1115   static int old_mx = 0, old_my = 0;
1116   static int last_button = MB_LEFTBUTTON;
1117   static boolean touched = FALSE;
1118   static boolean tapped = FALSE;
1119
1120   // screen tile was tapped (but finger not touching the screen anymore)
1121   // (this point will also be reached without receiving a touch event)
1122   if (tapped && !touched)
1123   {
1124     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1125
1126     tapped = FALSE;
1127   }
1128
1129   // stop here if this function was not triggered by a touch event
1130   if (button == -1)
1131     return;
1132
1133   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1134   {
1135     // finger started touching the screen
1136
1137     touched = TRUE;
1138     tapped = TRUE;
1139
1140     if (!motion_status)
1141     {
1142       old_mx = mx;
1143       old_my = my;
1144
1145       ClearPlayerMouseAction();
1146
1147       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1148     }
1149   }
1150   else if (button == MB_RELEASED && touched)
1151   {
1152     // finger stopped touching the screen
1153
1154     touched = FALSE;
1155
1156     if (tapped)
1157       SetPlayerMouseAction(old_mx, old_my, last_button);
1158     else
1159       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1160
1161     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1162   }
1163
1164   if (touched)
1165   {
1166     // finger moved while touching the screen
1167
1168     int old_x = getLevelFromScreenX(old_mx);
1169     int old_y = getLevelFromScreenY(old_my);
1170     int new_x = getLevelFromScreenX(mx);
1171     int new_y = getLevelFromScreenY(my);
1172
1173     if (new_x != old_x || new_y != old_y)
1174     {
1175       // finger moved away from starting position
1176
1177       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1178
1179       // quickly alternate between clicking and releasing for maximum speed
1180       if (FrameCounter % 2 == 0)
1181         button_nr = MB_RELEASED;
1182
1183       SetPlayerMouseAction(old_mx, old_my, button_nr);
1184
1185       if (button_nr)
1186         last_button = button_nr;
1187
1188       tapped = FALSE;
1189
1190       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1191     }
1192     else
1193     {
1194       // finger stays at or returned to starting position
1195
1196       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1197
1198       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1199     }
1200   }
1201 }
1202
1203 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1204 {
1205   static int old_mx = 0, old_my = 0;
1206   static Key motion_key_x = KSYM_UNDEFINED;
1207   static Key motion_key_y = KSYM_UNDEFINED;
1208   static boolean touched = FALSE;
1209   static boolean started_on_player = FALSE;
1210   static boolean player_is_dropping = FALSE;
1211   static int player_drop_count = 0;
1212   static int last_player_x = -1;
1213   static int last_player_y = -1;
1214
1215   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1216   {
1217     touched = TRUE;
1218
1219     old_mx = mx;
1220     old_my = my;
1221
1222     if (!motion_status)
1223     {
1224       started_on_player = FALSE;
1225       player_is_dropping = FALSE;
1226       player_drop_count = 0;
1227       last_player_x = -1;
1228       last_player_y = -1;
1229
1230       motion_key_x = KSYM_UNDEFINED;
1231       motion_key_y = KSYM_UNDEFINED;
1232
1233       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1234     }
1235   }
1236   else if (button == MB_RELEASED && touched)
1237   {
1238     touched = FALSE;
1239
1240     old_mx = 0;
1241     old_my = 0;
1242
1243     if (motion_key_x != KSYM_UNDEFINED)
1244       HandleKey(motion_key_x, KEY_RELEASED);
1245     if (motion_key_y != KSYM_UNDEFINED)
1246       HandleKey(motion_key_y, KEY_RELEASED);
1247
1248     if (started_on_player)
1249     {
1250       if (player_is_dropping)
1251       {
1252         Debug("event:finger", "---------- DROP STOPPED ----------");
1253
1254         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1255       }
1256       else
1257       {
1258         Debug("event:finger", "---------- SNAP STOPPED ----------");
1259
1260         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1261       }
1262     }
1263
1264     motion_key_x = KSYM_UNDEFINED;
1265     motion_key_y = KSYM_UNDEFINED;
1266
1267     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1268   }
1269
1270   if (touched)
1271   {
1272     int src_x = local_player->jx;
1273     int src_y = local_player->jy;
1274     int dst_x = getLevelFromScreenX(old_mx);
1275     int dst_y = getLevelFromScreenY(old_my);
1276     int dx = dst_x - src_x;
1277     int dy = dst_y - src_y;
1278     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1279                             dx > 0 ? setup.input[0].key.right :
1280                             KSYM_UNDEFINED);
1281     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1282                             dy > 0 ? setup.input[0].key.down :
1283                             KSYM_UNDEFINED);
1284
1285     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1286         (last_player_x != local_player->jx ||
1287          last_player_y != local_player->jy))
1288     {
1289       // in case of asymmetric diagonal movement, use "preferred" direction
1290
1291       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1292
1293       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1294         game_em.ply[0]->last_move_dir = last_move_dir;
1295       else
1296         local_player->last_move_dir = last_move_dir;
1297
1298       // (required to prevent accidentally forcing direction for next movement)
1299       last_player_x = local_player->jx;
1300       last_player_y = local_player->jy;
1301     }
1302
1303     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1304     {
1305       started_on_player = TRUE;
1306       player_drop_count = getPlayerInventorySize(0);
1307       player_is_dropping = (player_drop_count > 0);
1308
1309       if (player_is_dropping)
1310       {
1311         Debug("event:finger", "---------- DROP STARTED ----------");
1312
1313         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1314       }
1315       else
1316       {
1317         Debug("event:finger", "---------- SNAP STARTED ----------");
1318
1319         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1320       }
1321     }
1322     else if (dx != 0 || dy != 0)
1323     {
1324       if (player_is_dropping &&
1325           player_drop_count == getPlayerInventorySize(0))
1326       {
1327         Debug("event:finger", "---------- DROP -> SNAP ----------");
1328
1329         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1330         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1331
1332         player_is_dropping = FALSE;
1333       }
1334     }
1335
1336     if (new_motion_key_x != motion_key_x)
1337     {
1338       Debug("event:finger", "---------- %s %s ----------",
1339             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1340             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1341
1342       if (motion_key_x != KSYM_UNDEFINED)
1343         HandleKey(motion_key_x, KEY_RELEASED);
1344       if (new_motion_key_x != KSYM_UNDEFINED)
1345         HandleKey(new_motion_key_x, KEY_PRESSED);
1346     }
1347
1348     if (new_motion_key_y != motion_key_y)
1349     {
1350       Debug("event:finger", "---------- %s %s ----------",
1351             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1352             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1353
1354       if (motion_key_y != KSYM_UNDEFINED)
1355         HandleKey(motion_key_y, KEY_RELEASED);
1356       if (new_motion_key_y != KSYM_UNDEFINED)
1357         HandleKey(new_motion_key_y, KEY_PRESSED);
1358     }
1359
1360     motion_key_x = new_motion_key_x;
1361     motion_key_y = new_motion_key_y;
1362   }
1363 }
1364
1365 static void HandleButtonOrFinger(int mx, int my, int button)
1366 {
1367   boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1368
1369   if (game_status != GAME_MODE_PLAYING)
1370     return;
1371
1372   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1373   {
1374     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1375       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1376     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1378     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1379       SetPlayerMouseAction(mx, my, button);     // special case
1380   }
1381   else
1382   {
1383     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1384       HandleButtonOrFinger_FollowFinger(mx, my, button);
1385     else if (game.use_mouse_actions && valid_mouse_event)
1386       SetPlayerMouseAction(mx, my, button);
1387   }
1388 }
1389
1390 static boolean checkTextInputKey(Key key)
1391 {
1392   // when playing, only handle raw key events and ignore text input
1393   if (game_status == GAME_MODE_PLAYING)
1394     return FALSE;
1395
1396   // if Shift or right Alt key is pressed, handle key as text input
1397   if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1398     return TRUE;
1399
1400   // ignore raw keys as text input when not in text input mode
1401   if (KSYM_RAW(key) && !textinput_status)
1402     return FALSE;
1403
1404   // else handle all printable keys as text input
1405   return KSYM_PRINTABLE(key);
1406 }
1407
1408 void HandleTextEvent(TextEvent *event)
1409 {
1410   char *text = event->text;
1411   Key key = getKeyFromKeyName(text);
1412
1413 #if DEBUG_EVENTS_TEXT
1414   Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1415         text,
1416         strlen(text),
1417         text[0], (int)(text[0]),
1418         key,
1419         getKeyNameFromKey(key),
1420         GetKeyModState());
1421 #endif
1422
1423   if (checkTextInputKey(key))
1424   {
1425     // process printable keys (with uppercase etc.) in text input mode
1426     HandleKey(key, KEY_PRESSED);
1427     HandleKey(key, KEY_RELEASED);
1428   }
1429 }
1430
1431 void HandlePauseResumeEvent(PauseResumeEvent *event)
1432 {
1433   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1434   {
1435     Mix_PauseMusic();
1436   }
1437   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1438   {
1439     Mix_ResumeMusic();
1440   }
1441 }
1442
1443 void HandleKeyEvent(KeyEvent *event)
1444 {
1445   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1446   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1447   Key key = GetEventKey(event, with_modifiers);
1448   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1449
1450 #if DEBUG_EVENTS_KEY
1451   Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1452         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1453         event->keysym.scancode,
1454         event->keysym.sym,
1455         keymod,
1456         GetKeyModState(),
1457         key,
1458         getKeyNameFromKey(key));
1459 #endif
1460
1461 #if defined(PLATFORM_ANDROID)
1462   if (key == KSYM_Back)
1463   {
1464     // always map the "back" button to the "escape" key on Android devices
1465     key = KSYM_Escape;
1466   }
1467   else if (key == KSYM_Menu)
1468   {
1469     // the "menu" button can be used to toggle displaying virtual buttons
1470     if (key_status == KEY_PRESSED)
1471       SetOverlayEnabled(!GetOverlayEnabled());
1472   }
1473   else
1474   {
1475     // for any other "real" key event, disable virtual buttons
1476     SetOverlayEnabled(FALSE);
1477
1478     // for any other "real" key event, disable overlay touch buttons
1479     runtime.uses_touch_device = FALSE;
1480   }
1481 #endif
1482
1483   HandleKeyModState(keymod, key_status);
1484
1485   // process all keys if not in text input mode or if non-printable keys
1486   if (!checkTextInputKey(key))
1487     HandleKey(key, key_status);
1488 }
1489
1490 static int HandleDropFileEvent(char *filename)
1491 {
1492   Debug("event:dropfile", "filename == '%s'", filename);
1493
1494   // check and extract dropped zip files into correct user data directory
1495   if (!strSuffixLower(filename, ".zip"))
1496   {
1497     Warn("file '%s' not supported", filename);
1498
1499     return TREE_TYPE_UNDEFINED;
1500   }
1501
1502   TreeInfo *tree_node = NULL;
1503   int tree_type = GetZipFileTreeType(filename);
1504   char *directory = TREE_USERDIR(tree_type);
1505
1506   if (directory == NULL)
1507   {
1508     Warn("zip file '%s' has invalid content!", filename);
1509
1510     return TREE_TYPE_UNDEFINED;
1511   }
1512
1513   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1514       game_status == GAME_MODE_LEVELS &&
1515       leveldir_current->node_parent != NULL)
1516   {
1517     // extract new level set next to currently selected level set
1518     tree_node = leveldir_current;
1519
1520     // get parent directory of currently selected level set directory
1521     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1522
1523     // use private level directory instead of top-level package level directory
1524     if (strPrefix(directory, options.level_directory) &&
1525         strEqual(leveldir_current->node_parent->fullpath, "."))
1526       directory = getUserLevelDir(NULL);
1527   }
1528
1529   // extract level or artwork set from zip file to target directory
1530   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1531
1532   if (top_dir == NULL)
1533   {
1534     // error message already issued by "ExtractZipFileIntoDirectory()"
1535
1536     return TREE_TYPE_UNDEFINED;
1537   }
1538
1539   // add extracted level or artwork set to tree info structure
1540   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1541
1542   // update menu screen (and possibly change current level set)
1543   DrawScreenAfterAddingSet(top_dir, tree_type);
1544
1545   return tree_type;
1546 }
1547
1548 static void HandleDropTextEvent(char *text)
1549 {
1550   Debug("event:droptext", "text == '%s'", text);
1551 }
1552
1553 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1554                                     int num_artwork_sets_succeeded,
1555                                     int num_files_failed)
1556 {
1557   // only show request dialog if no other request dialog already active
1558   if (game.request_active)
1559     return;
1560
1561   // this case can happen with drag-and-drop with older SDL versions
1562   if (num_level_sets_succeeded == 0 &&
1563       num_artwork_sets_succeeded == 0 &&
1564       num_files_failed == 0)
1565     return;
1566
1567   char message[100];
1568
1569   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1570   {
1571     char message_part1[50];
1572
1573     sprintf(message_part1, "New %s set%s added",
1574             (num_artwork_sets_succeeded == 0 ? "level" :
1575              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1576             (num_level_sets_succeeded +
1577              num_artwork_sets_succeeded > 1 ? "s" : ""));
1578
1579     if (num_files_failed > 0)
1580       sprintf(message, "%s, but %d dropped file%s failed!",
1581               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1582     else
1583       sprintf(message, "%s!", message_part1);
1584   }
1585   else if (num_files_failed > 0)
1586   {
1587     sprintf(message, "Failed to process dropped file%s!",
1588             num_files_failed > 1 ? "s" : "");
1589   }
1590
1591   Request(message, REQ_CONFIRM);
1592 }
1593
1594 void HandleDropEvent(Event *event)
1595 {
1596   static boolean confirm_on_drop_complete = FALSE;
1597   static int num_level_sets_succeeded = 0;
1598   static int num_artwork_sets_succeeded = 0;
1599   static int num_files_failed = 0;
1600
1601   switch (event->type)
1602   {
1603     case SDL_DROPBEGIN:
1604     {
1605       confirm_on_drop_complete = TRUE;
1606       num_level_sets_succeeded = 0;
1607       num_artwork_sets_succeeded = 0;
1608       num_files_failed = 0;
1609
1610       break;
1611     }
1612
1613     case SDL_DROPFILE:
1614     {
1615       int tree_type = HandleDropFileEvent(event->drop.file);
1616
1617       if (tree_type == TREE_TYPE_LEVEL_DIR)
1618         num_level_sets_succeeded++;
1619       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1620                tree_type == TREE_TYPE_SOUNDS_DIR ||
1621                tree_type == TREE_TYPE_MUSIC_DIR)
1622         num_artwork_sets_succeeded++;
1623       else
1624         num_files_failed++;
1625
1626       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1627       if (!confirm_on_drop_complete)
1628       {
1629         // process all remaining events, including further SDL_DROPFILE events
1630         ClearEventQueue();
1631
1632         HandleDropCompleteEvent(num_level_sets_succeeded,
1633                                 num_artwork_sets_succeeded,
1634                                 num_files_failed);
1635
1636         num_level_sets_succeeded = 0;
1637         num_artwork_sets_succeeded = 0;
1638         num_files_failed = 0;
1639       }
1640
1641       break;
1642     }
1643
1644     case SDL_DROPTEXT:
1645     {
1646       HandleDropTextEvent(event->drop.file);
1647
1648       break;
1649     }
1650
1651     case SDL_DROPCOMPLETE:
1652     {
1653       HandleDropCompleteEvent(num_level_sets_succeeded,
1654                               num_artwork_sets_succeeded,
1655                               num_files_failed);
1656
1657       break;
1658     }
1659   }
1660
1661   if (event->drop.file != NULL)
1662     SDL_free(event->drop.file);
1663 }
1664
1665 void HandleUserEvent(UserEvent *event)
1666 {
1667   switch (event->code)
1668   {
1669     case USEREVENT_ANIM_DELAY_ACTION:
1670     case USEREVENT_ANIM_EVENT_ACTION:
1671       // execute action functions until matching action was found
1672       if (DoKeysymAction(event->value1) ||
1673           DoGadgetAction(event->value1) ||
1674           DoScreenAction(event->value1))
1675         return;
1676       break;
1677
1678     default:
1679       break;
1680   }
1681 }
1682
1683 void HandleButton(int mx, int my, int button, int button_nr)
1684 {
1685   static int old_mx = 0, old_my = 0;
1686   boolean button_hold = FALSE;
1687   boolean handle_gadgets = TRUE;
1688
1689   if (button_nr < 0)
1690   {
1691     mx = old_mx;
1692     my = old_my;
1693     button_nr = -button_nr;
1694     button_hold = TRUE;
1695   }
1696   else
1697   {
1698     old_mx = mx;
1699     old_my = my;
1700   }
1701
1702 #if defined(PLATFORM_ANDROID)
1703   // when playing, only handle gadgets when using "follow finger" controls
1704   // or when using touch controls in combination with the MM game engine
1705   // or when using gadgets that do not overlap with virtual buttons
1706   handle_gadgets =
1707     (game_status != GAME_MODE_PLAYING ||
1708      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1709      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1710      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1711       !CheckVirtualButtonPressed(mx, my, button)));
1712
1713   // always recognize potentially releasing already pressed gadgets
1714   if (button == MB_RELEASED)
1715     handle_gadgets = TRUE;
1716
1717   // always recognize pressing or releasing overlay touch buttons
1718   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1719     handle_gadgets = TRUE;
1720 #endif
1721
1722   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1723   {
1724     // do not handle this button event anymore
1725     return;             // force mouse event not to be handled at all
1726   }
1727
1728   if (handle_gadgets && HandleGadgets(mx, my, button))
1729   {
1730     // do not handle this button event anymore
1731     mx = my = -32;      // force mouse event to be outside screen tiles
1732   }
1733
1734   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1735     return;
1736
1737   // do not use scroll wheel button events for anything other than gadgets
1738   if (IS_WHEEL_BUTTON(button_nr))
1739     return;
1740
1741   switch (game_status)
1742   {
1743     case GAME_MODE_TITLE:
1744       HandleTitleScreen(mx, my, 0, 0, button);
1745       break;
1746
1747     case GAME_MODE_MAIN:
1748       HandleMainMenu(mx, my, 0, 0, button);
1749       break;
1750
1751     case GAME_MODE_PSEUDO_TYPENAME:
1752       HandleTypeName(KSYM_Return);
1753       break;
1754
1755     case GAME_MODE_LEVELS:
1756       HandleChooseLevelSet(mx, my, 0, 0, button);
1757       break;
1758
1759     case GAME_MODE_LEVELNR:
1760       HandleChooseLevelNr(mx, my, 0, 0, button);
1761       break;
1762
1763     case GAME_MODE_SCORES:
1764       HandleHallOfFame(0, 0, 0, 0, button);
1765       break;
1766
1767     case GAME_MODE_EDITOR:
1768       HandleLevelEditorIdle();
1769       break;
1770
1771     case GAME_MODE_INFO:
1772       HandleInfoScreen(mx, my, 0, 0, button);
1773       break;
1774
1775     case GAME_MODE_SETUP:
1776       HandleSetupScreen(mx, my, 0, 0, button);
1777       break;
1778
1779     case GAME_MODE_PLAYING:
1780       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1781         HandleButtonOrFinger(mx, my, button);
1782       else
1783         SetPlayerMouseAction(mx, my, button);
1784
1785 #ifdef DEBUG
1786       if (button == MB_PRESSED && !motion_status && !button_hold &&
1787           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1788         DumpTileFromScreen(mx, my);
1789 #endif
1790
1791       break;
1792
1793     default:
1794       break;
1795   }
1796 }
1797
1798 #define MAX_CHEAT_INPUT_LEN     32
1799
1800 static void HandleKeysSpecial(Key key)
1801 {
1802   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1803   char letter = getCharFromKey(key);
1804   int cheat_input_len = strlen(cheat_input);
1805   int i;
1806
1807   if (letter == 0)
1808     return;
1809
1810   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1811   {
1812     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1813       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1814
1815     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1816   }
1817
1818   cheat_input[cheat_input_len++] = letter;
1819   cheat_input[cheat_input_len] = '\0';
1820
1821 #if DEBUG_EVENTS_KEY
1822   Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1823 #endif
1824
1825   if (game_status == GAME_MODE_MAIN)
1826   {
1827     if (strSuffix(cheat_input, ":insert-solution-tape") ||
1828         strSuffix(cheat_input, ":ist"))
1829     {
1830       InsertSolutionTape();
1831     }
1832     else if (strSuffix(cheat_input, ":play-solution-tape") ||
1833              strSuffix(cheat_input, ":pst"))
1834     {
1835       PlaySolutionTape();
1836     }
1837     else if (strSuffix(cheat_input, ":reload-graphics") ||
1838              strSuffix(cheat_input, ":rg"))
1839     {
1840       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1841       DrawMainMenu();
1842     }
1843     else if (strSuffix(cheat_input, ":reload-sounds") ||
1844              strSuffix(cheat_input, ":rs"))
1845     {
1846       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1847       DrawMainMenu();
1848     }
1849     else if (strSuffix(cheat_input, ":reload-music") ||
1850              strSuffix(cheat_input, ":rm"))
1851     {
1852       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1853       DrawMainMenu();
1854     }
1855     else if (strSuffix(cheat_input, ":reload-artwork") ||
1856              strSuffix(cheat_input, ":ra"))
1857     {
1858       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1859                           1 << ARTWORK_TYPE_SOUNDS |
1860                           1 << ARTWORK_TYPE_MUSIC);
1861       DrawMainMenu();
1862     }
1863     else if (strSuffix(cheat_input, ":dump-level") ||
1864              strSuffix(cheat_input, ":dl"))
1865     {
1866       DumpLevel(&level);
1867     }
1868     else if (strSuffix(cheat_input, ":dump-tape") ||
1869              strSuffix(cheat_input, ":dt"))
1870     {
1871       DumpTape(&tape);
1872     }
1873     else if (strSuffix(cheat_input, ":undo-tape") ||
1874              strSuffix(cheat_input, ":ut"))
1875     {
1876       UndoTape();
1877     }
1878     else if (strSuffix(cheat_input, ":fix-tape") ||
1879              strSuffix(cheat_input, ":ft"))
1880     {
1881       FixTape_ForceSinglePlayer();
1882     }
1883     else if (strSuffix(cheat_input, ":save-native-level") ||
1884              strSuffix(cheat_input, ":snl"))
1885     {
1886       SaveNativeLevel(&level);
1887     }
1888     else if (strSuffix(cheat_input, ":frames-per-second") ||
1889              strSuffix(cheat_input, ":fps"))
1890     {
1891       global.show_frames_per_second = !global.show_frames_per_second;
1892     }
1893     else if (strSuffix(cheat_input, ":xsn"))
1894     {
1895       tile_cursor.xsn_debug = TRUE;
1896     }
1897   }
1898   else if (game_status == GAME_MODE_PLAYING)
1899   {
1900 #ifdef DEBUG
1901     if (strSuffix(cheat_input, ".q"))
1902       DEBUG_SetMaximumDynamite();
1903 #endif
1904   }
1905   else if (game_status == GAME_MODE_EDITOR)
1906   {
1907     if (strSuffix(cheat_input, ":dump-brush") ||
1908         strSuffix(cheat_input, ":DB"))
1909     {
1910       DumpBrush();
1911     }
1912     else if (strSuffix(cheat_input, ":DDB"))
1913     {
1914       DumpBrush_Small();
1915     }
1916
1917     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1918     {
1919       if (letter == 'x')        // copy brush to clipboard (small size)
1920       {
1921         CopyBrushToClipboard_Small();
1922       }
1923       else if (letter == 'c')   // copy brush to clipboard (normal size)
1924       {
1925         CopyBrushToClipboard();
1926       }
1927       else if (letter == 'v')   // paste brush from Clipboard
1928       {
1929         CopyClipboardToBrush();
1930       }
1931       else if (letter == 'z')   // undo or redo last operation
1932       {
1933         if (GetKeyModState() & KMOD_Shift)
1934           RedoLevelEditorOperation();
1935         else
1936           UndoLevelEditorOperation();
1937       }
1938     }
1939   }
1940
1941   // special key shortcuts for all game modes
1942   if (strSuffix(cheat_input, ":dump-event-actions") ||
1943       strSuffix(cheat_input, ":dea") ||
1944       strSuffix(cheat_input, ":DEA"))
1945   {
1946     DumpGadgetIdentifiers();
1947     DumpScreenIdentifiers();
1948   }
1949 }
1950
1951 boolean HandleKeysDebug(Key key, int key_status)
1952 {
1953 #ifdef DEBUG
1954   int i;
1955
1956   if (key_status != KEY_PRESSED)
1957     return FALSE;
1958
1959   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1960   {
1961     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1962
1963     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1964     {
1965       if (key == setup.debug.frame_delay_key[i] &&
1966           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1967       {
1968         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1969                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1970
1971         if (!setup.debug.frame_delay_game_only)
1972           MenuFrameDelay = GameFrameDelay;
1973
1974         SetVideoFrameDelay(GameFrameDelay);
1975
1976         if (GameFrameDelay > ONE_SECOND_DELAY)
1977           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1978         else if (GameFrameDelay != 0)
1979           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1980                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1981                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1982         else
1983           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1984
1985         return TRUE;
1986       }
1987     }
1988   }
1989
1990   if (game_status == GAME_MODE_PLAYING)
1991   {
1992     if (key == KSYM_d)
1993     {
1994       options.debug = !options.debug;
1995
1996       Debug("event:key:debug", "debug mode %s",
1997             (options.debug ? "enabled" : "disabled"));
1998
1999       return TRUE;
2000     }
2001     else if (key == KSYM_v)
2002     {
2003       Debug("event:key:debug", "currently using game engine version %d",
2004             game.engine_version);
2005
2006       return TRUE;
2007     }
2008   }
2009 #endif
2010
2011   return FALSE;
2012 }
2013
2014 void HandleKey(Key key, int key_status)
2015 {
2016   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2017   static boolean ignore_repeated_key = FALSE;
2018   static struct SetupKeyboardInfo ski;
2019   static struct SetupShortcutInfo ssi;
2020   static struct
2021   {
2022     Key *key_custom;
2023     Key *key_snap;
2024     Key key_default;
2025     byte action;
2026   } key_info[] =
2027   {
2028     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2029     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2030     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2031     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2032     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2033     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2034   };
2035   int joy = 0;
2036   int i;
2037
2038   if (HandleKeysDebug(key, key_status))
2039     return;             // do not handle already processed keys again
2040
2041   // map special keys (media keys / remote control buttons) to default keys
2042   if (key == KSYM_PlayPause)
2043     key = KSYM_space;
2044   else if (key == KSYM_Select)
2045     key = KSYM_Return;
2046
2047   HandleSpecialGameControllerKeys(key, key_status);
2048
2049   if (game_status == GAME_MODE_PLAYING)
2050   {
2051     // only needed for single-step tape recording mode
2052     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2053     int pnr;
2054
2055     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2056     {
2057       byte key_action = 0;
2058       byte key_snap_action = 0;
2059
2060       if (setup.input[pnr].use_joystick)
2061         continue;
2062
2063       ski = setup.input[pnr].key;
2064
2065       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2066         if (key == *key_info[i].key_custom)
2067           key_action |= key_info[i].action;
2068
2069       // use combined snap+direction keys for the first player only
2070       if (pnr == 0)
2071       {
2072         ssi = setup.shortcut;
2073
2074         // also remember normal snap key when handling snap+direction keys
2075         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2076
2077         for (i = 0; i < NUM_DIRECTIONS; i++)
2078         {
2079           if (key == *key_info[i].key_snap)
2080           {
2081             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2082             key_snap_action |= key_info[i].action;
2083           }
2084         }
2085       }
2086
2087       if (key_status == KEY_PRESSED)
2088       {
2089         stored_player[pnr].action      |= key_action;
2090         stored_player[pnr].snap_action |= key_snap_action;
2091       }
2092       else
2093       {
2094         stored_player[pnr].action      &= ~key_action;
2095         stored_player[pnr].snap_action &= ~key_snap_action;
2096       }
2097
2098       // restore snap action if one of several pressed snap keys was released
2099       if (stored_player[pnr].snap_action)
2100         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2101
2102       if (tape.recording && tape.pausing && tape.use_key_actions)
2103       {
2104         if (tape.single_step)
2105         {
2106           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2107           {
2108             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2109
2110             // if snap key already pressed, keep pause mode when releasing
2111             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2112               has_snapped[pnr] = TRUE;
2113           }
2114           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2115           {
2116             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2117
2118             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2119                 getRedDiskReleaseFlag_SP() == 0)
2120             {
2121               // add a single inactive frame before dropping starts
2122               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2123               stored_player[pnr].force_dropping = TRUE;
2124             }
2125           }
2126           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2127           {
2128             // if snap key was pressed without direction, leave pause mode
2129             if (!has_snapped[pnr])
2130               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2131
2132             has_snapped[pnr] = FALSE;
2133           }
2134         }
2135         else
2136         {
2137           // prevent key release events from un-pausing a paused game
2138           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2139             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2140         }
2141       }
2142
2143       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2144       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2145         joy |= key_action;
2146     }
2147   }
2148   else
2149   {
2150     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2151       if (key == key_info[i].key_default)
2152         joy |= key_info[i].action;
2153   }
2154
2155   if (joy)
2156   {
2157     if (key_status == KEY_PRESSED)
2158       key_joystick_mapping |= joy;
2159     else
2160       key_joystick_mapping &= ~joy;
2161
2162     HandleJoystick();
2163   }
2164
2165   if (game_status != GAME_MODE_PLAYING)
2166     key_joystick_mapping = 0;
2167
2168   if (key_status == KEY_RELEASED)
2169   {
2170     // reset flag to ignore repeated "key pressed" events after key release
2171     ignore_repeated_key = FALSE;
2172
2173     return;
2174   }
2175
2176   if ((key == KSYM_F11 ||
2177        ((key == KSYM_Return ||
2178          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2179       video.fullscreen_available &&
2180       !ignore_repeated_key)
2181   {
2182     setup.fullscreen = !setup.fullscreen;
2183
2184     ToggleFullscreenIfNeeded();
2185
2186     if (game_status == GAME_MODE_SETUP)
2187       RedrawSetupScreenAfterFullscreenToggle();
2188
2189     UpdateMousePosition();
2190
2191     // set flag to ignore repeated "key pressed" events
2192     ignore_repeated_key = TRUE;
2193
2194     return;
2195   }
2196
2197   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2198        key == KSYM_minus || key == KSYM_KP_Subtract ||
2199        key == KSYM_plus  || key == KSYM_KP_Add ||
2200        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2201       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2202       video.window_scaling_available &&
2203       !video.fullscreen_enabled)
2204   {
2205     if (key == KSYM_0 || key == KSYM_KP_0)
2206       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2207     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2208       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2209     else
2210       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2211
2212     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2213       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2214     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2215       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2216
2217     ChangeWindowScalingIfNeeded();
2218
2219     if (game_status == GAME_MODE_SETUP)
2220       RedrawSetupScreenAfterFullscreenToggle();
2221
2222     UpdateMousePosition();
2223
2224     return;
2225   }
2226
2227   // some key events are handled like clicks for global animations
2228   boolean click = (key == KSYM_space ||
2229                    key == KSYM_Return ||
2230                    key == KSYM_Escape);
2231
2232   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2233   {
2234     // do not handle this key event anymore
2235     if (key != KSYM_Escape)     // always allow ESC key to be handled
2236       return;
2237   }
2238
2239   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2240       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2241   {
2242     GameEnd();
2243
2244     return;
2245   }
2246
2247   if (game_status == GAME_MODE_MAIN &&
2248       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2249   {
2250     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2251
2252     return;
2253   }
2254
2255   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2256   {
2257     if (key == setup.shortcut.save_game)
2258       TapeQuickSave();
2259     else if (key == setup.shortcut.load_game)
2260       TapeQuickLoad();
2261     else if (key == setup.shortcut.toggle_pause)
2262       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2263
2264     HandleTapeButtonKeys(key);
2265     HandleSoundButtonKeys(key);
2266   }
2267
2268   if (game_status == GAME_MODE_PLAYING && !network_playing)
2269   {
2270     int centered_player_nr_next = -999;
2271
2272     if (key == setup.shortcut.focus_player_all)
2273       centered_player_nr_next = -1;
2274     else
2275       for (i = 0; i < MAX_PLAYERS; i++)
2276         if (key == setup.shortcut.focus_player[i])
2277           centered_player_nr_next = i;
2278
2279     if (centered_player_nr_next != -999)
2280     {
2281       game.centered_player_nr_next = centered_player_nr_next;
2282       game.set_centered_player = TRUE;
2283
2284       if (tape.recording)
2285       {
2286         tape.centered_player_nr_next = game.centered_player_nr_next;
2287         tape.set_centered_player = TRUE;
2288       }
2289     }
2290   }
2291
2292   HandleKeysSpecial(key);
2293
2294   if (HandleGadgetsKeyInput(key))
2295     return;             // do not handle already processed keys again
2296
2297   switch (game_status)
2298   {
2299     case GAME_MODE_PSEUDO_TYPENAME:
2300       HandleTypeName(key);
2301       break;
2302
2303     case GAME_MODE_TITLE:
2304     case GAME_MODE_MAIN:
2305     case GAME_MODE_LEVELS:
2306     case GAME_MODE_LEVELNR:
2307     case GAME_MODE_SETUP:
2308     case GAME_MODE_INFO:
2309     case GAME_MODE_SCORES:
2310
2311       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2312         break;
2313
2314       switch (key)
2315       {
2316         case KSYM_space:
2317         case KSYM_Return:
2318           if (game_status == GAME_MODE_TITLE)
2319             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2320           else if (game_status == GAME_MODE_MAIN)
2321             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2322           else if (game_status == GAME_MODE_LEVELS)
2323             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2324           else if (game_status == GAME_MODE_LEVELNR)
2325             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2326           else if (game_status == GAME_MODE_SETUP)
2327             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2328           else if (game_status == GAME_MODE_INFO)
2329             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2330           else if (game_status == GAME_MODE_SCORES)
2331             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2332           break;
2333
2334         case KSYM_Escape:
2335           if (game_status != GAME_MODE_MAIN)
2336             FadeSkipNextFadeIn();
2337
2338           if (game_status == GAME_MODE_TITLE)
2339             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2340           else if (game_status == GAME_MODE_LEVELS)
2341             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2342           else if (game_status == GAME_MODE_LEVELNR)
2343             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2344           else if (game_status == GAME_MODE_SETUP)
2345             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2346           else if (game_status == GAME_MODE_INFO)
2347             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2348           else if (game_status == GAME_MODE_SCORES)
2349             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2350           break;
2351
2352         case KSYM_Page_Up:
2353           if (game_status == GAME_MODE_LEVELS)
2354             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2355           else if (game_status == GAME_MODE_LEVELNR)
2356             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2357           else if (game_status == GAME_MODE_SETUP)
2358             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2359           else if (game_status == GAME_MODE_INFO)
2360             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2361           else if (game_status == GAME_MODE_SCORES)
2362             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2363           break;
2364
2365         case KSYM_Page_Down:
2366           if (game_status == GAME_MODE_LEVELS)
2367             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2368           else if (game_status == GAME_MODE_LEVELNR)
2369             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2370           else if (game_status == GAME_MODE_SETUP)
2371             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2372           else if (game_status == GAME_MODE_INFO)
2373             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2374           else if (game_status == GAME_MODE_SCORES)
2375             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2376           break;
2377
2378         default:
2379           break;
2380       }
2381       break;
2382
2383     case GAME_MODE_EDITOR:
2384       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2385         HandleLevelEditorKeyInput(key);
2386       break;
2387
2388     case GAME_MODE_PLAYING:
2389     {
2390       switch (key)
2391       {
2392         case KSYM_Escape:
2393           RequestQuitGame(setup.ask_on_escape);
2394           break;
2395
2396         default:
2397           break;
2398       }
2399       break;
2400     }
2401
2402     default:
2403       if (key == KSYM_Escape)
2404       {
2405         SetGameStatus(GAME_MODE_MAIN);
2406
2407         DrawMainMenu();
2408
2409         return;
2410       }
2411   }
2412 }
2413
2414 void HandleNoEvent(void)
2415 {
2416   HandleMouseCursor();
2417
2418   switch (game_status)
2419   {
2420     case GAME_MODE_PLAYING:
2421       HandleButtonOrFinger(-1, -1, -1);
2422       break;
2423   }
2424 }
2425
2426 void HandleEventActions(void)
2427 {
2428   // if (button_status && game_status != GAME_MODE_PLAYING)
2429   if (button_status && (game_status != GAME_MODE_PLAYING ||
2430                         tape.pausing ||
2431                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2432   {
2433     HandleButton(0, 0, button_status, -button_status);
2434   }
2435   else
2436   {
2437     HandleJoystick();
2438   }
2439
2440   if (network.enabled)
2441     HandleNetworking();
2442
2443   switch (game_status)
2444   {
2445     case GAME_MODE_MAIN:
2446       DrawPreviewLevelAnimation();
2447       break;
2448
2449     case GAME_MODE_EDITOR:
2450       HandleLevelEditorIdle();
2451       break;
2452
2453     default:
2454       break;
2455   }
2456 }
2457
2458 static void HandleTileCursor(int dx, int dy, int button)
2459 {
2460   if (!dx || !button)
2461     ClearPlayerMouseAction();
2462
2463   if (!dx && !dy)
2464     return;
2465
2466   if (button)
2467   {
2468     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2469                          (dx < 0 ? MB_LEFTBUTTON :
2470                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2471   }
2472   else if (!tile_cursor.moving)
2473   {
2474     int old_xpos = tile_cursor.xpos;
2475     int old_ypos = tile_cursor.ypos;
2476     int new_xpos = old_xpos;
2477     int new_ypos = old_ypos;
2478
2479     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2480       new_xpos = old_xpos + dx;
2481
2482     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2483       new_ypos = old_ypos + dy;
2484
2485     SetTileCursorTargetXY(new_xpos, new_ypos);
2486   }
2487 }
2488
2489 static int HandleJoystickForAllPlayers(void)
2490 {
2491   int i;
2492   int result = 0;
2493   boolean no_joysticks_configured = TRUE;
2494   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2495   static byte joy_action_last[MAX_PLAYERS];
2496
2497   for (i = 0; i < MAX_PLAYERS; i++)
2498     if (setup.input[i].use_joystick)
2499       no_joysticks_configured = FALSE;
2500
2501   // if no joysticks configured, map connected joysticks to players
2502   if (no_joysticks_configured)
2503     use_as_joystick_nr = TRUE;
2504
2505   for (i = 0; i < MAX_PLAYERS; i++)
2506   {
2507     byte joy_action = 0;
2508
2509     joy_action = JoystickExt(i, use_as_joystick_nr);
2510     result |= joy_action;
2511
2512     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2513         joy_action != joy_action_last[i])
2514       stored_player[i].action = joy_action;
2515
2516     joy_action_last[i] = joy_action;
2517   }
2518
2519   return result;
2520 }
2521
2522 void HandleJoystick(void)
2523 {
2524   static unsigned int joytest_delay = 0;
2525   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2526   static int joytest_last = 0;
2527   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2528   int delay_value       = GADGET_FRAME_DELAY;
2529   int joystick  = HandleJoystickForAllPlayers();
2530   int keyboard  = key_joystick_mapping;
2531   int joy       = (joystick | keyboard);
2532   int joytest   = joystick;
2533   int left      = joy & JOY_LEFT;
2534   int right     = joy & JOY_RIGHT;
2535   int up        = joy & JOY_UP;
2536   int down      = joy & JOY_DOWN;
2537   int button    = joy & JOY_BUTTON;
2538   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2539   int dx        = (left ? -1    : right ? 1     : 0);
2540   int dy        = (up   ? -1    : down  ? 1     : 0);
2541   boolean use_delay_value_first = (joytest != joytest_last);
2542
2543   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2544   {
2545     // do not handle this button event anymore
2546     return;
2547   }
2548
2549   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2550                     anyTextGadgetActive()))
2551   {
2552     // leave name input in main menu or text input gadget
2553     HandleKey(KSYM_Escape, KEY_PRESSED);
2554     HandleKey(KSYM_Escape, KEY_RELEASED);
2555
2556     return;
2557   }
2558
2559   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2560   {
2561     if (game_status == GAME_MODE_PLAYING)
2562     {
2563       // when playing MM style levels, also use delay for keyboard events
2564       joytest |= keyboard;
2565
2566       // only use first delay value for new events, but not for changed events
2567       use_delay_value_first = (!joytest != !joytest_last);
2568
2569       // only use delay after the initial keyboard event
2570       delay_value = 0;
2571     }
2572
2573     // for any joystick or keyboard event, enable playfield tile cursor
2574     if (dx || dy || button)
2575       SetTileCursorEnabled(TRUE);
2576   }
2577
2578   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2579   {
2580     // delay joystick/keyboard actions if axes/keys continually pressed
2581     newbutton = dx = dy = 0;
2582   }
2583   else
2584   {
2585     // first start with longer delay, then continue with shorter delay
2586     joytest_delay_value =
2587       (use_delay_value_first ? delay_value_first : delay_value);
2588   }
2589
2590   joytest_last = joytest;
2591
2592   switch (game_status)
2593   {
2594     case GAME_MODE_TITLE:
2595     case GAME_MODE_MAIN:
2596     case GAME_MODE_LEVELS:
2597     case GAME_MODE_LEVELNR:
2598     case GAME_MODE_SETUP:
2599     case GAME_MODE_INFO:
2600     case GAME_MODE_SCORES:
2601     {
2602       if (anyTextGadgetActive())
2603         break;
2604
2605       if (game_status == GAME_MODE_TITLE)
2606         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2607       else if (game_status == GAME_MODE_MAIN)
2608         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2609       else if (game_status == GAME_MODE_LEVELS)
2610         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2611       else if (game_status == GAME_MODE_LEVELNR)
2612         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2613       else if (game_status == GAME_MODE_SETUP)
2614         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2615       else if (game_status == GAME_MODE_INFO)
2616         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2617       else if (game_status == GAME_MODE_SCORES)
2618         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2619
2620       break;
2621     }
2622
2623     case GAME_MODE_PLAYING:
2624 #if 0
2625       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2626       if (tape.playing || keyboard)
2627         newbutton = ((joy & JOY_BUTTON) != 0);
2628 #endif
2629
2630       if (newbutton && game.all_players_gone)
2631       {
2632         GameEnd();
2633
2634         return;
2635       }
2636
2637       if (tape.recording && tape.pausing && tape.use_key_actions)
2638       {
2639         if (tape.single_step)
2640         {
2641           if (joystick & JOY_ACTION)
2642             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2643         }
2644         else
2645         {
2646           if (joystick & JOY_ACTION)
2647             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2648         }
2649       }
2650
2651       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2652         HandleTileCursor(dx, dy, button);
2653
2654       break;
2655
2656     default:
2657       break;
2658   }
2659 }
2660
2661 void HandleSpecialGameControllerButtons(Event *event)
2662 {
2663   int key_status;
2664   Key key;
2665
2666   switch (event->type)
2667   {
2668     case SDL_CONTROLLERBUTTONDOWN:
2669       key_status = KEY_PRESSED;
2670       break;
2671
2672     case SDL_CONTROLLERBUTTONUP:
2673       key_status = KEY_RELEASED;
2674       break;
2675
2676     default:
2677       return;
2678   }
2679
2680   switch (event->cbutton.button)
2681   {
2682     case SDL_CONTROLLER_BUTTON_START:
2683       key = KSYM_space;
2684       break;
2685
2686     case SDL_CONTROLLER_BUTTON_BACK:
2687       key = KSYM_Escape;
2688       break;
2689
2690     default:
2691       return;
2692   }
2693
2694   HandleKey(key, key_status);
2695 }
2696
2697 void HandleSpecialGameControllerKeys(Key key, int key_status)
2698 {
2699 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2700   int button = SDL_CONTROLLER_BUTTON_INVALID;
2701
2702   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2703   if (key == KSYM_Rewind)
2704     button = SDL_CONTROLLER_BUTTON_A;
2705   else if (key == KSYM_FastForward || key == KSYM_Menu)
2706     button = SDL_CONTROLLER_BUTTON_B;
2707
2708   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2709   {
2710     Event event;
2711
2712     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2713                   SDL_CONTROLLERBUTTONUP);
2714
2715     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2716     event.cbutton.button = button;
2717     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2718                            SDL_RELEASED);
2719
2720     HandleJoystickEvent(&event);
2721   }
2722 #endif
2723 }
2724
2725 boolean DoKeysymAction(int keysym)
2726 {
2727   if (keysym < 0)
2728   {
2729     Key key = (Key)(-keysym);
2730
2731     HandleKey(key, KEY_PRESSED);
2732     HandleKey(key, KEY_RELEASED);
2733
2734     return TRUE;
2735   }
2736
2737   return FALSE;
2738 }