11fd28f40035daab17fc49caf6e05a0995b8262c
[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   // some key events are handled like clicks for global animations
2149   boolean click = (key == KSYM_space ||
2150                    key == KSYM_Return ||
2151                    key == KSYM_Escape);
2152
2153   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2154   {
2155     // do not handle this key event anymore
2156     if (key != KSYM_Escape)     // always allow ESC key to be handled
2157       return;
2158   }
2159
2160   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2161       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2162   {
2163     GameEnd();
2164
2165     return;
2166   }
2167
2168   if (game_status == GAME_MODE_MAIN &&
2169       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2170   {
2171     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2172
2173     return;
2174   }
2175
2176   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2177   {
2178     if (key == setup.shortcut.save_game)
2179       TapeQuickSave();
2180     else if (key == setup.shortcut.load_game)
2181       TapeQuickLoad();
2182     else if (key == setup.shortcut.toggle_pause)
2183       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2184
2185     HandleTapeButtonKeys(key);
2186     HandleSoundButtonKeys(key);
2187   }
2188
2189   if (game_status == GAME_MODE_PLAYING && !network_playing)
2190   {
2191     int centered_player_nr_next = -999;
2192
2193     if (key == setup.shortcut.focus_player_all)
2194       centered_player_nr_next = -1;
2195     else
2196       for (i = 0; i < MAX_PLAYERS; i++)
2197         if (key == setup.shortcut.focus_player[i])
2198           centered_player_nr_next = i;
2199
2200     if (centered_player_nr_next != -999)
2201     {
2202       game.centered_player_nr_next = centered_player_nr_next;
2203       game.set_centered_player = TRUE;
2204
2205       if (tape.recording)
2206       {
2207         tape.centered_player_nr_next = game.centered_player_nr_next;
2208         tape.set_centered_player = TRUE;
2209       }
2210     }
2211   }
2212
2213   HandleKeysSpecial(key);
2214
2215   if (HandleGadgetsKeyInput(key))
2216     return;             // do not handle already processed keys again
2217
2218   switch (game_status)
2219   {
2220     case GAME_MODE_PSEUDO_TYPENAME:
2221       HandleTypeName(0, key);
2222       break;
2223
2224     case GAME_MODE_TITLE:
2225     case GAME_MODE_MAIN:
2226     case GAME_MODE_LEVELS:
2227     case GAME_MODE_LEVELNR:
2228     case GAME_MODE_SETUP:
2229     case GAME_MODE_INFO:
2230     case GAME_MODE_SCORES:
2231
2232       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2233         break;
2234
2235       switch (key)
2236       {
2237         case KSYM_space:
2238         case KSYM_Return:
2239           if (game_status == GAME_MODE_TITLE)
2240             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2241           else if (game_status == GAME_MODE_MAIN)
2242             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2243           else if (game_status == GAME_MODE_LEVELS)
2244             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2245           else if (game_status == GAME_MODE_LEVELNR)
2246             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2247           else if (game_status == GAME_MODE_SETUP)
2248             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2249           else if (game_status == GAME_MODE_INFO)
2250             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2251           else if (game_status == GAME_MODE_SCORES)
2252             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2253           break;
2254
2255         case KSYM_Escape:
2256           if (game_status != GAME_MODE_MAIN)
2257             FadeSkipNextFadeIn();
2258
2259           if (game_status == GAME_MODE_TITLE)
2260             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2261           else if (game_status == GAME_MODE_LEVELS)
2262             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2263           else if (game_status == GAME_MODE_LEVELNR)
2264             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2265           else if (game_status == GAME_MODE_SETUP)
2266             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2267           else if (game_status == GAME_MODE_INFO)
2268             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2269           else if (game_status == GAME_MODE_SCORES)
2270             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2271           break;
2272
2273         case KSYM_Page_Up:
2274           if (game_status == GAME_MODE_LEVELS)
2275             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2276           else if (game_status == GAME_MODE_LEVELNR)
2277             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2278           else if (game_status == GAME_MODE_SETUP)
2279             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2280           else if (game_status == GAME_MODE_INFO)
2281             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2282           else if (game_status == GAME_MODE_SCORES)
2283             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2284           break;
2285
2286         case KSYM_Page_Down:
2287           if (game_status == GAME_MODE_LEVELS)
2288             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2289           else if (game_status == GAME_MODE_LEVELNR)
2290             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2291           else if (game_status == GAME_MODE_SETUP)
2292             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2293           else if (game_status == GAME_MODE_INFO)
2294             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2295           else if (game_status == GAME_MODE_SCORES)
2296             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2297           break;
2298
2299         default:
2300           break;
2301       }
2302       break;
2303
2304     case GAME_MODE_EDITOR:
2305       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2306         HandleLevelEditorKeyInput(key);
2307       break;
2308
2309     case GAME_MODE_PLAYING:
2310     {
2311       switch (key)
2312       {
2313         case KSYM_Escape:
2314           RequestQuitGame(setup.ask_on_escape);
2315           break;
2316
2317         default:
2318           break;
2319       }
2320       break;
2321     }
2322
2323     default:
2324       if (key == KSYM_Escape)
2325       {
2326         SetGameStatus(GAME_MODE_MAIN);
2327
2328         DrawMainMenu();
2329
2330         return;
2331       }
2332   }
2333 }
2334
2335 void HandleNoEvent(void)
2336 {
2337   HandleMouseCursor();
2338
2339   switch (game_status)
2340   {
2341     case GAME_MODE_PLAYING:
2342       HandleButtonOrFinger(-1, -1, -1);
2343       break;
2344   }
2345 }
2346
2347 void HandleEventActions(void)
2348 {
2349   // if (button_status && game_status != GAME_MODE_PLAYING)
2350   if (button_status && (game_status != GAME_MODE_PLAYING ||
2351                         tape.pausing ||
2352                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2353   {
2354     HandleButton(0, 0, button_status, -button_status);
2355   }
2356   else
2357   {
2358     HandleJoystick();
2359   }
2360
2361   if (network.enabled)
2362     HandleNetworking();
2363
2364   switch (game_status)
2365   {
2366     case GAME_MODE_MAIN:
2367       DrawPreviewLevelAnimation();
2368       break;
2369
2370     case GAME_MODE_EDITOR:
2371       HandleLevelEditorIdle();
2372       break;
2373
2374     default:
2375       break;
2376   }
2377 }
2378
2379 static void HandleTileCursor(int dx, int dy, int button)
2380 {
2381   if (!dx || !button)
2382     ClearPlayerMouseAction();
2383
2384   if (!dx && !dy)
2385     return;
2386
2387   if (button)
2388   {
2389     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2390                          (dx < 0 ? MB_LEFTBUTTON :
2391                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2392   }
2393   else if (!tile_cursor.moving)
2394   {
2395     int old_xpos = tile_cursor.xpos;
2396     int old_ypos = tile_cursor.ypos;
2397     int new_xpos = old_xpos;
2398     int new_ypos = old_ypos;
2399
2400     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2401       new_xpos = old_xpos + dx;
2402
2403     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2404       new_ypos = old_ypos + dy;
2405
2406     SetTileCursorTargetXY(new_xpos, new_ypos);
2407   }
2408 }
2409
2410 static int HandleJoystickForAllPlayers(void)
2411 {
2412   int i;
2413   int result = 0;
2414   boolean no_joysticks_configured = TRUE;
2415   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2416   static byte joy_action_last[MAX_PLAYERS];
2417
2418   for (i = 0; i < MAX_PLAYERS; i++)
2419     if (setup.input[i].use_joystick)
2420       no_joysticks_configured = FALSE;
2421
2422   // if no joysticks configured, map connected joysticks to players
2423   if (no_joysticks_configured)
2424     use_as_joystick_nr = TRUE;
2425
2426   for (i = 0; i < MAX_PLAYERS; i++)
2427   {
2428     byte joy_action = 0;
2429
2430     joy_action = JoystickExt(i, use_as_joystick_nr);
2431     result |= joy_action;
2432
2433     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2434         joy_action != joy_action_last[i])
2435       stored_player[i].action = joy_action;
2436
2437     joy_action_last[i] = joy_action;
2438   }
2439
2440   return result;
2441 }
2442
2443 void HandleJoystick(void)
2444 {
2445   static unsigned int joytest_delay = 0;
2446   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2447   static int joytest_last = 0;
2448   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2449   int delay_value       = GADGET_FRAME_DELAY;
2450   int joystick  = HandleJoystickForAllPlayers();
2451   int keyboard  = key_joystick_mapping;
2452   int joy       = (joystick | keyboard);
2453   int joytest   = joystick;
2454   int left      = joy & JOY_LEFT;
2455   int right     = joy & JOY_RIGHT;
2456   int up        = joy & JOY_UP;
2457   int down      = joy & JOY_DOWN;
2458   int button    = joy & JOY_BUTTON;
2459   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2460   int dx        = (left ? -1    : right ? 1     : 0);
2461   int dy        = (up   ? -1    : down  ? 1     : 0);
2462   boolean use_delay_value_first = (joytest != joytest_last);
2463
2464   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2465   {
2466     // do not handle this button event anymore
2467     return;
2468   }
2469
2470   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2471                     anyTextGadgetActive()))
2472   {
2473     // leave name input in main menu or text input gadget
2474     HandleKey(KSYM_Escape, KEY_PRESSED);
2475     HandleKey(KSYM_Escape, KEY_RELEASED);
2476
2477     return;
2478   }
2479
2480   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2481   {
2482     if (game_status == GAME_MODE_PLAYING)
2483     {
2484       // when playing MM style levels, also use delay for keyboard events
2485       joytest |= keyboard;
2486
2487       // only use first delay value for new events, but not for changed events
2488       use_delay_value_first = (!joytest != !joytest_last);
2489
2490       // only use delay after the initial keyboard event
2491       delay_value = 0;
2492     }
2493
2494     // for any joystick or keyboard event, enable playfield tile cursor
2495     if (dx || dy || button)
2496       SetTileCursorEnabled(TRUE);
2497   }
2498
2499   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2500   {
2501     // delay joystick/keyboard actions if axes/keys continually pressed
2502     newbutton = dx = dy = 0;
2503   }
2504   else
2505   {
2506     // first start with longer delay, then continue with shorter delay
2507     joytest_delay_value =
2508       (use_delay_value_first ? delay_value_first : delay_value);
2509   }
2510
2511   joytest_last = joytest;
2512
2513   switch (game_status)
2514   {
2515     case GAME_MODE_TITLE:
2516     case GAME_MODE_MAIN:
2517     case GAME_MODE_LEVELS:
2518     case GAME_MODE_LEVELNR:
2519     case GAME_MODE_SETUP:
2520     case GAME_MODE_INFO:
2521     case GAME_MODE_SCORES:
2522     {
2523       if (anyTextGadgetActive())
2524         break;
2525
2526       if (game_status == GAME_MODE_TITLE)
2527         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2528       else if (game_status == GAME_MODE_MAIN)
2529         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2530       else if (game_status == GAME_MODE_LEVELS)
2531         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2532       else if (game_status == GAME_MODE_LEVELNR)
2533         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2534       else if (game_status == GAME_MODE_SETUP)
2535         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2536       else if (game_status == GAME_MODE_INFO)
2537         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2538       else if (game_status == GAME_MODE_SCORES)
2539         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2540
2541       break;
2542     }
2543
2544     case GAME_MODE_PLAYING:
2545 #if 0
2546       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2547       if (tape.playing || keyboard)
2548         newbutton = ((joy & JOY_BUTTON) != 0);
2549 #endif
2550
2551       if (newbutton && game.all_players_gone)
2552       {
2553         GameEnd();
2554
2555         return;
2556       }
2557
2558       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2559       {
2560         if (joystick & JOY_ACTION)
2561           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2562       }
2563       else if (tape.recording && tape.pausing && !tape.use_mouse)
2564       {
2565         if (joystick & JOY_ACTION)
2566           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2567       }
2568
2569       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2570         HandleTileCursor(dx, dy, button);
2571
2572       break;
2573
2574     default:
2575       break;
2576   }
2577 }
2578
2579 void HandleSpecialGameControllerButtons(Event *event)
2580 {
2581   int key_status;
2582   Key key;
2583
2584   switch (event->type)
2585   {
2586     case SDL_CONTROLLERBUTTONDOWN:
2587       key_status = KEY_PRESSED;
2588       break;
2589
2590     case SDL_CONTROLLERBUTTONUP:
2591       key_status = KEY_RELEASED;
2592       break;
2593
2594     default:
2595       return;
2596   }
2597
2598   switch (event->cbutton.button)
2599   {
2600     case SDL_CONTROLLER_BUTTON_START:
2601       key = KSYM_space;
2602       break;
2603
2604     case SDL_CONTROLLER_BUTTON_BACK:
2605       key = KSYM_Escape;
2606       break;
2607
2608     default:
2609       return;
2610   }
2611
2612   HandleKey(key, key_status);
2613 }
2614
2615 void HandleSpecialGameControllerKeys(Key key, int key_status)
2616 {
2617 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2618   int button = SDL_CONTROLLER_BUTTON_INVALID;
2619
2620   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2621   if (key == KSYM_Rewind)
2622     button = SDL_CONTROLLER_BUTTON_A;
2623   else if (key == KSYM_FastForward || key == KSYM_Menu)
2624     button = SDL_CONTROLLER_BUTTON_B;
2625
2626   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2627   {
2628     Event event;
2629
2630     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2631                   SDL_CONTROLLERBUTTONUP);
2632
2633     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2634     event.cbutton.button = button;
2635     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2636                            SDL_RELEASED);
2637
2638     HandleJoystickEvent(&event);
2639   }
2640 #endif
2641 }
2642
2643 boolean DoKeysymAction(int keysym)
2644 {
2645   if (keysym < 0)
2646   {
2647     Key key = (Key)(-keysym);
2648
2649     HandleKey(key, KEY_PRESSED);
2650     HandleKey(key, KEY_RELEASED);
2651
2652     return TRUE;
2653   }
2654
2655   return FALSE;
2656 }