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