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