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