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