added delay counter data type and adjusted related functions
[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      "(UNKNOWN)");
624
625   Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
626         event_name, event->data1, event->data2);
627 #endif
628
629 #if 0
630   // (not needed, as the screen gets redrawn every 20 ms anyway)
631   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
632       event->event == SDL_WINDOWEVENT_RESIZED ||
633       event->event == SDL_WINDOWEVENT_EXPOSED)
634     SDLRedrawWindow();
635 #endif
636
637   if (event->event == SDL_WINDOWEVENT_RESIZED)
638   {
639     if (!video.fullscreen_enabled)
640     {
641       int new_window_width  = event->data1;
642       int new_window_height = event->data2;
643
644       // if window size has changed after resizing, calculate new scaling factor
645       if (new_window_width  != video.window_width ||
646           new_window_height != video.window_height)
647       {
648         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
649         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
650
651         // (extreme window scaling allowed, but cannot be saved permanently)
652         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
653         setup.window_scaling_percent =
654           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
655               MAX_WINDOW_SCALING_PERCENT);
656
657         video.window_width  = new_window_width;
658         video.window_height = new_window_height;
659
660         if (game_status == GAME_MODE_SETUP)
661           RedrawSetupScreenAfterFullscreenToggle();
662
663         UpdateMousePosition();
664
665         SetWindowTitle();
666       }
667     }
668 #if defined(PLATFORM_ANDROID)
669     else
670     {
671       int new_display_width  = event->data1;
672       int new_display_height = event->data2;
673
674       // if fullscreen display size has changed, device has been rotated
675       if (new_display_width  != video.display_width ||
676           new_display_height != video.display_height)
677       {
678         int nr = GRID_ACTIVE_NR();      // previous screen orientation
679
680         video.display_width  = new_display_width;
681         video.display_height = new_display_height;
682
683         SDLSetScreenProperties();
684         SetGadgetsPosition_OverlayTouchButtons();
685
686         // check if screen orientation has changed (should always be true here)
687         if (nr != GRID_ACTIVE_NR())
688         {
689           if (game_status == GAME_MODE_SETUP)
690             RedrawSetupScreenAfterScreenRotation(nr);
691
692           SetOverlayGridSizeAndButtons();
693         }
694       }
695     }
696 #endif
697   }
698 }
699
700 #define NUM_TOUCH_FINGERS               3
701
702 static struct
703 {
704   boolean touched;
705   SDL_FingerID finger_id;
706   int counter;
707   Key key;
708   byte action;
709 } touch_info[NUM_TOUCH_FINGERS];
710
711 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
712                          Key key, byte action)
713 {
714   touch_info[pos].touched = (action != JOY_NO_ACTION);
715   touch_info[pos].finger_id = finger_id;
716   touch_info[pos].counter = counter;
717   touch_info[pos].key = key;
718   touch_info[pos].action = action;
719 }
720
721 static void ClearTouchInfo(void)
722 {
723   int i;
724
725   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
726     SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
727 }
728
729 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
730 {
731   int x = event->x * overlay.grid_xsize;
732   int y = event->y * overlay.grid_ysize;
733   int grid_button = overlay.grid_button[x][y];
734   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
735   Key key = GetKeyFromGridButton(grid_button);
736   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
737                     KEY_PRESSED);
738   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
739                            "KEY_PRESSED");
740   int i;
741
742   // for any touch input event, enable overlay buttons (if activated)
743   SetOverlayEnabled(TRUE);
744
745   Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
746         getKeyNameFromKey(key), key_status_name, event->fingerId);
747
748   if (key_status == KEY_PRESSED)
749     overlay.grid_button_action |= grid_button_action;
750   else
751     overlay.grid_button_action &= ~grid_button_action;
752
753   // check if we already know this touch event's finger id
754   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755   {
756     if (touch_info[i].touched &&
757         touch_info[i].finger_id == event->fingerId)
758     {
759       // Debug("event:finger", "MARK 1: %d", i);
760
761       break;
762     }
763   }
764
765   if (i >= NUM_TOUCH_FINGERS)
766   {
767     if (key_status == KEY_PRESSED)
768     {
769       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
770
771       // unknown finger id -- get new, empty slot, if available
772       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
773       {
774         if (touch_info[i].counter < oldest_counter)
775         {
776           oldest_pos = i;
777           oldest_counter = touch_info[i].counter;
778
779           // Debug("event:finger", "MARK 2: %d", i);
780         }
781
782         if (!touch_info[i].touched)
783         {
784           // Debug("event:finger", "MARK 3: %d", i);
785
786           break;
787         }
788       }
789
790       if (i >= NUM_TOUCH_FINGERS)
791       {
792         // all slots allocated -- use oldest slot
793         i = oldest_pos;
794
795         // Debug("event:finger", "MARK 4: %d", i);
796       }
797     }
798     else
799     {
800       // release of previously unknown key (should not happen)
801
802       if (key != KSYM_UNDEFINED)
803       {
804         HandleKey(key, KEY_RELEASED);
805
806         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
807               getKeyNameFromKey(key), "KEY_RELEASED", i);
808       }
809     }
810   }
811
812   if (i < NUM_TOUCH_FINGERS)
813   {
814     if (key_status == KEY_PRESSED)
815     {
816       if (touch_info[i].key != key)
817       {
818         if (touch_info[i].key != KSYM_UNDEFINED)
819         {
820           HandleKey(touch_info[i].key, KEY_RELEASED);
821
822           // undraw previous grid button when moving finger away
823           overlay.grid_button_action &= ~touch_info[i].action;
824
825           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
826                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
827         }
828
829         if (key != KSYM_UNDEFINED)
830         {
831           HandleKey(key, KEY_PRESSED);
832
833           Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
834                 getKeyNameFromKey(key), "KEY_PRESSED", i);
835         }
836       }
837
838       SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
839     }
840     else
841     {
842       if (touch_info[i].key != KSYM_UNDEFINED)
843       {
844         HandleKey(touch_info[i].key, KEY_RELEASED);
845
846         Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
847               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
848       }
849
850       SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
851     }
852   }
853 }
854
855 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
856 {
857   static Key motion_key_x = KSYM_UNDEFINED;
858   static Key motion_key_y = KSYM_UNDEFINED;
859   static Key button_key = KSYM_UNDEFINED;
860   static float motion_x1, motion_y1;
861   static float button_x1, button_y1;
862   static SDL_FingerID motion_id = -1;
863   static SDL_FingerID button_id = -1;
864   int move_trigger_distance_percent = setup.touch.move_distance;
865   int drop_trigger_distance_percent = setup.touch.drop_distance;
866   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
867   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
868   float event_x = event->x;
869   float event_y = event->y;
870
871   if (event->type == EVENT_FINGERPRESS)
872   {
873     if (event_x > 1.0 / 3.0)
874     {
875       // motion area
876
877       motion_id = event->fingerId;
878
879       motion_x1 = event_x;
880       motion_y1 = event_y;
881
882       motion_key_x = KSYM_UNDEFINED;
883       motion_key_y = KSYM_UNDEFINED;
884
885       Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
886     }
887     else
888     {
889       // button area
890
891       button_id = event->fingerId;
892
893       button_x1 = event_x;
894       button_y1 = event_y;
895
896       button_key = setup.input[0].key.snap;
897
898       HandleKey(button_key, KEY_PRESSED);
899
900       Debug("event:finger", "---------- SNAP STARTED ----------");
901     }
902   }
903   else if (event->type == EVENT_FINGERRELEASE)
904   {
905     if (event->fingerId == motion_id)
906     {
907       motion_id = -1;
908
909       if (motion_key_x != KSYM_UNDEFINED)
910         HandleKey(motion_key_x, KEY_RELEASED);
911       if (motion_key_y != KSYM_UNDEFINED)
912         HandleKey(motion_key_y, KEY_RELEASED);
913
914       motion_key_x = KSYM_UNDEFINED;
915       motion_key_y = KSYM_UNDEFINED;
916
917       Debug("event:finger", "---------- MOVE STOPPED ----------");
918     }
919     else if (event->fingerId == button_id)
920     {
921       button_id = -1;
922
923       if (button_key != KSYM_UNDEFINED)
924         HandleKey(button_key, KEY_RELEASED);
925
926       button_key = KSYM_UNDEFINED;
927
928       Debug("event:finger", "---------- SNAP STOPPED ----------");
929     }
930   }
931   else if (event->type == EVENT_FINGERMOTION)
932   {
933     if (event->fingerId == motion_id)
934     {
935       float distance_x = ABS(event_x - motion_x1);
936       float distance_y = ABS(event_y - motion_y1);
937       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
938                               event_x > motion_x1 ? setup.input[0].key.right :
939                               KSYM_UNDEFINED);
940       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
941                               event_y > motion_y1 ? setup.input[0].key.down :
942                               KSYM_UNDEFINED);
943
944       if (distance_x < move_trigger_distance / 2 ||
945           distance_x < distance_y)
946         new_motion_key_x = KSYM_UNDEFINED;
947
948       if (distance_y < move_trigger_distance / 2 ||
949           distance_y < distance_x)
950         new_motion_key_y = KSYM_UNDEFINED;
951
952       if (distance_x > move_trigger_distance ||
953           distance_y > move_trigger_distance)
954       {
955         if (new_motion_key_x != motion_key_x)
956         {
957           if (motion_key_x != KSYM_UNDEFINED)
958             HandleKey(motion_key_x, KEY_RELEASED);
959           if (new_motion_key_x != KSYM_UNDEFINED)
960             HandleKey(new_motion_key_x, KEY_PRESSED);
961         }
962
963         if (new_motion_key_y != motion_key_y)
964         {
965           if (motion_key_y != KSYM_UNDEFINED)
966             HandleKey(motion_key_y, KEY_RELEASED);
967           if (new_motion_key_y != KSYM_UNDEFINED)
968             HandleKey(new_motion_key_y, KEY_PRESSED);
969         }
970
971         motion_x1 = event_x;
972         motion_y1 = event_y;
973
974         motion_key_x = new_motion_key_x;
975         motion_key_y = new_motion_key_y;
976
977         Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
978       }
979     }
980     else if (event->fingerId == button_id)
981     {
982       float distance_x = ABS(event_x - button_x1);
983       float distance_y = ABS(event_y - button_y1);
984
985       if (distance_x < drop_trigger_distance / 2 &&
986           distance_y > drop_trigger_distance)
987       {
988         if (button_key == setup.input[0].key.snap)
989           HandleKey(button_key, KEY_RELEASED);
990
991         button_x1 = event_x;
992         button_y1 = event_y;
993
994         button_key = setup.input[0].key.drop;
995
996         HandleKey(button_key, KEY_PRESSED);
997
998         Debug("event:finger", "---------- DROP STARTED ----------");
999       }
1000     }
1001   }
1002 }
1003
1004 void HandleFingerEvent(FingerEvent *event)
1005 {
1006 #if DEBUG_EVENTS_FINGER
1007   Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1008         event->type == EVENT_FINGERPRESS ? "pressed" :
1009         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1010         event->touchId,
1011         event->fingerId,
1012         event->x, event->y,
1013         event->dx, event->dy,
1014         event->pressure);
1015 #endif
1016
1017   runtime.uses_touch_device = TRUE;
1018
1019   if (game_status != GAME_MODE_PLAYING)
1020     return;
1021
1022   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1023   {
1024     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1025       local_player->mouse_action.button_hint =
1026         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1027          event->x < 0.5                     ? MB_LEFTBUTTON  :
1028          event->x > 0.5                     ? MB_RIGHTBUTTON :
1029          MB_NOT_PRESSED);
1030
1031     return;
1032   }
1033
1034   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1035     HandleFingerEvent_VirtualButtons(event);
1036   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1037     HandleFingerEvent_WipeGestures(event);
1038 }
1039
1040 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1041 {
1042   static int old_mx = 0, old_my = 0;
1043   static int last_button = MB_LEFTBUTTON;
1044   static boolean touched = FALSE;
1045   static boolean tapped = FALSE;
1046
1047   // screen tile was tapped (but finger not touching the screen anymore)
1048   // (this point will also be reached without receiving a touch event)
1049   if (tapped && !touched)
1050   {
1051     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1052
1053     tapped = FALSE;
1054   }
1055
1056   // stop here if this function was not triggered by a touch event
1057   if (button == -1)
1058     return;
1059
1060   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1061   {
1062     // finger started touching the screen
1063
1064     touched = TRUE;
1065     tapped = TRUE;
1066
1067     if (!motion_status)
1068     {
1069       old_mx = mx;
1070       old_my = my;
1071
1072       ClearPlayerMouseAction();
1073
1074       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1075     }
1076   }
1077   else if (button == MB_RELEASED && touched)
1078   {
1079     // finger stopped touching the screen
1080
1081     touched = FALSE;
1082
1083     if (tapped)
1084       SetPlayerMouseAction(old_mx, old_my, last_button);
1085     else
1086       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1087
1088     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1089   }
1090
1091   if (touched)
1092   {
1093     // finger moved while touching the screen
1094
1095     int old_x = getLevelFromScreenX(old_mx);
1096     int old_y = getLevelFromScreenY(old_my);
1097     int new_x = getLevelFromScreenX(mx);
1098     int new_y = getLevelFromScreenY(my);
1099
1100     if (new_x != old_x || new_y != old_y)
1101       tapped = FALSE;
1102
1103     if (new_x != old_x)
1104     {
1105       // finger moved left or right from (horizontal) starting position
1106
1107       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1108
1109       SetPlayerMouseAction(old_mx, old_my, button_nr);
1110
1111       last_button = button_nr;
1112
1113       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1114     }
1115     else
1116     {
1117       // finger stays at or returned to (horizontal) starting position
1118
1119       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1120
1121       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1122     }
1123   }
1124 }
1125
1126 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1127 {
1128   static int old_mx = 0, old_my = 0;
1129   static int last_button = MB_LEFTBUTTON;
1130   static boolean touched = FALSE;
1131   static boolean tapped = FALSE;
1132
1133   // screen tile was tapped (but finger not touching the screen anymore)
1134   // (this point will also be reached without receiving a touch event)
1135   if (tapped && !touched)
1136   {
1137     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1138
1139     tapped = FALSE;
1140   }
1141
1142   // stop here if this function was not triggered by a touch event
1143   if (button == -1)
1144     return;
1145
1146   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1147   {
1148     // finger started touching the screen
1149
1150     touched = TRUE;
1151     tapped = TRUE;
1152
1153     if (!motion_status)
1154     {
1155       old_mx = mx;
1156       old_my = my;
1157
1158       ClearPlayerMouseAction();
1159
1160       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1161     }
1162   }
1163   else if (button == MB_RELEASED && touched)
1164   {
1165     // finger stopped touching the screen
1166
1167     touched = FALSE;
1168
1169     if (tapped)
1170       SetPlayerMouseAction(old_mx, old_my, last_button);
1171     else
1172       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1173
1174     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1175   }
1176
1177   if (touched)
1178   {
1179     // finger moved while touching the screen
1180
1181     int old_x = getLevelFromScreenX(old_mx);
1182     int old_y = getLevelFromScreenY(old_my);
1183     int new_x = getLevelFromScreenX(mx);
1184     int new_y = getLevelFromScreenY(my);
1185
1186     if (new_x != old_x || new_y != old_y)
1187     {
1188       // finger moved away from starting position
1189
1190       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1191
1192       // quickly alternate between clicking and releasing for maximum speed
1193       if (FrameCounter % 2 == 0)
1194         button_nr = MB_RELEASED;
1195
1196       SetPlayerMouseAction(old_mx, old_my, button_nr);
1197
1198       if (button_nr)
1199         last_button = button_nr;
1200
1201       tapped = FALSE;
1202
1203       Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1204     }
1205     else
1206     {
1207       // finger stays at or returned to starting position
1208
1209       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1210
1211       Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1212     }
1213   }
1214 }
1215
1216 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1217 {
1218   static int old_mx = 0, old_my = 0;
1219   static Key motion_key_x = KSYM_UNDEFINED;
1220   static Key motion_key_y = KSYM_UNDEFINED;
1221   static boolean touched = FALSE;
1222   static boolean started_on_player = FALSE;
1223   static boolean player_is_dropping = FALSE;
1224   static int player_drop_count = 0;
1225   static int last_player_x = -1;
1226   static int last_player_y = -1;
1227
1228   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1229   {
1230     touched = TRUE;
1231
1232     old_mx = mx;
1233     old_my = my;
1234
1235     if (!motion_status)
1236     {
1237       started_on_player = FALSE;
1238       player_is_dropping = FALSE;
1239       player_drop_count = 0;
1240       last_player_x = -1;
1241       last_player_y = -1;
1242
1243       motion_key_x = KSYM_UNDEFINED;
1244       motion_key_y = KSYM_UNDEFINED;
1245
1246       Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1247     }
1248   }
1249   else if (button == MB_RELEASED && touched)
1250   {
1251     touched = FALSE;
1252
1253     old_mx = 0;
1254     old_my = 0;
1255
1256     if (motion_key_x != KSYM_UNDEFINED)
1257       HandleKey(motion_key_x, KEY_RELEASED);
1258     if (motion_key_y != KSYM_UNDEFINED)
1259       HandleKey(motion_key_y, KEY_RELEASED);
1260
1261     if (started_on_player)
1262     {
1263       if (player_is_dropping)
1264       {
1265         Debug("event:finger", "---------- DROP STOPPED ----------");
1266
1267         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1268       }
1269       else
1270       {
1271         Debug("event:finger", "---------- SNAP STOPPED ----------");
1272
1273         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1274       }
1275     }
1276
1277     motion_key_x = KSYM_UNDEFINED;
1278     motion_key_y = KSYM_UNDEFINED;
1279
1280     Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1281   }
1282
1283   if (touched)
1284   {
1285     int src_x = local_player->jx;
1286     int src_y = local_player->jy;
1287     int dst_x = getLevelFromScreenX(old_mx);
1288     int dst_y = getLevelFromScreenY(old_my);
1289     int dx = dst_x - src_x;
1290     int dy = dst_y - src_y;
1291     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1292                             dx > 0 ? setup.input[0].key.right :
1293                             KSYM_UNDEFINED);
1294     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1295                             dy > 0 ? setup.input[0].key.down :
1296                             KSYM_UNDEFINED);
1297
1298     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1299         (last_player_x != local_player->jx ||
1300          last_player_y != local_player->jy))
1301     {
1302       // in case of asymmetric diagonal movement, use "preferred" direction
1303
1304       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1305
1306       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1307         game_em.ply[0]->last_move_dir = last_move_dir;
1308       else
1309         local_player->last_move_dir = last_move_dir;
1310
1311       // (required to prevent accidentally forcing direction for next movement)
1312       last_player_x = local_player->jx;
1313       last_player_y = local_player->jy;
1314     }
1315
1316     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1317     {
1318       started_on_player = TRUE;
1319       player_drop_count = getPlayerInventorySize(0);
1320       player_is_dropping = (player_drop_count > 0);
1321
1322       if (player_is_dropping)
1323       {
1324         Debug("event:finger", "---------- DROP STARTED ----------");
1325
1326         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1327       }
1328       else
1329       {
1330         Debug("event:finger", "---------- SNAP STARTED ----------");
1331
1332         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1333       }
1334     }
1335     else if (dx != 0 || dy != 0)
1336     {
1337       if (player_is_dropping &&
1338           player_drop_count == getPlayerInventorySize(0))
1339       {
1340         Debug("event:finger", "---------- DROP -> SNAP ----------");
1341
1342         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1343         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1344
1345         player_is_dropping = FALSE;
1346       }
1347     }
1348
1349     if (new_motion_key_x != motion_key_x)
1350     {
1351       Debug("event:finger", "---------- %s %s ----------",
1352             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1353             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1354
1355       if (motion_key_x != KSYM_UNDEFINED)
1356         HandleKey(motion_key_x, KEY_RELEASED);
1357       if (new_motion_key_x != KSYM_UNDEFINED)
1358         HandleKey(new_motion_key_x, KEY_PRESSED);
1359     }
1360
1361     if (new_motion_key_y != motion_key_y)
1362     {
1363       Debug("event:finger", "---------- %s %s ----------",
1364             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1365             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1366
1367       if (motion_key_y != KSYM_UNDEFINED)
1368         HandleKey(motion_key_y, KEY_RELEASED);
1369       if (new_motion_key_y != KSYM_UNDEFINED)
1370         HandleKey(new_motion_key_y, KEY_PRESSED);
1371     }
1372
1373     motion_key_x = new_motion_key_x;
1374     motion_key_y = new_motion_key_y;
1375   }
1376 }
1377
1378 static void HandleButtonOrFinger(int mx, int my, int button)
1379 {
1380   boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1381
1382   if (game_status != GAME_MODE_PLAYING)
1383     return;
1384
1385   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1386   {
1387     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1388       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1389     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1391     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1392       SetPlayerMouseAction(mx, my, button);     // special case
1393   }
1394   else
1395   {
1396     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1397       HandleButtonOrFinger_FollowFinger(mx, my, button);
1398     else if (game.use_mouse_actions && valid_mouse_event)
1399       SetPlayerMouseAction(mx, my, button);
1400   }
1401 }
1402
1403 static boolean checkTextInputKey(Key key)
1404 {
1405   // when playing, only handle raw key events and ignore text input
1406   if (game_status == GAME_MODE_PLAYING)
1407     return FALSE;
1408
1409   // if Shift or right Alt key is pressed, handle key as text input
1410   if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1411     return TRUE;
1412
1413   // ignore raw keys as text input when not in text input mode
1414   if (KSYM_RAW(key) && !textinput_status)
1415     return FALSE;
1416
1417   // else handle all printable keys as text input
1418   return KSYM_PRINTABLE(key);
1419 }
1420
1421 void HandleTextEvent(TextEvent *event)
1422 {
1423   char *text = event->text;
1424   Key key = getKeyFromKeyName(text);
1425
1426 #if DEBUG_EVENTS_TEXT
1427   Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1428         text,
1429         strlen(text),
1430         text[0], (int)(text[0]),
1431         key,
1432         getKeyNameFromKey(key),
1433         GetKeyModState());
1434 #endif
1435
1436   if (checkTextInputKey(key))
1437   {
1438     // process printable keys (with uppercase etc.) in text input mode
1439     HandleKey(key, KEY_PRESSED);
1440     HandleKey(key, KEY_RELEASED);
1441   }
1442 }
1443
1444 void HandlePauseResumeEvent(PauseResumeEvent *event)
1445 {
1446   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1447   {
1448     Mix_PauseMusic();
1449   }
1450   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1451   {
1452     Mix_ResumeMusic();
1453   }
1454 }
1455
1456 void HandleKeyEvent(KeyEvent *event)
1457 {
1458   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1459   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1460   Key key = GetEventKey(event, with_modifiers);
1461   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1462
1463 #if DEBUG_EVENTS_KEY
1464   Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1465         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1466         event->keysym.scancode,
1467         event->keysym.sym,
1468         keymod,
1469         GetKeyModState(),
1470         key,
1471         getKeyNameFromKey(key));
1472 #endif
1473
1474 #if defined(PLATFORM_ANDROID)
1475   if (key == KSYM_Back)
1476   {
1477     // always map the "back" button to the "escape" key on Android devices
1478     key = KSYM_Escape;
1479   }
1480   else if (key == KSYM_Menu)
1481   {
1482     // the "menu" button can be used to toggle displaying virtual buttons
1483     if (key_status == KEY_PRESSED)
1484       SetOverlayEnabled(!GetOverlayEnabled());
1485   }
1486   else if (!textinput_status)
1487   {
1488     // for any other "real" key event, disable virtual buttons
1489     SetOverlayEnabled(FALSE);
1490
1491     // for any other "real" key event, disable overlay touch buttons
1492     runtime.uses_touch_device = FALSE;
1493   }
1494 #endif
1495
1496   HandleKeyModState(keymod, key_status);
1497
1498   // process all keys if not in text input mode or if non-printable keys
1499   if (!checkTextInputKey(key))
1500     HandleKey(key, key_status);
1501 }
1502
1503 static int HandleDropFileEvent(char *filename)
1504 {
1505   Debug("event:dropfile", "filename == '%s'", filename);
1506
1507   // check and extract dropped zip files into correct user data directory
1508   if (!strSuffixLower(filename, ".zip"))
1509   {
1510     Warn("file '%s' not supported", filename);
1511
1512     return TREE_TYPE_UNDEFINED;
1513   }
1514
1515   TreeInfo *tree_node = NULL;
1516   int tree_type = GetZipFileTreeType(filename);
1517   char *directory = TREE_USERDIR(tree_type);
1518
1519   if (directory == NULL)
1520   {
1521     Warn("zip file '%s' has invalid content!", filename);
1522
1523     return TREE_TYPE_UNDEFINED;
1524   }
1525
1526   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1527       game_status == GAME_MODE_LEVELS &&
1528       leveldir_current->node_parent != NULL)
1529   {
1530     // extract new level set next to currently selected level set
1531     tree_node = leveldir_current;
1532
1533     // get parent directory of currently selected level set directory
1534     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1535
1536     // use private level directory instead of top-level package level directory
1537     if (strPrefix(directory, options.level_directory) &&
1538         strEqual(leveldir_current->node_parent->fullpath, "."))
1539       directory = getUserLevelDir(NULL);
1540   }
1541
1542   // extract level or artwork set from zip file to target directory
1543   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1544
1545   if (top_dir == NULL)
1546   {
1547     // error message already issued by "ExtractZipFileIntoDirectory()"
1548
1549     return TREE_TYPE_UNDEFINED;
1550   }
1551
1552   // add extracted level or artwork set to tree info structure
1553   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1554
1555   // update menu screen (and possibly change current level set)
1556   DrawScreenAfterAddingSet(top_dir, tree_type);
1557
1558   return tree_type;
1559 }
1560
1561 static void HandleDropTextEvent(char *text)
1562 {
1563   Debug("event:droptext", "text == '%s'", text);
1564 }
1565
1566 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1567                                     int num_artwork_sets_succeeded,
1568                                     int num_files_failed)
1569 {
1570   // only show request dialog if no other request dialog already active
1571   if (game.request_active)
1572     return;
1573
1574   // this case can happen with drag-and-drop with older SDL versions
1575   if (num_level_sets_succeeded == 0 &&
1576       num_artwork_sets_succeeded == 0 &&
1577       num_files_failed == 0)
1578     return;
1579
1580   char message[100];
1581
1582   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1583   {
1584     char message_part1[50];
1585
1586     sprintf(message_part1, "New %s set%s added",
1587             (num_artwork_sets_succeeded == 0 ? "level" :
1588              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1589             (num_level_sets_succeeded +
1590              num_artwork_sets_succeeded > 1 ? "s" : ""));
1591
1592     if (num_files_failed > 0)
1593       sprintf(message, "%s, but %d dropped file%s failed!",
1594               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1595     else
1596       sprintf(message, "%s!", message_part1);
1597   }
1598   else if (num_files_failed > 0)
1599   {
1600     sprintf(message, "Failed to process dropped file%s!",
1601             num_files_failed > 1 ? "s" : "");
1602   }
1603
1604   Request(message, REQ_CONFIRM);
1605 }
1606
1607 void HandleDropEvent(Event *event)
1608 {
1609   static boolean confirm_on_drop_complete = FALSE;
1610   static int num_level_sets_succeeded = 0;
1611   static int num_artwork_sets_succeeded = 0;
1612   static int num_files_failed = 0;
1613
1614   switch (event->type)
1615   {
1616     case SDL_DROPBEGIN:
1617     {
1618       confirm_on_drop_complete = TRUE;
1619       num_level_sets_succeeded = 0;
1620       num_artwork_sets_succeeded = 0;
1621       num_files_failed = 0;
1622
1623       break;
1624     }
1625
1626     case SDL_DROPFILE:
1627     {
1628       int tree_type = HandleDropFileEvent(event->drop.file);
1629
1630       if (tree_type == TREE_TYPE_LEVEL_DIR)
1631         num_level_sets_succeeded++;
1632       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1633                tree_type == TREE_TYPE_SOUNDS_DIR ||
1634                tree_type == TREE_TYPE_MUSIC_DIR)
1635         num_artwork_sets_succeeded++;
1636       else
1637         num_files_failed++;
1638
1639       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1640       if (!confirm_on_drop_complete)
1641       {
1642         // process all remaining events, including further SDL_DROPFILE events
1643         ClearEventQueue();
1644
1645         HandleDropCompleteEvent(num_level_sets_succeeded,
1646                                 num_artwork_sets_succeeded,
1647                                 num_files_failed);
1648
1649         num_level_sets_succeeded = 0;
1650         num_artwork_sets_succeeded = 0;
1651         num_files_failed = 0;
1652       }
1653
1654       break;
1655     }
1656
1657     case SDL_DROPTEXT:
1658     {
1659       HandleDropTextEvent(event->drop.file);
1660
1661       break;
1662     }
1663
1664     case SDL_DROPCOMPLETE:
1665     {
1666       HandleDropCompleteEvent(num_level_sets_succeeded,
1667                               num_artwork_sets_succeeded,
1668                               num_files_failed);
1669
1670       break;
1671     }
1672   }
1673
1674   if (event->drop.file != NULL)
1675     SDL_free(event->drop.file);
1676 }
1677
1678 void HandleUserEvent(UserEvent *event)
1679 {
1680   switch (event->code)
1681   {
1682     case USEREVENT_ANIM_DELAY_ACTION:
1683     case USEREVENT_ANIM_EVENT_ACTION:
1684       // execute action functions until matching action was found
1685       if (DoKeysymAction(event->value1) ||
1686           DoGadgetAction(event->value1) ||
1687           DoScreenAction(event->value1))
1688         return;
1689       break;
1690
1691     default:
1692       break;
1693   }
1694 }
1695
1696 void HandleButton(int mx, int my, int button, int button_nr)
1697 {
1698   static int old_mx = 0, old_my = 0;
1699   boolean button_hold = FALSE;
1700   boolean handle_gadgets = TRUE;
1701
1702   if (button_nr < 0)
1703   {
1704     mx = old_mx;
1705     my = old_my;
1706     button_nr = -button_nr;
1707     button_hold = TRUE;
1708   }
1709   else
1710   {
1711     old_mx = mx;
1712     old_my = my;
1713   }
1714
1715 #if defined(PLATFORM_ANDROID)
1716   // when playing, only handle gadgets when using "follow finger" controls
1717   // or when using touch controls in combination with the MM game engine
1718   // or when using gadgets that do not overlap with virtual buttons
1719   // or when touch controls are disabled (e.g., with mouse-only levels)
1720   handle_gadgets =
1721     (game_status != GAME_MODE_PLAYING ||
1722      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1723      strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1724      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1725      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1726       !CheckVirtualButtonPressed(mx, my, button)));
1727
1728   // always recognize potentially releasing already pressed gadgets
1729   if (button == MB_RELEASED)
1730     handle_gadgets = TRUE;
1731
1732   // always recognize pressing or releasing overlay touch buttons
1733   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1734     handle_gadgets = TRUE;
1735 #endif
1736
1737   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1738   {
1739     // do not handle this button event anymore
1740     return;             // force mouse event not to be handled at all
1741   }
1742
1743   if (handle_gadgets && HandleGadgets(mx, my, button))
1744   {
1745     // do not handle this button event anymore
1746     mx = my = -32;      // force mouse event to be outside screen tiles
1747   }
1748
1749   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1750     return;
1751
1752   // do not use scroll wheel button events for anything other than gadgets
1753   if (IS_WHEEL_BUTTON(button_nr))
1754     return;
1755
1756   switch (game_status)
1757   {
1758     case GAME_MODE_TITLE:
1759       HandleTitleScreen(mx, my, 0, 0, button);
1760       break;
1761
1762     case GAME_MODE_MAIN:
1763       HandleMainMenu(mx, my, 0, 0, button);
1764       break;
1765
1766     case GAME_MODE_PSEUDO_TYPENAME:
1767     case GAME_MODE_PSEUDO_TYPENAMES:
1768       HandleTypeName(KSYM_Return);
1769       break;
1770
1771     case GAME_MODE_NAMES:
1772       HandleChoosePlayerName(mx, my, 0, 0, button);
1773       break;
1774
1775     case GAME_MODE_LEVELS:
1776       HandleChooseLevelSet(mx, my, 0, 0, button);
1777       break;
1778
1779     case GAME_MODE_LEVELNR:
1780       HandleChooseLevelNr(mx, my, 0, 0, button);
1781       break;
1782
1783     case GAME_MODE_SCORES:
1784       HandleHallOfFame(mx, my, 0, 0, button);
1785       break;
1786
1787     case GAME_MODE_SCOREINFO:
1788       HandleScoreInfo(mx, my, 0, 0, button);
1789       break;
1790
1791     case GAME_MODE_EDITOR:
1792       HandleLevelEditorIdle();
1793       break;
1794
1795     case GAME_MODE_INFO:
1796       HandleInfoScreen(mx, my, 0, 0, button);
1797       break;
1798
1799     case GAME_MODE_SETUP:
1800       HandleSetupScreen(mx, my, 0, 0, button);
1801       break;
1802
1803     case GAME_MODE_PLAYING:
1804       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1805         HandleButtonOrFinger(mx, my, button);
1806       else
1807         SetPlayerMouseAction(mx, my, button);
1808
1809 #ifdef DEBUG
1810       if (button == MB_PRESSED && !motion_status && !button_hold &&
1811           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1812         DumpTileFromScreen(mx, my);
1813 #endif
1814
1815       break;
1816
1817     default:
1818       break;
1819   }
1820 }
1821
1822 #define MAX_CHEAT_INPUT_LEN     32
1823
1824 static void HandleKeysSpecial(Key key)
1825 {
1826   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1827   char letter = getCharFromKey(key);
1828   int cheat_input_len = strlen(cheat_input);
1829   int i;
1830
1831   if (letter == 0)
1832     return;
1833
1834   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1835   {
1836     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1837       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1838
1839     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1840   }
1841
1842   cheat_input[cheat_input_len++] = letter;
1843   cheat_input[cheat_input_len] = '\0';
1844
1845 #if DEBUG_EVENTS_KEY
1846   Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1847 #endif
1848
1849   if (game_status == GAME_MODE_MAIN)
1850   {
1851     if (strSuffix(cheat_input, ":insert-solution-tape") ||
1852         strSuffix(cheat_input, ":ist"))
1853     {
1854       InsertSolutionTape();
1855     }
1856     else if (strSuffix(cheat_input, ":play-solution-tape") ||
1857              strSuffix(cheat_input, ":pst"))
1858     {
1859       PlaySolutionTape();
1860     }
1861     else if (strSuffix(cheat_input, ":reload-graphics") ||
1862              strSuffix(cheat_input, ":rg"))
1863     {
1864       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1865       DrawMainMenu();
1866     }
1867     else if (strSuffix(cheat_input, ":reload-sounds") ||
1868              strSuffix(cheat_input, ":rs"))
1869     {
1870       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1871       DrawMainMenu();
1872     }
1873     else if (strSuffix(cheat_input, ":reload-music") ||
1874              strSuffix(cheat_input, ":rm"))
1875     {
1876       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1877       DrawMainMenu();
1878     }
1879     else if (strSuffix(cheat_input, ":reload-artwork") ||
1880              strSuffix(cheat_input, ":ra"))
1881     {
1882       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1883                           1 << ARTWORK_TYPE_SOUNDS |
1884                           1 << ARTWORK_TYPE_MUSIC);
1885       DrawMainMenu();
1886     }
1887     else if (strSuffix(cheat_input, ":dump-level") ||
1888              strSuffix(cheat_input, ":dl"))
1889     {
1890       DumpLevel(&level);
1891     }
1892     else if (strSuffix(cheat_input, ":dump-tape") ||
1893              strSuffix(cheat_input, ":dt"))
1894     {
1895       DumpTape(&tape);
1896     }
1897     else if (strSuffix(cheat_input, ":undo-tape") ||
1898              strSuffix(cheat_input, ":ut"))
1899     {
1900       UndoTape();
1901     }
1902     else if (strSuffix(cheat_input, ":fix-tape") ||
1903              strSuffix(cheat_input, ":ft"))
1904     {
1905       FixTape_ForceSinglePlayer();
1906     }
1907     else if (strSuffix(cheat_input, ":save-native-level") ||
1908              strSuffix(cheat_input, ":snl"))
1909     {
1910       SaveNativeLevel(&level);
1911     }
1912     else if (strSuffix(cheat_input, ":frames-per-second") ||
1913              strSuffix(cheat_input, ":fps"))
1914     {
1915       global.show_frames_per_second = !global.show_frames_per_second;
1916     }
1917     else if (strSuffix(cheat_input, ":xsn"))
1918     {
1919       tile_cursor.xsn_debug = TRUE;
1920     }
1921   }
1922   else if (game_status == GAME_MODE_PLAYING)
1923   {
1924 #ifdef DEBUG
1925     if (strSuffix(cheat_input, ".q"))
1926       DEBUG_SetMaximumDynamite();
1927 #endif
1928   }
1929   else if (game_status == GAME_MODE_EDITOR)
1930   {
1931     if (strSuffix(cheat_input, ":dump-brush") ||
1932         strSuffix(cheat_input, ":DB"))
1933     {
1934       DumpBrush();
1935     }
1936     else if (strSuffix(cheat_input, ":DDB"))
1937     {
1938       DumpBrush_Small();
1939     }
1940
1941     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1942     {
1943       if (letter == 'x')        // copy brush to clipboard (small size)
1944       {
1945         CopyBrushToClipboard_Small();
1946       }
1947       else if (letter == 'c')   // copy brush to clipboard (normal size)
1948       {
1949         CopyBrushToClipboard();
1950       }
1951       else if (letter == 'v')   // paste brush from Clipboard
1952       {
1953         CopyClipboardToBrush();
1954       }
1955       else if (letter == 'z')   // undo or redo last operation
1956       {
1957         if (GetKeyModState() & KMOD_Shift)
1958           RedoLevelEditorOperation();
1959         else
1960           UndoLevelEditorOperation();
1961       }
1962     }
1963   }
1964
1965   // special key shortcuts for all game modes
1966   if (strSuffix(cheat_input, ":dump-event-actions") ||
1967       strSuffix(cheat_input, ":dea") ||
1968       strSuffix(cheat_input, ":DEA"))
1969   {
1970     DumpGadgetIdentifiers();
1971     DumpScreenIdentifiers();
1972   }
1973 }
1974
1975 boolean HandleKeysDebug(Key key, int key_status)
1976 {
1977 #ifdef DEBUG
1978   int i;
1979
1980   if (key_status != KEY_PRESSED)
1981     return FALSE;
1982
1983   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1984   {
1985     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1986
1987     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1988     {
1989       if (key == setup.debug.frame_delay_key[i] &&
1990           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1991       {
1992         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1993                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1994
1995         if (!setup.debug.frame_delay_game_only)
1996           MenuFrameDelay = GameFrameDelay;
1997
1998         SetVideoFrameDelay(GameFrameDelay);
1999
2000         if (GameFrameDelay > ONE_SECOND_DELAY)
2001           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2002         else if (GameFrameDelay != 0)
2003           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2004                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2005                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2006         else
2007           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2008
2009         return TRUE;
2010       }
2011     }
2012   }
2013
2014   if (game_status == GAME_MODE_PLAYING)
2015   {
2016     if (key == KSYM_d)
2017     {
2018       options.debug = !options.debug;
2019
2020       Debug("event:key:debug", "debug mode %s",
2021             (options.debug ? "enabled" : "disabled"));
2022
2023       return TRUE;
2024     }
2025     else if (key == KSYM_v)
2026     {
2027       Debug("event:key:debug", "currently using game engine version %d",
2028             game.engine_version);
2029
2030       return TRUE;
2031     }
2032   }
2033 #endif
2034
2035   return FALSE;
2036 }
2037
2038 void HandleKey(Key key, int key_status)
2039 {
2040   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2041   static boolean ignore_repeated_key = FALSE;
2042   static struct SetupKeyboardInfo ski;
2043   static struct SetupShortcutInfo ssi;
2044   static struct
2045   {
2046     Key *key_custom;
2047     Key *key_snap;
2048     Key key_default;
2049     byte action;
2050   } key_info[] =
2051   {
2052     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2053     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2054     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2055     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2056     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2057     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2058   };
2059   int joy = 0;
2060   int i;
2061
2062   if (HandleKeysDebug(key, key_status))
2063     return;             // do not handle already processed keys again
2064
2065   // map special keys (media keys / remote control buttons) to default keys
2066   if (key == KSYM_PlayPause)
2067     key = KSYM_space;
2068   else if (key == KSYM_Select)
2069     key = KSYM_Return;
2070
2071   HandleSpecialGameControllerKeys(key, key_status);
2072
2073   if (game_status == GAME_MODE_PLAYING)
2074   {
2075     // only needed for single-step tape recording mode
2076     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2077     int pnr;
2078
2079     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2080     {
2081       byte key_action = 0;
2082       byte key_snap_action = 0;
2083
2084       if (setup.input[pnr].use_joystick)
2085         continue;
2086
2087       ski = setup.input[pnr].key;
2088
2089       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2090         if (key == *key_info[i].key_custom)
2091           key_action |= key_info[i].action;
2092
2093       // use combined snap+direction keys for the first player only
2094       if (pnr == 0)
2095       {
2096         ssi = setup.shortcut;
2097
2098         // also remember normal snap key when handling snap+direction keys
2099         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2100
2101         for (i = 0; i < NUM_DIRECTIONS; i++)
2102         {
2103           if (key == *key_info[i].key_snap)
2104           {
2105             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2106             key_snap_action |= key_info[i].action;
2107
2108             tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2109           }
2110         }
2111       }
2112
2113       if (key_status == KEY_PRESSED)
2114       {
2115         stored_player[pnr].action      |= key_action;
2116         stored_player[pnr].snap_action |= key_snap_action;
2117       }
2118       else
2119       {
2120         stored_player[pnr].action      &= ~key_action;
2121         stored_player[pnr].snap_action &= ~key_snap_action;
2122       }
2123
2124       // restore snap action if one of several pressed snap keys was released
2125       if (stored_player[pnr].snap_action)
2126         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2127
2128       if (tape.recording && tape.pausing && tape.use_key_actions)
2129       {
2130         if (tape.single_step)
2131         {
2132           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2133           {
2134             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2135
2136             // if snap key already pressed, keep pause mode when releasing
2137             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2138               has_snapped[pnr] = TRUE;
2139           }
2140           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2141           {
2142             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2143
2144             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2145                 getRedDiskReleaseFlag_SP() == 0)
2146             {
2147               // add a single inactive frame before dropping starts
2148               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2149               stored_player[pnr].force_dropping = TRUE;
2150             }
2151           }
2152           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2153           {
2154             // if snap key was pressed without direction, leave pause mode
2155             if (!has_snapped[pnr])
2156               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2157
2158             has_snapped[pnr] = FALSE;
2159           }
2160         }
2161         else
2162         {
2163           // prevent key release events from un-pausing a paused game
2164           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2165             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2166         }
2167       }
2168
2169       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2170       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2171         joy |= key_action;
2172
2173       // for any keyboard event, enable playfield mouse cursor
2174       if (key_action && key_status == KEY_PRESSED)
2175         SetPlayfieldMouseCursorEnabled(TRUE);
2176     }
2177   }
2178   else
2179   {
2180     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2181       if (key == key_info[i].key_default)
2182         joy |= key_info[i].action;
2183   }
2184
2185   if (joy)
2186   {
2187     if (key_status == KEY_PRESSED)
2188       key_joystick_mapping |= joy;
2189     else
2190       key_joystick_mapping &= ~joy;
2191
2192     HandleJoystick();
2193   }
2194
2195   if (game_status != GAME_MODE_PLAYING)
2196     key_joystick_mapping = 0;
2197
2198   if (key_status == KEY_RELEASED)
2199   {
2200     // reset flag to ignore repeated "key pressed" events after key release
2201     ignore_repeated_key = FALSE;
2202
2203     return;
2204   }
2205
2206   if ((key == KSYM_F11 ||
2207        ((key == KSYM_Return ||
2208          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2209       video.fullscreen_available &&
2210       !ignore_repeated_key)
2211   {
2212     setup.fullscreen = !setup.fullscreen;
2213
2214     ToggleFullscreenIfNeeded();
2215
2216     if (game_status == GAME_MODE_SETUP)
2217       RedrawSetupScreenAfterFullscreenToggle();
2218
2219     UpdateMousePosition();
2220
2221     // set flag to ignore repeated "key pressed" events
2222     ignore_repeated_key = TRUE;
2223
2224     return;
2225   }
2226
2227   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2228        key == KSYM_minus || key == KSYM_KP_Subtract ||
2229        key == KSYM_plus  || key == KSYM_KP_Add ||
2230        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2231       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2232       video.window_scaling_available &&
2233       !video.fullscreen_enabled)
2234   {
2235     if (key == KSYM_0 || key == KSYM_KP_0)
2236       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2237     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2238       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2239     else
2240       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2241
2242     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2243       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2244     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2245       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2246
2247     ChangeWindowScalingIfNeeded();
2248
2249     if (game_status == GAME_MODE_SETUP)
2250       RedrawSetupScreenAfterFullscreenToggle();
2251
2252     UpdateMousePosition();
2253
2254     return;
2255   }
2256
2257   // some key events are handled like clicks for global animations
2258   boolean click = (key == KSYM_space ||
2259                    key == KSYM_Return ||
2260                    key == KSYM_Escape);
2261
2262   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2263   {
2264     // do not handle this key event anymore
2265     if (key != KSYM_Escape)     // always allow ESC key to be handled
2266       return;
2267   }
2268
2269   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2270       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2271   {
2272     GameEnd();
2273
2274     return;
2275   }
2276
2277   if (game_status == GAME_MODE_MAIN &&
2278       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2279   {
2280     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2281
2282     return;
2283   }
2284
2285   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2286   {
2287     if (key == setup.shortcut.save_game)
2288       TapeQuickSave();
2289     else if (key == setup.shortcut.load_game)
2290       TapeQuickLoad();
2291     else if (key == setup.shortcut.restart_game)
2292       TapeRestartGame();
2293     else if (key == setup.shortcut.pause_before_end)
2294       TapeReplayAndPauseBeforeEnd();
2295     else if (key == setup.shortcut.toggle_pause)
2296       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2297
2298     HandleTapeButtonKeys(key);
2299     HandleSoundButtonKeys(key);
2300   }
2301
2302   if (game_status == GAME_MODE_SCOREINFO)
2303   {
2304     HandleScreenGadgetKeys(key);
2305   }
2306
2307   if (game_status == GAME_MODE_PLAYING && !network_playing)
2308   {
2309     int centered_player_nr_next = -999;
2310
2311     if (key == setup.shortcut.focus_player_all)
2312       centered_player_nr_next = -1;
2313     else
2314       for (i = 0; i < MAX_PLAYERS; i++)
2315         if (key == setup.shortcut.focus_player[i])
2316           centered_player_nr_next = i;
2317
2318     if (centered_player_nr_next != -999)
2319     {
2320       game.centered_player_nr_next = centered_player_nr_next;
2321       game.set_centered_player = TRUE;
2322
2323       if (tape.recording)
2324       {
2325         tape.centered_player_nr_next = game.centered_player_nr_next;
2326         tape.set_centered_player = TRUE;
2327       }
2328     }
2329   }
2330
2331   HandleKeysSpecial(key);
2332
2333   if (HandleGadgetsKeyInput(key))
2334     return;             // do not handle already processed keys again
2335
2336   // special case: on "space" key, either continue playing or go to main menu
2337   if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2338   {
2339     HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2340
2341     return;
2342   }
2343
2344   switch (game_status)
2345   {
2346     case GAME_MODE_PSEUDO_TYPENAME:
2347     case GAME_MODE_PSEUDO_TYPENAMES:
2348       HandleTypeName(key);
2349       break;
2350
2351     case GAME_MODE_TITLE:
2352     case GAME_MODE_MAIN:
2353     case GAME_MODE_NAMES:
2354     case GAME_MODE_LEVELS:
2355     case GAME_MODE_LEVELNR:
2356     case GAME_MODE_SETUP:
2357     case GAME_MODE_INFO:
2358     case GAME_MODE_SCORES:
2359     case GAME_MODE_SCOREINFO:
2360
2361       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2362         break;
2363
2364       switch (key)
2365       {
2366         case KSYM_space:
2367         case KSYM_Return:
2368           if (game_status == GAME_MODE_TITLE)
2369             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2370           else if (game_status == GAME_MODE_MAIN)
2371             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2372           else if (game_status == GAME_MODE_NAMES)
2373             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2374           else if (game_status == GAME_MODE_LEVELS)
2375             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2376           else if (game_status == GAME_MODE_LEVELNR)
2377             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2378           else if (game_status == GAME_MODE_SETUP)
2379             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2380           else if (game_status == GAME_MODE_INFO)
2381             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2382           else if (game_status == GAME_MODE_SCORES)
2383             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2384           else if (game_status == GAME_MODE_SCOREINFO)
2385             HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2386           break;
2387
2388         case KSYM_Escape:
2389           if (game_status != GAME_MODE_MAIN)
2390             FadeSkipNextFadeIn();
2391
2392           if (game_status == GAME_MODE_TITLE)
2393             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2394           else if (game_status == GAME_MODE_NAMES)
2395             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2396           else if (game_status == GAME_MODE_LEVELS)
2397             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2398           else if (game_status == GAME_MODE_LEVELNR)
2399             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2400           else if (game_status == GAME_MODE_SETUP)
2401             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2402           else if (game_status == GAME_MODE_INFO)
2403             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2404           else if (game_status == GAME_MODE_SCORES)
2405             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2406           else if (game_status == GAME_MODE_SCOREINFO)
2407             HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2408           break;
2409
2410         case KSYM_Page_Up:
2411           if (game_status == GAME_MODE_NAMES)
2412             HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2413           else if (game_status == GAME_MODE_LEVELS)
2414             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2415           else if (game_status == GAME_MODE_LEVELNR)
2416             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2417           else if (game_status == GAME_MODE_SETUP)
2418             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2419           else if (game_status == GAME_MODE_INFO)
2420             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2421           else if (game_status == GAME_MODE_SCORES)
2422             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2423           else if (game_status == GAME_MODE_SCOREINFO)
2424             HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2425           break;
2426
2427         case KSYM_Page_Down:
2428           if (game_status == GAME_MODE_NAMES)
2429             HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2430           else if (game_status == GAME_MODE_LEVELS)
2431             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2432           else if (game_status == GAME_MODE_LEVELNR)
2433             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2434           else if (game_status == GAME_MODE_SETUP)
2435             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2436           else if (game_status == GAME_MODE_INFO)
2437             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2438           else if (game_status == GAME_MODE_SCORES)
2439             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2440           else if (game_status == GAME_MODE_SCOREINFO)
2441             HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2442           break;
2443
2444         default:
2445           break;
2446       }
2447       break;
2448
2449     case GAME_MODE_EDITOR:
2450       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2451         HandleLevelEditorKeyInput(key);
2452       break;
2453
2454     case GAME_MODE_PLAYING:
2455     {
2456       switch (key)
2457       {
2458         case KSYM_Escape:
2459           RequestQuitGame(TRUE);
2460           break;
2461
2462         default:
2463           break;
2464       }
2465       break;
2466     }
2467
2468     default:
2469       if (key == KSYM_Escape)
2470       {
2471         SetGameStatus(GAME_MODE_MAIN);
2472
2473         DrawMainMenu();
2474
2475         return;
2476       }
2477   }
2478 }
2479
2480 void HandleNoEvent(void)
2481 {
2482   HandleMouseCursor();
2483
2484   switch (game_status)
2485   {
2486     case GAME_MODE_PLAYING:
2487       HandleButtonOrFinger(-1, -1, -1);
2488       break;
2489   }
2490 }
2491
2492 void HandleEventActions(void)
2493 {
2494   // if (button_status && game_status != GAME_MODE_PLAYING)
2495   if (button_status && (game_status != GAME_MODE_PLAYING ||
2496                         tape.pausing ||
2497                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2498   {
2499     HandleButton(0, 0, button_status, -button_status);
2500   }
2501   else
2502   {
2503     HandleJoystick();
2504   }
2505
2506   if (network.enabled)
2507     HandleNetworking();
2508
2509   switch (game_status)
2510   {
2511     case GAME_MODE_MAIN:
2512       DrawPreviewLevelAnimation();
2513       break;
2514
2515     case GAME_MODE_EDITOR:
2516       HandleLevelEditorIdle();
2517       break;
2518
2519     default:
2520       break;
2521   }
2522 }
2523
2524 static void HandleTileCursor(int dx, int dy, int button)
2525 {
2526   if (!dx || !button)
2527     ClearPlayerMouseAction();
2528
2529   if (!dx && !dy)
2530     return;
2531
2532   if (button)
2533   {
2534     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2535                          (dx < 0 ? MB_LEFTBUTTON :
2536                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2537   }
2538   else if (!tile_cursor.moving)
2539   {
2540     int old_xpos = tile_cursor.xpos;
2541     int old_ypos = tile_cursor.ypos;
2542     int new_xpos = old_xpos;
2543     int new_ypos = old_ypos;
2544
2545     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2546       new_xpos = old_xpos + dx;
2547
2548     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2549       new_ypos = old_ypos + dy;
2550
2551     SetTileCursorTargetXY(new_xpos, new_ypos);
2552   }
2553 }
2554
2555 static int HandleJoystickForAllPlayers(void)
2556 {
2557   int i;
2558   int result = 0;
2559   boolean no_joysticks_configured = TRUE;
2560   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2561   static byte joy_action_last[MAX_PLAYERS];
2562
2563   for (i = 0; i < MAX_PLAYERS; i++)
2564     if (setup.input[i].use_joystick)
2565       no_joysticks_configured = FALSE;
2566
2567   // if no joysticks configured, map connected joysticks to players
2568   if (no_joysticks_configured)
2569     use_as_joystick_nr = TRUE;
2570
2571   for (i = 0; i < MAX_PLAYERS; i++)
2572   {
2573     byte joy_action = 0;
2574
2575     joy_action = JoystickExt(i, use_as_joystick_nr);
2576     result |= joy_action;
2577
2578     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2579         joy_action != joy_action_last[i])
2580       stored_player[i].action = joy_action;
2581
2582     joy_action_last[i] = joy_action;
2583   }
2584
2585   return result;
2586 }
2587
2588 void HandleJoystick(void)
2589 {
2590   static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2591   static int joytest_last = 0;
2592   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2593   int delay_value       = GADGET_FRAME_DELAY;
2594   int joystick  = HandleJoystickForAllPlayers();
2595   int keyboard  = key_joystick_mapping;
2596   int joy       = (joystick | keyboard);
2597   int joytest   = joystick;
2598   int left      = joy & JOY_LEFT;
2599   int right     = joy & JOY_RIGHT;
2600   int up        = joy & JOY_UP;
2601   int down      = joy & JOY_DOWN;
2602   int button    = joy & JOY_BUTTON;
2603   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2604   int dx        = (left ? -1    : right ? 1     : 0);
2605   int dy        = (up   ? -1    : down  ? 1     : 0);
2606   boolean use_delay_value_first = (joytest != joytest_last);
2607
2608   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2609   {
2610     // do not handle this button event anymore
2611     return;
2612   }
2613
2614   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2615                     game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2616                     anyTextGadgetActive()))
2617   {
2618     // leave name input in main menu or text input gadget
2619     HandleKey(KSYM_Escape, KEY_PRESSED);
2620     HandleKey(KSYM_Escape, KEY_RELEASED);
2621
2622     return;
2623   }
2624
2625   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2626   {
2627     if (game_status == GAME_MODE_PLAYING)
2628     {
2629       // when playing MM style levels, also use delay for keyboard events
2630       joytest |= keyboard;
2631
2632       // only use first delay value for new events, but not for changed events
2633       use_delay_value_first = (!joytest != !joytest_last);
2634
2635       // only use delay after the initial keyboard event
2636       delay_value = 0;
2637     }
2638
2639     // for any joystick or keyboard event, enable playfield tile cursor
2640     if (dx || dy || button)
2641       SetTileCursorEnabled(TRUE);
2642   }
2643
2644   // for any joystick event, enable playfield mouse cursor
2645   if (dx || dy || button)
2646     SetPlayfieldMouseCursorEnabled(TRUE);
2647
2648   if (joytest && !button && !DelayReached(&joytest_delay))
2649   {
2650     // delay joystick/keyboard actions if axes/keys continually pressed
2651     newbutton = dx = dy = 0;
2652   }
2653   else
2654   {
2655     // first start with longer delay, then continue with shorter delay
2656     joytest_delay.value =
2657       (use_delay_value_first ? delay_value_first : delay_value);
2658   }
2659
2660   joytest_last = joytest;
2661
2662   switch (game_status)
2663   {
2664     case GAME_MODE_TITLE:
2665     case GAME_MODE_MAIN:
2666     case GAME_MODE_NAMES:
2667     case GAME_MODE_LEVELS:
2668     case GAME_MODE_LEVELNR:
2669     case GAME_MODE_SETUP:
2670     case GAME_MODE_INFO:
2671     case GAME_MODE_SCORES:
2672     case GAME_MODE_SCOREINFO:
2673     {
2674       if (anyTextGadgetActive())
2675         break;
2676
2677       if (game_status == GAME_MODE_TITLE)
2678         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2679       else if (game_status == GAME_MODE_MAIN)
2680         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2681       else if (game_status == GAME_MODE_NAMES)
2682         HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2683       else if (game_status == GAME_MODE_LEVELS)
2684         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2685       else if (game_status == GAME_MODE_LEVELNR)
2686         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2687       else if (game_status == GAME_MODE_SETUP)
2688         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2689       else if (game_status == GAME_MODE_INFO)
2690         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2691       else if (game_status == GAME_MODE_SCORES)
2692         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2693       else if (game_status == GAME_MODE_SCOREINFO)
2694         HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2695
2696       break;
2697     }
2698
2699     case GAME_MODE_PLAYING:
2700 #if 0
2701       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2702       if (tape.playing || keyboard)
2703         newbutton = ((joy & JOY_BUTTON) != 0);
2704 #endif
2705
2706       if (newbutton && game.all_players_gone)
2707       {
2708         GameEnd();
2709
2710         return;
2711       }
2712
2713       if (tape.recording && tape.pausing && tape.use_key_actions)
2714       {
2715         if (tape.single_step)
2716         {
2717           if (joystick & JOY_ACTION)
2718             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2719         }
2720         else
2721         {
2722           if (joystick & JOY_ACTION)
2723             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2724         }
2725       }
2726
2727       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2728         HandleTileCursor(dx, dy, button);
2729
2730       break;
2731
2732     default:
2733       break;
2734   }
2735 }
2736
2737 void HandleSpecialGameControllerButtons(Event *event)
2738 {
2739   int key_status;
2740   Key key;
2741
2742   switch (event->type)
2743   {
2744     case SDL_CONTROLLERBUTTONDOWN:
2745       key_status = KEY_PRESSED;
2746       break;
2747
2748     case SDL_CONTROLLERBUTTONUP:
2749       key_status = KEY_RELEASED;
2750       break;
2751
2752     default:
2753       return;
2754   }
2755
2756   switch (event->cbutton.button)
2757   {
2758     case SDL_CONTROLLER_BUTTON_START:
2759       key = KSYM_space;
2760       break;
2761
2762     case SDL_CONTROLLER_BUTTON_BACK:
2763       key = KSYM_Escape;
2764       break;
2765
2766     default:
2767       return;
2768   }
2769
2770   HandleKey(key, key_status);
2771 }
2772
2773 void HandleSpecialGameControllerKeys(Key key, int key_status)
2774 {
2775 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2776   int button = SDL_CONTROLLER_BUTTON_INVALID;
2777
2778   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2779   if (key == KSYM_Rewind)
2780     button = SDL_CONTROLLER_BUTTON_A;
2781   else if (key == KSYM_FastForward || key == KSYM_Menu)
2782     button = SDL_CONTROLLER_BUTTON_B;
2783
2784   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2785   {
2786     Event event;
2787
2788     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2789                   SDL_CONTROLLERBUTTONUP);
2790
2791     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2792     event.cbutton.button = button;
2793     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2794                            SDL_RELEASED);
2795
2796     HandleJoystickEvent(&event);
2797   }
2798 #endif
2799 }
2800
2801 boolean DoKeysymAction(int keysym)
2802 {
2803   if (keysym < 0)
2804   {
2805     Key key = (Key)(-keysym);
2806
2807     HandleKey(key, KEY_PRESSED);
2808     HandleKey(key, KEY_RELEASED);
2809
2810     return TRUE;
2811   }
2812
2813   return FALSE;
2814 }