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