added forcing user to restart program after adding level collection
[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   // force restart after adding level collection
1555   if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1556   {
1557     Request("Program must be restarted after adding a new level collection!",
1558             REQ_CONFIRM);
1559
1560     CloseAllAndExit(0);
1561   }
1562
1563   // update menu screen (and possibly change current level set)
1564   DrawScreenAfterAddingSet(top_dir, tree_type);
1565
1566   return tree_type;
1567 }
1568
1569 static void HandleDropTextEvent(char *text)
1570 {
1571   Debug("event:droptext", "text == '%s'", text);
1572 }
1573
1574 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1575                                     int num_artwork_sets_succeeded,
1576                                     int num_files_failed)
1577 {
1578   // only show request dialog if no other request dialog already active
1579   if (game.request_active)
1580     return;
1581
1582   // this case can happen with drag-and-drop with older SDL versions
1583   if (num_level_sets_succeeded == 0 &&
1584       num_artwork_sets_succeeded == 0 &&
1585       num_files_failed == 0)
1586     return;
1587
1588   char message[100];
1589
1590   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1591   {
1592     char message_part1[50];
1593
1594     sprintf(message_part1, "New %s set%s added",
1595             (num_artwork_sets_succeeded == 0 ? "level" :
1596              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1597             (num_level_sets_succeeded +
1598              num_artwork_sets_succeeded > 1 ? "s" : ""));
1599
1600     if (num_files_failed > 0)
1601       sprintf(message, "%s, but %d dropped file%s failed!",
1602               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1603     else
1604       sprintf(message, "%s!", message_part1);
1605   }
1606   else if (num_files_failed > 0)
1607   {
1608     sprintf(message, "Failed to process dropped file%s!",
1609             num_files_failed > 1 ? "s" : "");
1610   }
1611
1612   Request(message, REQ_CONFIRM);
1613 }
1614
1615 void HandleDropEvent(Event *event)
1616 {
1617   static boolean confirm_on_drop_complete = FALSE;
1618   static int num_level_sets_succeeded = 0;
1619   static int num_artwork_sets_succeeded = 0;
1620   static int num_files_failed = 0;
1621
1622   switch (event->type)
1623   {
1624     case SDL_DROPBEGIN:
1625     {
1626       confirm_on_drop_complete = TRUE;
1627       num_level_sets_succeeded = 0;
1628       num_artwork_sets_succeeded = 0;
1629       num_files_failed = 0;
1630
1631       break;
1632     }
1633
1634     case SDL_DROPFILE:
1635     {
1636       int tree_type = HandleDropFileEvent(event->drop.file);
1637
1638       if (tree_type == TREE_TYPE_LEVEL_DIR)
1639         num_level_sets_succeeded++;
1640       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1641                tree_type == TREE_TYPE_SOUNDS_DIR ||
1642                tree_type == TREE_TYPE_MUSIC_DIR)
1643         num_artwork_sets_succeeded++;
1644       else
1645         num_files_failed++;
1646
1647       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1648       if (!confirm_on_drop_complete)
1649       {
1650         // process all remaining events, including further SDL_DROPFILE events
1651         ClearEventQueue();
1652
1653         HandleDropCompleteEvent(num_level_sets_succeeded,
1654                                 num_artwork_sets_succeeded,
1655                                 num_files_failed);
1656
1657         num_level_sets_succeeded = 0;
1658         num_artwork_sets_succeeded = 0;
1659         num_files_failed = 0;
1660       }
1661
1662       break;
1663     }
1664
1665     case SDL_DROPTEXT:
1666     {
1667       HandleDropTextEvent(event->drop.file);
1668
1669       break;
1670     }
1671
1672     case SDL_DROPCOMPLETE:
1673     {
1674       HandleDropCompleteEvent(num_level_sets_succeeded,
1675                               num_artwork_sets_succeeded,
1676                               num_files_failed);
1677
1678       break;
1679     }
1680   }
1681
1682   if (event->drop.file != NULL)
1683     SDL_free(event->drop.file);
1684 }
1685
1686 void HandleUserEvent(UserEvent *event)
1687 {
1688   switch (event->code)
1689   {
1690     case USEREVENT_ANIM_DELAY_ACTION:
1691     case USEREVENT_ANIM_EVENT_ACTION:
1692       // execute action functions until matching action was found
1693       if (DoKeysymAction(event->value1) ||
1694           DoGadgetAction(event->value1) ||
1695           DoScreenAction(event->value1))
1696         return;
1697       break;
1698
1699     default:
1700       break;
1701   }
1702 }
1703
1704 void HandleButton(int mx, int my, int button, int button_nr)
1705 {
1706   static int old_mx = 0, old_my = 0;
1707   boolean button_hold = FALSE;
1708   boolean handle_gadgets = TRUE;
1709   int game_status_last = game_status;
1710
1711   if (button_nr < 0)
1712   {
1713     mx = old_mx;
1714     my = old_my;
1715     button_nr = -button_nr;
1716     button_hold = TRUE;
1717   }
1718   else
1719   {
1720     old_mx = mx;
1721     old_my = my;
1722   }
1723
1724 #if defined(PLATFORM_ANDROID)
1725   // when playing, only handle gadgets when using "follow finger" controls
1726   // or when using touch controls in combination with the MM game engine
1727   // or when using gadgets that do not overlap with virtual buttons
1728   // or when touch controls are disabled (e.g., with mouse-only levels)
1729   handle_gadgets =
1730     (game_status != GAME_MODE_PLAYING ||
1731      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1732      strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1733      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1734      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1735       !CheckVirtualButtonPressed(mx, my, button)));
1736
1737   // always recognize potentially releasing already pressed gadgets
1738   if (button == MB_RELEASED)
1739     handle_gadgets = TRUE;
1740
1741   // always recognize pressing or releasing overlay touch buttons
1742   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1743     handle_gadgets = TRUE;
1744 #endif
1745
1746   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1747   {
1748     // do not handle this button event anymore
1749     return;             // force mouse event not to be handled at all
1750   }
1751
1752   if (handle_gadgets && HandleGadgets(mx, my, button))
1753   {
1754     // do not handle this button event anymore with position on screen
1755     mx = my = -32;      // force mouse event to be outside screen tiles
1756
1757     // do not handle this button event anymore if game status has changed
1758     if (game_status != game_status_last)
1759       return;
1760   }
1761
1762   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1763     return;
1764
1765   // do not use scroll wheel button events for anything other than gadgets
1766   if (IS_WHEEL_BUTTON(button_nr))
1767     return;
1768
1769   switch (game_status)
1770   {
1771     case GAME_MODE_TITLE:
1772       HandleTitleScreen(mx, my, 0, 0, button);
1773       break;
1774
1775     case GAME_MODE_MAIN:
1776       HandleMainMenu(mx, my, 0, 0, button);
1777       break;
1778
1779     case GAME_MODE_PSEUDO_TYPENAME:
1780     case GAME_MODE_PSEUDO_TYPENAMES:
1781       HandleTypeName(KSYM_Return);
1782       break;
1783
1784     case GAME_MODE_NAMES:
1785       HandleChoosePlayerName(mx, my, 0, 0, button);
1786       break;
1787
1788     case GAME_MODE_LEVELS:
1789       HandleChooseLevelSet(mx, my, 0, 0, button);
1790       break;
1791
1792     case GAME_MODE_LEVELNR:
1793       HandleChooseLevelNr(mx, my, 0, 0, button);
1794       break;
1795
1796     case GAME_MODE_SCORES:
1797       HandleHallOfFame(mx, my, 0, 0, button);
1798       break;
1799
1800     case GAME_MODE_SCOREINFO:
1801       HandleScoreInfo(mx, my, 0, 0, button);
1802       break;
1803
1804     case GAME_MODE_EDITOR:
1805       HandleLevelEditorIdle();
1806       break;
1807
1808     case GAME_MODE_INFO:
1809       HandleInfoScreen(mx, my, 0, 0, button);
1810       break;
1811
1812     case GAME_MODE_SETUP:
1813       HandleSetupScreen(mx, my, 0, 0, button);
1814       break;
1815
1816     case GAME_MODE_PLAYING:
1817       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1818         HandleButtonOrFinger(mx, my, button);
1819       else
1820         SetPlayerMouseAction(mx, my, button);
1821
1822 #ifdef DEBUG
1823       if (button == MB_PRESSED && !motion_status && !button_hold &&
1824           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1825         DumpTileFromScreen(mx, my);
1826 #endif
1827
1828       break;
1829
1830     default:
1831       break;
1832   }
1833 }
1834
1835 #define MAX_CHEAT_INPUT_LEN     32
1836
1837 static void HandleKeysSpecial(Key key)
1838 {
1839   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1840   char letter = getCharFromKey(key);
1841   int cheat_input_len = strlen(cheat_input);
1842   int i;
1843
1844   if (letter == 0)
1845     return;
1846
1847   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1848   {
1849     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1850       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1851
1852     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1853   }
1854
1855   cheat_input[cheat_input_len++] = letter;
1856   cheat_input[cheat_input_len] = '\0';
1857
1858 #if DEBUG_EVENTS_KEY
1859   Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1860 #endif
1861
1862   if (game_status == GAME_MODE_MAIN)
1863   {
1864     if (strSuffix(cheat_input, ":insert-solution-tape") ||
1865         strSuffix(cheat_input, ":ist"))
1866     {
1867       InsertSolutionTape();
1868     }
1869     else if (strSuffix(cheat_input, ":play-solution-tape") ||
1870              strSuffix(cheat_input, ":pst"))
1871     {
1872       PlaySolutionTape();
1873     }
1874     else if (strSuffix(cheat_input, ":reload-graphics") ||
1875              strSuffix(cheat_input, ":rg"))
1876     {
1877       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1878       DrawMainMenu();
1879     }
1880     else if (strSuffix(cheat_input, ":reload-sounds") ||
1881              strSuffix(cheat_input, ":rs"))
1882     {
1883       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1884       DrawMainMenu();
1885     }
1886     else if (strSuffix(cheat_input, ":reload-music") ||
1887              strSuffix(cheat_input, ":rm"))
1888     {
1889       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1890       DrawMainMenu();
1891     }
1892     else if (strSuffix(cheat_input, ":reload-artwork") ||
1893              strSuffix(cheat_input, ":ra"))
1894     {
1895       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1896                           1 << ARTWORK_TYPE_SOUNDS |
1897                           1 << ARTWORK_TYPE_MUSIC);
1898       DrawMainMenu();
1899     }
1900     else if (strSuffix(cheat_input, ":dump-level") ||
1901              strSuffix(cheat_input, ":dl"))
1902     {
1903       DumpLevel(&level);
1904     }
1905     else if (strSuffix(cheat_input, ":dump-tape") ||
1906              strSuffix(cheat_input, ":dt"))
1907     {
1908       DumpTape(&tape);
1909     }
1910     else if (strSuffix(cheat_input, ":undo-tape") ||
1911              strSuffix(cheat_input, ":ut"))
1912     {
1913       UndoTape();
1914     }
1915     else if (strSuffix(cheat_input, ":fix-tape") ||
1916              strSuffix(cheat_input, ":ft"))
1917     {
1918       FixTape_ForceSinglePlayer();
1919     }
1920     else if (strSuffix(cheat_input, ":save-native-level") ||
1921              strSuffix(cheat_input, ":snl"))
1922     {
1923       SaveNativeLevel(&level);
1924     }
1925     else if (strSuffix(cheat_input, ":frames-per-second") ||
1926              strSuffix(cheat_input, ":fps"))
1927     {
1928       global.show_frames_per_second = !global.show_frames_per_second;
1929     }
1930     else if (strSuffix(cheat_input, ":xsn"))
1931     {
1932       tile_cursor.xsn_debug = TRUE;
1933     }
1934   }
1935   else if (game_status == GAME_MODE_PLAYING)
1936   {
1937 #ifdef DEBUG
1938     if (strSuffix(cheat_input, ".q"))
1939       DEBUG_SetMaximumDynamite();
1940 #endif
1941   }
1942   else if (game_status == GAME_MODE_EDITOR)
1943   {
1944     if (strSuffix(cheat_input, ":dump-brush") ||
1945         strSuffix(cheat_input, ":DB"))
1946     {
1947       DumpBrush();
1948     }
1949     else if (strSuffix(cheat_input, ":DDB"))
1950     {
1951       DumpBrush_Small();
1952     }
1953
1954     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1955     {
1956       if (letter == 'x')        // copy brush to clipboard (small size)
1957       {
1958         CopyBrushToClipboard_Small();
1959       }
1960       else if (letter == 'c')   // copy brush to clipboard (normal size)
1961       {
1962         CopyBrushToClipboard();
1963       }
1964       else if (letter == 'v')   // paste brush from Clipboard
1965       {
1966         CopyClipboardToBrush();
1967       }
1968       else if (letter == 'z')   // undo or redo last operation
1969       {
1970         if (GetKeyModState() & KMOD_Shift)
1971           RedoLevelEditorOperation();
1972         else
1973           UndoLevelEditorOperation();
1974       }
1975     }
1976   }
1977
1978   // special key shortcuts for all game modes
1979   if (strSuffix(cheat_input, ":dump-event-actions") ||
1980       strSuffix(cheat_input, ":dea") ||
1981       strSuffix(cheat_input, ":DEA"))
1982   {
1983     DumpGadgetIdentifiers();
1984     DumpScreenIdentifiers();
1985   }
1986 }
1987
1988 boolean HandleKeysDebug(Key key, int key_status)
1989 {
1990 #ifdef DEBUG
1991   int i;
1992
1993   if (key_status != KEY_PRESSED)
1994     return FALSE;
1995
1996   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1997   {
1998     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1999
2000     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2001     {
2002       if (key == setup.debug.frame_delay_key[i] &&
2003           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2004       {
2005         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2006                           setup.debug.frame_delay[i] : setup.game_frame_delay);
2007
2008         if (!setup.debug.frame_delay_game_only)
2009           MenuFrameDelay = GameFrameDelay;
2010
2011         SetVideoFrameDelay(GameFrameDelay);
2012
2013         if (GameFrameDelay > ONE_SECOND_DELAY)
2014           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2015         else if (GameFrameDelay != 0)
2016           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2017                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2018                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2019         else
2020           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2021
2022         return TRUE;
2023       }
2024     }
2025   }
2026
2027   if (game_status == GAME_MODE_PLAYING)
2028   {
2029     if (key == KSYM_d)
2030     {
2031       options.debug = !options.debug;
2032
2033       Debug("event:key:debug", "debug mode %s",
2034             (options.debug ? "enabled" : "disabled"));
2035
2036       return TRUE;
2037     }
2038     else if (key == KSYM_v)
2039     {
2040       Debug("event:key:debug", "currently using game engine version %d",
2041             game.engine_version);
2042
2043       return TRUE;
2044     }
2045   }
2046 #endif
2047
2048   return FALSE;
2049 }
2050
2051 void HandleKey(Key key, int key_status)
2052 {
2053   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2054   static boolean ignore_repeated_key = FALSE;
2055   static struct SetupKeyboardInfo ski;
2056   static struct SetupShortcutInfo ssi;
2057   static struct
2058   {
2059     Key *key_custom;
2060     Key *key_snap;
2061     Key key_default;
2062     byte action;
2063   } key_info[] =
2064   {
2065     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2066     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2067     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2068     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2069     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2070     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2071   };
2072   int joy = 0;
2073   int i;
2074
2075   if (HandleKeysDebug(key, key_status))
2076     return;             // do not handle already processed keys again
2077
2078   // map special keys (media keys / remote control buttons) to default keys
2079   if (key == KSYM_PlayPause)
2080     key = KSYM_space;
2081   else if (key == KSYM_Select)
2082     key = KSYM_Return;
2083
2084   HandleSpecialGameControllerKeys(key, key_status);
2085
2086   if (game_status == GAME_MODE_PLAYING)
2087   {
2088     // only needed for single-step tape recording mode
2089     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2090     int pnr;
2091
2092     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2093     {
2094       byte key_action = 0;
2095       byte key_snap_action = 0;
2096
2097       if (setup.input[pnr].use_joystick)
2098         continue;
2099
2100       ski = setup.input[pnr].key;
2101
2102       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2103         if (key == *key_info[i].key_custom)
2104           key_action |= key_info[i].action;
2105
2106       // use combined snap+direction keys for the first player only
2107       if (pnr == 0)
2108       {
2109         ssi = setup.shortcut;
2110
2111         // also remember normal snap key when handling snap+direction keys
2112         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2113
2114         for (i = 0; i < NUM_DIRECTIONS; i++)
2115         {
2116           if (key == *key_info[i].key_snap)
2117           {
2118             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2119             key_snap_action |= key_info[i].action;
2120
2121             tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2122           }
2123         }
2124       }
2125
2126       if (key_status == KEY_PRESSED)
2127       {
2128         stored_player[pnr].action      |= key_action;
2129         stored_player[pnr].snap_action |= key_snap_action;
2130       }
2131       else
2132       {
2133         stored_player[pnr].action      &= ~key_action;
2134         stored_player[pnr].snap_action &= ~key_snap_action;
2135       }
2136
2137       // restore snap action if one of several pressed snap keys was released
2138       if (stored_player[pnr].snap_action)
2139         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2140
2141       if (tape.recording && tape.pausing && tape.use_key_actions)
2142       {
2143         if (tape.single_step)
2144         {
2145           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2146           {
2147             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2148
2149             // if snap key already pressed, keep pause mode when releasing
2150             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2151               has_snapped[pnr] = TRUE;
2152           }
2153           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2154           {
2155             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2156
2157             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2158                 getRedDiskReleaseFlag_SP() == 0)
2159             {
2160               // add a single inactive frame before dropping starts
2161               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2162               stored_player[pnr].force_dropping = TRUE;
2163             }
2164           }
2165           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2166           {
2167             // if snap key was pressed without direction, leave pause mode
2168             if (!has_snapped[pnr])
2169               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2170
2171             has_snapped[pnr] = FALSE;
2172           }
2173         }
2174         else
2175         {
2176           // prevent key release events from un-pausing a paused game
2177           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2178             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2179         }
2180       }
2181
2182       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2183       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2184         joy |= key_action;
2185
2186       // for any keyboard event, enable playfield mouse cursor
2187       if (key_action && key_status == KEY_PRESSED)
2188         SetPlayfieldMouseCursorEnabled(TRUE);
2189     }
2190   }
2191   else
2192   {
2193     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2194       if (key == key_info[i].key_default)
2195         joy |= key_info[i].action;
2196   }
2197
2198   if (joy)
2199   {
2200     if (key_status == KEY_PRESSED)
2201       key_joystick_mapping |= joy;
2202     else
2203       key_joystick_mapping &= ~joy;
2204
2205     HandleJoystick();
2206   }
2207
2208   if (game_status != GAME_MODE_PLAYING)
2209     key_joystick_mapping = 0;
2210
2211   if (key_status == KEY_RELEASED)
2212   {
2213     // reset flag to ignore repeated "key pressed" events after key release
2214     ignore_repeated_key = FALSE;
2215
2216     // send key release event to global animation event handling
2217     HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2218
2219     return;
2220   }
2221
2222   if ((key == KSYM_F11 ||
2223        ((key == KSYM_Return ||
2224          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2225       video.fullscreen_available &&
2226       !ignore_repeated_key)
2227   {
2228     setup.fullscreen = !setup.fullscreen;
2229
2230     ToggleFullscreenIfNeeded();
2231
2232     if (game_status == GAME_MODE_SETUP)
2233       RedrawSetupScreenAfterFullscreenToggle();
2234
2235     UpdateMousePosition();
2236
2237     // set flag to ignore repeated "key pressed" events
2238     ignore_repeated_key = TRUE;
2239
2240     return;
2241   }
2242
2243   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2244        key == KSYM_minus || key == KSYM_KP_Subtract ||
2245        key == KSYM_plus  || key == KSYM_KP_Add ||
2246        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2247       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2248       video.window_scaling_available &&
2249       !video.fullscreen_enabled)
2250   {
2251     if (key == KSYM_0 || key == KSYM_KP_0)
2252       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2253     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2254       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2255     else
2256       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2257
2258     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2259       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2260     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2261       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2262
2263     ChangeWindowScalingIfNeeded();
2264
2265     if (game_status == GAME_MODE_SETUP)
2266       RedrawSetupScreenAfterFullscreenToggle();
2267
2268     UpdateMousePosition();
2269
2270     return;
2271   }
2272
2273   // some key events are handled like clicks for global animations
2274   boolean click = (key == KSYM_space ||
2275                    key == KSYM_Return ||
2276                    key == KSYM_Escape);
2277
2278   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2279   {
2280     // do not handle this key event anymore
2281     if (key != KSYM_Escape)     // always allow ESC key to be handled
2282       return;
2283   }
2284
2285   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2286       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2287   {
2288     GameEnd();
2289
2290     return;
2291   }
2292
2293   if (game_status == GAME_MODE_MAIN &&
2294       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2295   {
2296     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2297
2298     return;
2299   }
2300
2301   if (game_status == GAME_MODE_MAIN &&
2302       (setup.internal.info_screens_from_main ||
2303        leveldir_current->info_screens_from_main) &&
2304       (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2305   {
2306     DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2307
2308     return;
2309   }
2310
2311   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2312   {
2313     if (key == setup.shortcut.save_game)
2314       TapeQuickSave();
2315     else if (key == setup.shortcut.load_game)
2316       TapeQuickLoad();
2317     else if (key == setup.shortcut.restart_game)
2318       TapeRestartGame();
2319     else if (key == setup.shortcut.pause_before_end)
2320       TapeReplayAndPauseBeforeEnd();
2321     else if (key == setup.shortcut.toggle_pause)
2322       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2323
2324     HandleTapeButtonKeys(key);
2325     HandleSoundButtonKeys(key);
2326   }
2327
2328   if (game_status == GAME_MODE_SCOREINFO)
2329   {
2330     HandleScreenGadgetKeys(key);
2331   }
2332
2333   if (game_status == GAME_MODE_PLAYING && !network_playing)
2334   {
2335     int centered_player_nr_next = -999;
2336
2337     if (key == setup.shortcut.focus_player_all)
2338       centered_player_nr_next = -1;
2339     else
2340       for (i = 0; i < MAX_PLAYERS; i++)
2341         if (key == setup.shortcut.focus_player[i])
2342           centered_player_nr_next = i;
2343
2344     if (centered_player_nr_next != -999)
2345     {
2346       game.centered_player_nr_next = centered_player_nr_next;
2347       game.set_centered_player = TRUE;
2348
2349       if (tape.recording)
2350       {
2351         tape.centered_player_nr_next = game.centered_player_nr_next;
2352         tape.set_centered_player = TRUE;
2353       }
2354     }
2355   }
2356
2357   HandleKeysSpecial(key);
2358
2359   if (HandleGadgetsKeyInput(key))
2360     return;             // do not handle already processed keys again
2361
2362   // special case: on "space" key, either continue playing or go to main menu
2363   if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2364   {
2365     HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2366
2367     return;
2368   }
2369
2370   switch (game_status)
2371   {
2372     case GAME_MODE_PSEUDO_TYPENAME:
2373     case GAME_MODE_PSEUDO_TYPENAMES:
2374       HandleTypeName(key);
2375       break;
2376
2377     case GAME_MODE_TITLE:
2378     case GAME_MODE_MAIN:
2379     case GAME_MODE_NAMES:
2380     case GAME_MODE_LEVELS:
2381     case GAME_MODE_LEVELNR:
2382     case GAME_MODE_SETUP:
2383     case GAME_MODE_INFO:
2384     case GAME_MODE_SCORES:
2385     case GAME_MODE_SCOREINFO:
2386
2387       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2388         break;
2389
2390       switch (key)
2391       {
2392         case KSYM_space:
2393         case KSYM_Return:
2394           if (game_status == GAME_MODE_TITLE)
2395             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2396           else if (game_status == GAME_MODE_MAIN)
2397             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2398           else if (game_status == GAME_MODE_NAMES)
2399             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2400           else if (game_status == GAME_MODE_LEVELS)
2401             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2402           else if (game_status == GAME_MODE_LEVELNR)
2403             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2404           else if (game_status == GAME_MODE_SETUP)
2405             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2406           else if (game_status == GAME_MODE_INFO)
2407             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2408           else if (game_status == GAME_MODE_SCORES)
2409             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2410           else if (game_status == GAME_MODE_SCOREINFO)
2411             HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2412           break;
2413
2414         case KSYM_Escape:
2415           if (game_status != GAME_MODE_MAIN)
2416             FadeSkipNextFadeIn();
2417
2418           if (game_status == GAME_MODE_TITLE)
2419             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2420           else if (game_status == GAME_MODE_NAMES)
2421             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2422           else if (game_status == GAME_MODE_LEVELS)
2423             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2424           else if (game_status == GAME_MODE_LEVELNR)
2425             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2426           else if (game_status == GAME_MODE_SETUP)
2427             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2428           else if (game_status == GAME_MODE_INFO)
2429             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2430           else if (game_status == GAME_MODE_SCORES)
2431             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2432           else if (game_status == GAME_MODE_SCOREINFO)
2433             HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2434           break;
2435
2436         case KSYM_Page_Up:
2437           if (game_status == GAME_MODE_NAMES)
2438             HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2439           else if (game_status == GAME_MODE_LEVELS)
2440             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2441           else if (game_status == GAME_MODE_LEVELNR)
2442             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2443           else if (game_status == GAME_MODE_SETUP)
2444             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2445           else if (game_status == GAME_MODE_INFO)
2446             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2447           else if (game_status == GAME_MODE_SCORES)
2448             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2449           else if (game_status == GAME_MODE_SCOREINFO)
2450             HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2451           break;
2452
2453         case KSYM_Page_Down:
2454           if (game_status == GAME_MODE_NAMES)
2455             HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2456           else if (game_status == GAME_MODE_LEVELS)
2457             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2458           else if (game_status == GAME_MODE_LEVELNR)
2459             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2460           else if (game_status == GAME_MODE_SETUP)
2461             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2462           else if (game_status == GAME_MODE_INFO)
2463             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2464           else if (game_status == GAME_MODE_SCORES)
2465             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2466           else if (game_status == GAME_MODE_SCOREINFO)
2467             HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2468           break;
2469
2470         default:
2471           break;
2472       }
2473       break;
2474
2475     case GAME_MODE_EDITOR:
2476       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2477         HandleLevelEditorKeyInput(key);
2478       break;
2479
2480     case GAME_MODE_PLAYING:
2481     {
2482       switch (key)
2483       {
2484         case KSYM_Escape:
2485           RequestQuitGame(TRUE);
2486           break;
2487
2488         default:
2489           break;
2490       }
2491       break;
2492     }
2493
2494     default:
2495       if (key == KSYM_Escape)
2496       {
2497         SetGameStatus(GAME_MODE_MAIN);
2498
2499         DrawMainMenu();
2500
2501         return;
2502       }
2503   }
2504 }
2505
2506 void HandleNoEvent(void)
2507 {
2508   HandleMouseCursor();
2509
2510   switch (game_status)
2511   {
2512     case GAME_MODE_PLAYING:
2513       HandleButtonOrFinger(-1, -1, -1);
2514       break;
2515   }
2516 }
2517
2518 void HandleEventActions(void)
2519 {
2520   // if (button_status && game_status != GAME_MODE_PLAYING)
2521   if (button_status && (game_status != GAME_MODE_PLAYING ||
2522                         tape.pausing ||
2523                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2524   {
2525     HandleButton(0, 0, button_status, -button_status);
2526   }
2527   else
2528   {
2529     HandleJoystick();
2530   }
2531
2532   if (network.enabled)
2533     HandleNetworking();
2534
2535   switch (game_status)
2536   {
2537     case GAME_MODE_MAIN:
2538       DrawPreviewLevelAnimation();
2539       break;
2540
2541     case GAME_MODE_EDITOR:
2542       HandleLevelEditorIdle();
2543       break;
2544
2545     default:
2546       break;
2547   }
2548 }
2549
2550 static void HandleTileCursor(int dx, int dy, int button)
2551 {
2552   if (!dx || !button)
2553     ClearPlayerMouseAction();
2554
2555   if (!dx && !dy)
2556     return;
2557
2558   if (button)
2559   {
2560     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2561                          (dx < 0 ? MB_LEFTBUTTON :
2562                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2563   }
2564   else if (!tile_cursor.moving)
2565   {
2566     int old_xpos = tile_cursor.xpos;
2567     int old_ypos = tile_cursor.ypos;
2568     int new_xpos = tile_cursor.xpos + dx;
2569     int new_ypos = tile_cursor.ypos + dy;
2570
2571     if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2572       new_xpos = old_xpos;
2573
2574     if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2575       new_ypos = old_ypos;
2576
2577     SetTileCursorTargetXY(new_xpos, new_ypos);
2578   }
2579 }
2580
2581 static int HandleJoystickForAllPlayers(void)
2582 {
2583   int i;
2584   int result = 0;
2585   boolean no_joysticks_configured = TRUE;
2586   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2587   static byte joy_action_last[MAX_PLAYERS];
2588
2589   for (i = 0; i < MAX_PLAYERS; i++)
2590     if (setup.input[i].use_joystick)
2591       no_joysticks_configured = FALSE;
2592
2593   // if no joysticks configured, map connected joysticks to players
2594   if (no_joysticks_configured)
2595     use_as_joystick_nr = TRUE;
2596
2597   for (i = 0; i < MAX_PLAYERS; i++)
2598   {
2599     byte joy_action = 0;
2600
2601     joy_action = JoystickExt(i, use_as_joystick_nr);
2602     result |= joy_action;
2603
2604     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2605         joy_action != joy_action_last[i])
2606       stored_player[i].action = joy_action;
2607
2608     joy_action_last[i] = joy_action;
2609   }
2610
2611   return result;
2612 }
2613
2614 void HandleJoystick(void)
2615 {
2616   static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2617   static int joytest_last = 0;
2618   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2619   int delay_value       = GADGET_FRAME_DELAY;
2620   int joystick  = HandleJoystickForAllPlayers();
2621   int keyboard  = key_joystick_mapping;
2622   int joy       = (joystick | keyboard);
2623   int joytest   = joystick;
2624   int left      = joy & JOY_LEFT;
2625   int right     = joy & JOY_RIGHT;
2626   int up        = joy & JOY_UP;
2627   int down      = joy & JOY_DOWN;
2628   int button    = joy & JOY_BUTTON;
2629   int anybutton = AnyJoystickButton();
2630   int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2631   int dx        = (left ? -1    : right ? 1     : 0);
2632   int dy        = (up   ? -1    : down  ? 1     : 0);
2633   boolean use_delay_value_first = (joytest != joytest_last);
2634   boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2635                               anybutton == JOY_BUTTON_NEW_RELEASED);
2636
2637   if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2638   {
2639     // do not handle this button event anymore
2640     return;
2641   }
2642
2643   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2644                     game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2645                     anyTextGadgetActive()))
2646   {
2647     // leave name input in main menu or text input gadget
2648     HandleKey(KSYM_Escape, KEY_PRESSED);
2649     HandleKey(KSYM_Escape, KEY_RELEASED);
2650
2651     return;
2652   }
2653
2654   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2655   {
2656     if (game_status == GAME_MODE_PLAYING)
2657     {
2658       // when playing MM style levels, also use delay for keyboard events
2659       joytest |= keyboard;
2660
2661       // only use first delay value for new events, but not for changed events
2662       use_delay_value_first = (!joytest != !joytest_last);
2663
2664       // only use delay after the initial keyboard event
2665       delay_value = 0;
2666     }
2667
2668     // for any joystick or keyboard event, enable playfield tile cursor
2669     if (dx || dy || button)
2670       SetTileCursorEnabled(TRUE);
2671   }
2672
2673   // for any joystick event, enable playfield mouse cursor
2674   if (dx || dy || button)
2675     SetPlayfieldMouseCursorEnabled(TRUE);
2676
2677   if (joytest && !button && !DelayReached(&joytest_delay))
2678   {
2679     // delay joystick/keyboard actions if axes/keys continually pressed
2680     newbutton = dx = dy = 0;
2681   }
2682   else
2683   {
2684     // first start with longer delay, then continue with shorter delay
2685     joytest_delay.value =
2686       (use_delay_value_first ? delay_value_first : delay_value);
2687   }
2688
2689   joytest_last = joytest;
2690
2691   switch (game_status)
2692   {
2693     case GAME_MODE_TITLE:
2694     case GAME_MODE_MAIN:
2695     case GAME_MODE_NAMES:
2696     case GAME_MODE_LEVELS:
2697     case GAME_MODE_LEVELNR:
2698     case GAME_MODE_SETUP:
2699     case GAME_MODE_INFO:
2700     case GAME_MODE_SCORES:
2701     case GAME_MODE_SCOREINFO:
2702     {
2703       if (anyTextGadgetActive())
2704         break;
2705
2706       if (game_status == GAME_MODE_TITLE)
2707         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2708       else if (game_status == GAME_MODE_MAIN)
2709         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2710       else if (game_status == GAME_MODE_NAMES)
2711         HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2712       else if (game_status == GAME_MODE_LEVELS)
2713         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2714       else if (game_status == GAME_MODE_LEVELNR)
2715         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2716       else if (game_status == GAME_MODE_SETUP)
2717         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2718       else if (game_status == GAME_MODE_INFO)
2719         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2720       else if (game_status == GAME_MODE_SCORES)
2721         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2722       else if (game_status == GAME_MODE_SCOREINFO)
2723         HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2724
2725       break;
2726     }
2727
2728     case GAME_MODE_PLAYING:
2729 #if 0
2730       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2731       if (tape.playing || keyboard)
2732         newbutton = ((joy & JOY_BUTTON) != 0);
2733 #endif
2734
2735       if (newbutton && game.all_players_gone)
2736       {
2737         GameEnd();
2738
2739         return;
2740       }
2741
2742       if (tape.recording && tape.pausing && tape.use_key_actions)
2743       {
2744         if (tape.single_step)
2745         {
2746           if (joystick & JOY_ACTION)
2747             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2748         }
2749         else
2750         {
2751           if (joystick & JOY_ACTION)
2752             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2753         }
2754       }
2755
2756       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2757         HandleTileCursor(dx, dy, button);
2758
2759       break;
2760
2761     default:
2762       break;
2763   }
2764 }
2765
2766 void HandleSpecialGameControllerButtons(Event *event)
2767 {
2768   int key_status;
2769   Key key;
2770
2771   switch (event->type)
2772   {
2773     case SDL_CONTROLLERBUTTONDOWN:
2774       key_status = KEY_PRESSED;
2775       break;
2776
2777     case SDL_CONTROLLERBUTTONUP:
2778       key_status = KEY_RELEASED;
2779       break;
2780
2781     default:
2782       return;
2783   }
2784
2785   switch (event->cbutton.button)
2786   {
2787     case SDL_CONTROLLER_BUTTON_START:
2788       key = KSYM_space;
2789       break;
2790
2791     case SDL_CONTROLLER_BUTTON_BACK:
2792       key = KSYM_Escape;
2793       break;
2794
2795     default:
2796       return;
2797   }
2798
2799   HandleKey(key, key_status);
2800 }
2801
2802 void HandleSpecialGameControllerKeys(Key key, int key_status)
2803 {
2804 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2805   int button = SDL_CONTROLLER_BUTTON_INVALID;
2806
2807   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2808   if (key == KSYM_Rewind)
2809     button = SDL_CONTROLLER_BUTTON_A;
2810   else if (key == KSYM_FastForward || key == KSYM_Menu)
2811     button = SDL_CONTROLLER_BUTTON_B;
2812
2813   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2814   {
2815     Event event;
2816
2817     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2818                   SDL_CONTROLLERBUTTONUP);
2819
2820     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2821     event.cbutton.button = button;
2822     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2823                            SDL_RELEASED);
2824
2825     HandleJoystickEvent(&event);
2826   }
2827 #endif
2828 }
2829
2830 boolean DoKeysymAction(int keysym)
2831 {
2832   if (keysym < 0)
2833   {
2834     Key key = (Key)(-keysym);
2835
2836     HandleKey(key, KEY_PRESSED);
2837     HandleKey(key, KEY_RELEASED);
2838
2839     return TRUE;
2840   }
2841
2842   return FALSE;
2843 }