0d1f6baf371a34982db7915ebd947e531bc96ca7
[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   TreeInfo *tree_node = NULL;
1541   int tree_type = GetZipFileTreeType(filename);
1542   char *directory = TREE_USERDIR(tree_type);
1543
1544   if (directory == NULL)
1545   {
1546     Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1547
1548     return FALSE;
1549   }
1550
1551   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1552       game_status == GAME_MODE_LEVELS &&
1553       leveldir_current->node_parent != NULL)
1554   {
1555     // extract new level set next to currently selected level set
1556     tree_node = leveldir_current;
1557
1558     // get parent directory of currently selected level set directory
1559     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1560
1561     // use private level directory instead of top-level package level directory
1562     if (strPrefix(directory, options.level_directory) &&
1563         strEqual(leveldir_current->node_parent->fullpath, "."))
1564       directory = getUserLevelDir(NULL);
1565   }
1566
1567   // extract level or artwork set from zip file to target directory
1568   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1569
1570   if (top_dir == NULL)
1571   {
1572     // error message already issued by "ExtractZipFileIntoDirectory()"
1573
1574     return FALSE;
1575   }
1576
1577   // add extracted level or artwork set to tree info structure
1578   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1579
1580   // update menu screen (and possibly change current level set)
1581   DrawScreenAfterAddingSet(top_dir, tree_type);
1582
1583   return TRUE;
1584 }
1585
1586 static void HandleDropTextEvent(char *text)
1587 {
1588   Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1589 }
1590
1591 void HandleDropEvent(Event *event)
1592 {
1593   static int files_succeeded = 0;
1594   static int files_failed = 0;
1595
1596   switch (event->type)
1597   {
1598     case SDL_DROPBEGIN:
1599     {
1600       files_succeeded = 0;
1601       files_failed = 0;
1602
1603       break;
1604     }
1605
1606     case SDL_DROPFILE:
1607     {
1608       boolean success = HandleDropFileEvent(event->drop.file);
1609
1610       if (success)
1611         files_succeeded++;
1612       else
1613         files_failed++;
1614
1615       break;
1616     }
1617
1618     case SDL_DROPTEXT:
1619     {
1620       HandleDropTextEvent(event->drop.file);
1621
1622       break;
1623     }
1624
1625     case SDL_DROPCOMPLETE:
1626     {
1627       // only show request dialog if no other request dialog already active
1628       if (!game.request_active)
1629       {
1630         if (files_succeeded > 0 && files_failed > 0)
1631           Request("New level or artwork set(s) added, "
1632                   "but some dropped file(s) failed!", REQ_CONFIRM);
1633         else if (files_succeeded > 0)
1634           Request("New level or artwork set(s) added!", REQ_CONFIRM);
1635         else if (files_failed > 0)
1636           Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1637       }
1638
1639       break;
1640     }
1641   }
1642
1643   if (event->drop.file != NULL)
1644     SDL_free(event->drop.file);
1645 }
1646
1647 void HandleButton(int mx, int my, int button, int button_nr)
1648 {
1649   static int old_mx = 0, old_my = 0;
1650   boolean button_hold = FALSE;
1651   boolean handle_gadgets = TRUE;
1652
1653   if (button_nr < 0)
1654   {
1655     mx = old_mx;
1656     my = old_my;
1657     button_nr = -button_nr;
1658     button_hold = TRUE;
1659   }
1660   else
1661   {
1662     old_mx = mx;
1663     old_my = my;
1664   }
1665
1666 #if defined(PLATFORM_ANDROID)
1667   // when playing, only handle gadgets when using "follow finger" controls
1668   // or when using touch controls in combination with the MM game engine
1669   // or when using gadgets that do not overlap with virtual buttons
1670   handle_gadgets =
1671     (game_status != GAME_MODE_PLAYING ||
1672      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1673      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1674      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1675       !virtual_button_pressed));
1676 #endif
1677
1678   if (HandleGlobalAnimClicks(mx, my, button))
1679   {
1680     // do not handle this button event anymore
1681     return;             // force mouse event not to be handled at all
1682   }
1683
1684   if (handle_gadgets && HandleGadgets(mx, my, button))
1685   {
1686     // do not handle this button event anymore
1687     mx = my = -32;      // force mouse event to be outside screen tiles
1688   }
1689
1690   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1691     return;
1692
1693   // do not use scroll wheel button events for anything other than gadgets
1694   if (IS_WHEEL_BUTTON(button_nr))
1695     return;
1696
1697   switch (game_status)
1698   {
1699     case GAME_MODE_TITLE:
1700       HandleTitleScreen(mx, my, 0, 0, button);
1701       break;
1702
1703     case GAME_MODE_MAIN:
1704       HandleMainMenu(mx, my, 0, 0, button);
1705       break;
1706
1707     case GAME_MODE_PSEUDO_TYPENAME:
1708       HandleTypeName(0, KSYM_Return);
1709       break;
1710
1711     case GAME_MODE_LEVELS:
1712       HandleChooseLevelSet(mx, my, 0, 0, button);
1713       break;
1714
1715     case GAME_MODE_LEVELNR:
1716       HandleChooseLevelNr(mx, my, 0, 0, button);
1717       break;
1718
1719     case GAME_MODE_SCORES:
1720       HandleHallOfFame(0, 0, 0, 0, button);
1721       break;
1722
1723     case GAME_MODE_EDITOR:
1724       HandleLevelEditorIdle();
1725       break;
1726
1727     case GAME_MODE_INFO:
1728       HandleInfoScreen(mx, my, 0, 0, button);
1729       break;
1730
1731     case GAME_MODE_SETUP:
1732       HandleSetupScreen(mx, my, 0, 0, button);
1733       break;
1734
1735     case GAME_MODE_PLAYING:
1736       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1737         HandleButtonOrFinger(mx, my, button);
1738       else
1739         SetPlayerMouseAction(mx, my, button);
1740
1741 #ifdef DEBUG
1742       if (button == MB_PRESSED && !motion_status && !button_hold &&
1743           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1744         DumpTileFromScreen(mx, my);
1745 #endif
1746
1747       break;
1748
1749     default:
1750       break;
1751   }
1752 }
1753
1754 static boolean is_string_suffix(char *string, char *suffix)
1755 {
1756   int string_len = strlen(string);
1757   int suffix_len = strlen(suffix);
1758
1759   if (suffix_len > string_len)
1760     return FALSE;
1761
1762   return (strEqual(&string[string_len - suffix_len], suffix));
1763 }
1764
1765 #define MAX_CHEAT_INPUT_LEN     32
1766
1767 static void HandleKeysSpecial(Key key)
1768 {
1769   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1770   char letter = getCharFromKey(key);
1771   int cheat_input_len = strlen(cheat_input);
1772   int i;
1773
1774   if (letter == 0)
1775     return;
1776
1777   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1778   {
1779     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1780       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1781
1782     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1783   }
1784
1785   cheat_input[cheat_input_len++] = letter;
1786   cheat_input[cheat_input_len] = '\0';
1787
1788 #if DEBUG_EVENTS_KEY
1789   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1790 #endif
1791
1792   if (game_status == GAME_MODE_MAIN)
1793   {
1794     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1795         is_string_suffix(cheat_input, ":ist"))
1796     {
1797       InsertSolutionTape();
1798     }
1799     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1800              is_string_suffix(cheat_input, ":pst"))
1801     {
1802       PlaySolutionTape();
1803     }
1804     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1805              is_string_suffix(cheat_input, ":rg"))
1806     {
1807       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1808       DrawMainMenu();
1809     }
1810     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1811              is_string_suffix(cheat_input, ":rs"))
1812     {
1813       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1814       DrawMainMenu();
1815     }
1816     else if (is_string_suffix(cheat_input, ":reload-music") ||
1817              is_string_suffix(cheat_input, ":rm"))
1818     {
1819       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1820       DrawMainMenu();
1821     }
1822     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1823              is_string_suffix(cheat_input, ":ra"))
1824     {
1825       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1826                           1 << ARTWORK_TYPE_SOUNDS |
1827                           1 << ARTWORK_TYPE_MUSIC);
1828       DrawMainMenu();
1829     }
1830     else if (is_string_suffix(cheat_input, ":dump-level") ||
1831              is_string_suffix(cheat_input, ":dl"))
1832     {
1833       DumpLevel(&level);
1834     }
1835     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1836              is_string_suffix(cheat_input, ":dt"))
1837     {
1838       DumpTape(&tape);
1839     }
1840     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1841              is_string_suffix(cheat_input, ":ft"))
1842     {
1843       /* fix single-player tapes that contain player input for more than one
1844          player (due to a bug in 3.3.1.2 and earlier versions), which results
1845          in playing levels with more than one player in multi-player mode,
1846          even though the tape was originally recorded in single-player mode */
1847
1848       // remove player input actions for all players but the first one
1849       for (i = 1; i < MAX_PLAYERS; i++)
1850         tape.player_participates[i] = FALSE;
1851
1852       tape.changed = TRUE;
1853     }
1854     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1855              is_string_suffix(cheat_input, ":snl"))
1856     {
1857       SaveNativeLevel(&level);
1858     }
1859     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1860              is_string_suffix(cheat_input, ":fps"))
1861     {
1862       global.show_frames_per_second = !global.show_frames_per_second;
1863     }
1864   }
1865   else if (game_status == GAME_MODE_PLAYING)
1866   {
1867 #ifdef DEBUG
1868     if (is_string_suffix(cheat_input, ".q"))
1869       DEBUG_SetMaximumDynamite();
1870 #endif
1871   }
1872   else if (game_status == GAME_MODE_EDITOR)
1873   {
1874     if (is_string_suffix(cheat_input, ":dump-brush") ||
1875         is_string_suffix(cheat_input, ":DB"))
1876     {
1877       DumpBrush();
1878     }
1879     else if (is_string_suffix(cheat_input, ":DDB"))
1880     {
1881       DumpBrush_Small();
1882     }
1883
1884     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1885     {
1886       if (letter == 'x')        // copy brush to clipboard (small size)
1887       {
1888         CopyBrushToClipboard_Small();
1889       }
1890       else if (letter == 'c')   // copy brush to clipboard (normal size)
1891       {
1892         CopyBrushToClipboard();
1893       }
1894       else if (letter == 'v')   // paste brush from Clipboard
1895       {
1896         CopyClipboardToBrush();
1897       }
1898     }
1899   }
1900
1901   // special key shortcuts for all game modes
1902   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1903       is_string_suffix(cheat_input, ":dea") ||
1904       is_string_suffix(cheat_input, ":DEA"))
1905   {
1906     DumpGadgetIdentifiers();
1907     DumpScreenIdentifiers();
1908   }
1909 }
1910
1911 boolean HandleKeysDebug(Key key, int key_status)
1912 {
1913 #ifdef DEBUG
1914   int i;
1915
1916   if (key_status != KEY_PRESSED)
1917     return FALSE;
1918
1919   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1920   {
1921     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1922
1923     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1924     {
1925       if (key == setup.debug.frame_delay_key[i] &&
1926           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1927       {
1928         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1929                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1930
1931         if (!setup.debug.frame_delay_game_only)
1932           MenuFrameDelay = GameFrameDelay;
1933
1934         SetVideoFrameDelay(GameFrameDelay);
1935
1936         if (GameFrameDelay > ONE_SECOND_DELAY)
1937           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1938         else if (GameFrameDelay != 0)
1939           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1940                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1941                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1942         else
1943           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1944
1945         return TRUE;
1946       }
1947     }
1948   }
1949
1950   if (game_status == GAME_MODE_PLAYING)
1951   {
1952     if (key == KSYM_d)
1953     {
1954       options.debug = !options.debug;
1955
1956       Error(ERR_DEBUG, "debug mode %s",
1957             (options.debug ? "enabled" : "disabled"));
1958
1959       return TRUE;
1960     }
1961     else if (key == KSYM_v)
1962     {
1963       Error(ERR_DEBUG, "currently using game engine version %d",
1964             game.engine_version);
1965
1966       return TRUE;
1967     }
1968   }
1969 #endif
1970
1971   return FALSE;
1972 }
1973
1974 void HandleKey(Key key, int key_status)
1975 {
1976   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1977   static boolean ignore_repeated_key = FALSE;
1978   static struct SetupKeyboardInfo ski;
1979   static struct SetupShortcutInfo ssi;
1980   static struct
1981   {
1982     Key *key_custom;
1983     Key *key_snap;
1984     Key key_default;
1985     byte action;
1986   } key_info[] =
1987   {
1988     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1989     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1990     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1991     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1992     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1993     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1994   };
1995   int joy = 0;
1996   int i;
1997
1998   if (HandleKeysDebug(key, key_status))
1999     return;             // do not handle already processed keys again
2000
2001   // map special keys (media keys / remote control buttons) to default keys
2002   if (key == KSYM_PlayPause)
2003     key = KSYM_space;
2004   else if (key == KSYM_Select)
2005     key = KSYM_Return;
2006
2007   HandleSpecialGameControllerKeys(key, key_status);
2008
2009   if (game_status == GAME_MODE_PLAYING)
2010   {
2011     // only needed for single-step tape recording mode
2012     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2013     int pnr;
2014
2015     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2016     {
2017       byte key_action = 0;
2018       byte key_snap_action = 0;
2019
2020       if (setup.input[pnr].use_joystick)
2021         continue;
2022
2023       ski = setup.input[pnr].key;
2024
2025       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2026         if (key == *key_info[i].key_custom)
2027           key_action |= key_info[i].action;
2028
2029       // use combined snap+direction keys for the first player only
2030       if (pnr == 0)
2031       {
2032         ssi = setup.shortcut;
2033
2034         // also remember normal snap key when handling snap+direction keys
2035         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2036
2037         for (i = 0; i < NUM_DIRECTIONS; i++)
2038         {
2039           if (key == *key_info[i].key_snap)
2040           {
2041             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2042             key_snap_action |= key_info[i].action;
2043           }
2044         }
2045       }
2046
2047       if (key_status == KEY_PRESSED)
2048       {
2049         stored_player[pnr].action      |= key_action;
2050         stored_player[pnr].snap_action |= key_snap_action;
2051       }
2052       else
2053       {
2054         stored_player[pnr].action      &= ~key_action;
2055         stored_player[pnr].snap_action &= ~key_snap_action;
2056       }
2057
2058       // restore snap action if one of several pressed snap keys was released
2059       if (stored_player[pnr].snap_action)
2060         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2061
2062       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2063       {
2064         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2065         {
2066           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2067
2068           // if snap key already pressed, keep pause mode when releasing
2069           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2070             has_snapped[pnr] = TRUE;
2071         }
2072         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2073         {
2074           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2075
2076           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2077               getRedDiskReleaseFlag_SP() == 0)
2078           {
2079             // add a single inactive frame before dropping starts
2080             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2081             stored_player[pnr].force_dropping = TRUE;
2082           }
2083         }
2084         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2085         {
2086           // if snap key was pressed without direction, leave pause mode
2087           if (!has_snapped[pnr])
2088             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2089
2090           has_snapped[pnr] = FALSE;
2091         }
2092       }
2093       else if (tape.recording && tape.pausing && !tape.use_mouse)
2094       {
2095         // prevent key release events from un-pausing a paused game
2096         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2097           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2098       }
2099
2100       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2101       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2102         joy |= key_action;
2103     }
2104   }
2105   else
2106   {
2107     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2108       if (key == key_info[i].key_default)
2109         joy |= key_info[i].action;
2110   }
2111
2112   if (joy)
2113   {
2114     if (key_status == KEY_PRESSED)
2115       key_joystick_mapping |= joy;
2116     else
2117       key_joystick_mapping &= ~joy;
2118
2119     HandleJoystick();
2120   }
2121
2122   if (game_status != GAME_MODE_PLAYING)
2123     key_joystick_mapping = 0;
2124
2125   if (key_status == KEY_RELEASED)
2126   {
2127     // reset flag to ignore repeated "key pressed" events after key release
2128     ignore_repeated_key = FALSE;
2129
2130     return;
2131   }
2132
2133   if ((key == KSYM_F11 ||
2134        ((key == KSYM_Return ||
2135          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2136       video.fullscreen_available &&
2137       !ignore_repeated_key)
2138   {
2139     setup.fullscreen = !setup.fullscreen;
2140
2141     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2142
2143     if (game_status == GAME_MODE_SETUP)
2144       RedrawSetupScreenAfterFullscreenToggle();
2145
2146     // set flag to ignore repeated "key pressed" events
2147     ignore_repeated_key = TRUE;
2148
2149     return;
2150   }
2151
2152   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2153        key == KSYM_minus || key == KSYM_KP_Subtract ||
2154        key == KSYM_plus  || key == KSYM_KP_Add ||
2155        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2156       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2157       video.window_scaling_available &&
2158       !video.fullscreen_enabled)
2159   {
2160     if (key == KSYM_0 || key == KSYM_KP_0)
2161       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2162     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2163       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2164     else
2165       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2166
2167     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2168       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2169     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2170       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2171
2172     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2173
2174     if (game_status == GAME_MODE_SETUP)
2175       RedrawSetupScreenAfterFullscreenToggle();
2176
2177     return;
2178   }
2179
2180   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2181                                       key == KSYM_Return ||
2182                                       key == KSYM_Escape)))
2183   {
2184     // do not handle this key event anymore
2185     if (key != KSYM_Escape)     // always allow ESC key to be handled
2186       return;
2187   }
2188
2189   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2190       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2191   {
2192     GameEnd();
2193
2194     return;
2195   }
2196
2197   if (game_status == GAME_MODE_MAIN &&
2198       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2199   {
2200     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2201
2202     return;
2203   }
2204
2205   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2206   {
2207     if (key == setup.shortcut.save_game)
2208       TapeQuickSave();
2209     else if (key == setup.shortcut.load_game)
2210       TapeQuickLoad();
2211     else if (key == setup.shortcut.toggle_pause)
2212       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2213
2214     HandleTapeButtonKeys(key);
2215     HandleSoundButtonKeys(key);
2216   }
2217
2218   if (game_status == GAME_MODE_PLAYING && !network_playing)
2219   {
2220     int centered_player_nr_next = -999;
2221
2222     if (key == setup.shortcut.focus_player_all)
2223       centered_player_nr_next = -1;
2224     else
2225       for (i = 0; i < MAX_PLAYERS; i++)
2226         if (key == setup.shortcut.focus_player[i])
2227           centered_player_nr_next = i;
2228
2229     if (centered_player_nr_next != -999)
2230     {
2231       game.centered_player_nr_next = centered_player_nr_next;
2232       game.set_centered_player = TRUE;
2233
2234       if (tape.recording)
2235       {
2236         tape.centered_player_nr_next = game.centered_player_nr_next;
2237         tape.set_centered_player = TRUE;
2238       }
2239     }
2240   }
2241
2242   HandleKeysSpecial(key);
2243
2244   if (HandleGadgetsKeyInput(key))
2245     return;             // do not handle already processed keys again
2246
2247   switch (game_status)
2248   {
2249     case GAME_MODE_PSEUDO_TYPENAME:
2250       HandleTypeName(0, key);
2251       break;
2252
2253     case GAME_MODE_TITLE:
2254     case GAME_MODE_MAIN:
2255     case GAME_MODE_LEVELS:
2256     case GAME_MODE_LEVELNR:
2257     case GAME_MODE_SETUP:
2258     case GAME_MODE_INFO:
2259     case GAME_MODE_SCORES:
2260
2261       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2262         break;
2263
2264       switch (key)
2265       {
2266         case KSYM_space:
2267         case KSYM_Return:
2268           if (game_status == GAME_MODE_TITLE)
2269             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2270           else if (game_status == GAME_MODE_MAIN)
2271             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2272           else if (game_status == GAME_MODE_LEVELS)
2273             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2274           else if (game_status == GAME_MODE_LEVELNR)
2275             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2276           else if (game_status == GAME_MODE_SETUP)
2277             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2278           else if (game_status == GAME_MODE_INFO)
2279             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2280           else if (game_status == GAME_MODE_SCORES)
2281             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2282           break;
2283
2284         case KSYM_Escape:
2285           if (game_status != GAME_MODE_MAIN)
2286             FadeSkipNextFadeIn();
2287
2288           if (game_status == GAME_MODE_TITLE)
2289             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2290           else if (game_status == GAME_MODE_LEVELS)
2291             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2292           else if (game_status == GAME_MODE_LEVELNR)
2293             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2294           else if (game_status == GAME_MODE_SETUP)
2295             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2296           else if (game_status == GAME_MODE_INFO)
2297             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2298           else if (game_status == GAME_MODE_SCORES)
2299             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2300           break;
2301
2302         case KSYM_Page_Up:
2303           if (game_status == GAME_MODE_LEVELS)
2304             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2305           else if (game_status == GAME_MODE_LEVELNR)
2306             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2307           else if (game_status == GAME_MODE_SETUP)
2308             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2309           else if (game_status == GAME_MODE_INFO)
2310             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2311           else if (game_status == GAME_MODE_SCORES)
2312             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2313           break;
2314
2315         case KSYM_Page_Down:
2316           if (game_status == GAME_MODE_LEVELS)
2317             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2318           else if (game_status == GAME_MODE_LEVELNR)
2319             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2320           else if (game_status == GAME_MODE_SETUP)
2321             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2322           else if (game_status == GAME_MODE_INFO)
2323             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2324           else if (game_status == GAME_MODE_SCORES)
2325             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2326           break;
2327
2328         default:
2329           break;
2330       }
2331       break;
2332
2333     case GAME_MODE_EDITOR:
2334       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2335         HandleLevelEditorKeyInput(key);
2336       break;
2337
2338     case GAME_MODE_PLAYING:
2339     {
2340       switch (key)
2341       {
2342         case KSYM_Escape:
2343           RequestQuitGame(setup.ask_on_escape);
2344           break;
2345
2346         default:
2347           break;
2348       }
2349       break;
2350     }
2351
2352     default:
2353       if (key == KSYM_Escape)
2354       {
2355         SetGameStatus(GAME_MODE_MAIN);
2356
2357         DrawMainMenu();
2358
2359         return;
2360       }
2361   }
2362 }
2363
2364 void HandleNoEvent(void)
2365 {
2366   HandleMouseCursor();
2367
2368   switch (game_status)
2369   {
2370     case GAME_MODE_PLAYING:
2371       HandleButtonOrFinger(-1, -1, -1);
2372       break;
2373   }
2374 }
2375
2376 void HandleEventActions(void)
2377 {
2378   // if (button_status && game_status != GAME_MODE_PLAYING)
2379   if (button_status && (game_status != GAME_MODE_PLAYING ||
2380                         tape.pausing ||
2381                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2382   {
2383     HandleButton(0, 0, button_status, -button_status);
2384   }
2385   else
2386   {
2387     HandleJoystick();
2388   }
2389
2390   if (network.enabled)
2391     HandleNetworking();
2392
2393   switch (game_status)
2394   {
2395     case GAME_MODE_MAIN:
2396       DrawPreviewLevelAnimation();
2397       break;
2398
2399     case GAME_MODE_EDITOR:
2400       HandleLevelEditorIdle();
2401       break;
2402
2403     default:
2404       break;
2405   }
2406 }
2407
2408 static void HandleTileCursor(int dx, int dy, int button)
2409 {
2410   if (!dx || !button)
2411     ClearPlayerMouseAction();
2412
2413   if (!dx && !dy)
2414     return;
2415
2416   if (button)
2417   {
2418     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2419                          (dx < 0 ? MB_LEFTBUTTON :
2420                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2421   }
2422   else if (!tile_cursor.moving)
2423   {
2424     int old_xpos = tile_cursor.xpos;
2425     int old_ypos = tile_cursor.ypos;
2426     int new_xpos = old_xpos;
2427     int new_ypos = old_ypos;
2428
2429     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2430       new_xpos = old_xpos + dx;
2431
2432     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2433       new_ypos = old_ypos + dy;
2434
2435     SetTileCursorTargetXY(new_xpos, new_ypos);
2436   }
2437 }
2438
2439 static int HandleJoystickForAllPlayers(void)
2440 {
2441   int i;
2442   int result = 0;
2443   boolean no_joysticks_configured = TRUE;
2444   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2445   static byte joy_action_last[MAX_PLAYERS];
2446
2447   for (i = 0; i < MAX_PLAYERS; i++)
2448     if (setup.input[i].use_joystick)
2449       no_joysticks_configured = FALSE;
2450
2451   // if no joysticks configured, map connected joysticks to players
2452   if (no_joysticks_configured)
2453     use_as_joystick_nr = TRUE;
2454
2455   for (i = 0; i < MAX_PLAYERS; i++)
2456   {
2457     byte joy_action = 0;
2458
2459     joy_action = JoystickExt(i, use_as_joystick_nr);
2460     result |= joy_action;
2461
2462     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2463         joy_action != joy_action_last[i])
2464       stored_player[i].action = joy_action;
2465
2466     joy_action_last[i] = joy_action;
2467   }
2468
2469   return result;
2470 }
2471
2472 void HandleJoystick(void)
2473 {
2474   static unsigned int joytest_delay = 0;
2475   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2476   static int joytest_last = 0;
2477   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2478   int delay_value       = GADGET_FRAME_DELAY;
2479   int joystick  = HandleJoystickForAllPlayers();
2480   int keyboard  = key_joystick_mapping;
2481   int joy       = (joystick | keyboard);
2482   int joytest   = joystick;
2483   int left      = joy & JOY_LEFT;
2484   int right     = joy & JOY_RIGHT;
2485   int up        = joy & JOY_UP;
2486   int down      = joy & JOY_DOWN;
2487   int button    = joy & JOY_BUTTON;
2488   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2489   int dx        = (left ? -1    : right ? 1     : 0);
2490   int dy        = (up   ? -1    : down  ? 1     : 0);
2491   boolean use_delay_value_first = (joytest != joytest_last);
2492
2493   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2494   {
2495     // do not handle this button event anymore
2496     return;
2497   }
2498
2499   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2500                     anyTextGadgetActive()))
2501   {
2502     // leave name input in main menu or text input gadget
2503     HandleKey(KSYM_Escape, KEY_PRESSED);
2504     HandleKey(KSYM_Escape, KEY_RELEASED);
2505
2506     return;
2507   }
2508
2509   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2510   {
2511     if (game_status == GAME_MODE_PLAYING)
2512     {
2513       // when playing MM style levels, also use delay for keyboard events
2514       joytest |= keyboard;
2515
2516       // only use first delay value for new events, but not for changed events
2517       use_delay_value_first = (!joytest != !joytest_last);
2518
2519       // only use delay after the initial keyboard event
2520       delay_value = 0;
2521     }
2522
2523     // for any joystick or keyboard event, enable playfield tile cursor
2524     if (dx || dy || button)
2525       SetTileCursorEnabled(TRUE);
2526   }
2527
2528   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2529   {
2530     // delay joystick/keyboard actions if axes/keys continually pressed
2531     newbutton = dx = dy = 0;
2532   }
2533   else
2534   {
2535     // first start with longer delay, then continue with shorter delay
2536     joytest_delay_value =
2537       (use_delay_value_first ? delay_value_first : delay_value);
2538   }
2539
2540   joytest_last = joytest;
2541
2542   switch (game_status)
2543   {
2544     case GAME_MODE_TITLE:
2545     case GAME_MODE_MAIN:
2546     case GAME_MODE_LEVELS:
2547     case GAME_MODE_LEVELNR:
2548     case GAME_MODE_SETUP:
2549     case GAME_MODE_INFO:
2550     case GAME_MODE_SCORES:
2551     {
2552       if (anyTextGadgetActive())
2553         break;
2554
2555       if (game_status == GAME_MODE_TITLE)
2556         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2557       else if (game_status == GAME_MODE_MAIN)
2558         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2559       else if (game_status == GAME_MODE_LEVELS)
2560         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2561       else if (game_status == GAME_MODE_LEVELNR)
2562         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2563       else if (game_status == GAME_MODE_SETUP)
2564         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2565       else if (game_status == GAME_MODE_INFO)
2566         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2567       else if (game_status == GAME_MODE_SCORES)
2568         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2569
2570       break;
2571     }
2572
2573     case GAME_MODE_PLAYING:
2574 #if 0
2575       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2576       if (tape.playing || keyboard)
2577         newbutton = ((joy & JOY_BUTTON) != 0);
2578 #endif
2579
2580       if (newbutton && game.all_players_gone)
2581       {
2582         GameEnd();
2583
2584         return;
2585       }
2586
2587       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2588       {
2589         if (joystick & JOY_ACTION)
2590           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2591       }
2592       else if (tape.recording && tape.pausing && !tape.use_mouse)
2593       {
2594         if (joystick & JOY_ACTION)
2595           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2596       }
2597
2598       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2599         HandleTileCursor(dx, dy, button);
2600
2601       break;
2602
2603     default:
2604       break;
2605   }
2606 }
2607
2608 void HandleSpecialGameControllerButtons(Event *event)
2609 {
2610   int key_status;
2611   Key key;
2612
2613   switch (event->type)
2614   {
2615     case SDL_CONTROLLERBUTTONDOWN:
2616       key_status = KEY_PRESSED;
2617       break;
2618
2619     case SDL_CONTROLLERBUTTONUP:
2620       key_status = KEY_RELEASED;
2621       break;
2622
2623     default:
2624       return;
2625   }
2626
2627   switch (event->cbutton.button)
2628   {
2629     case SDL_CONTROLLER_BUTTON_START:
2630       key = KSYM_space;
2631       break;
2632
2633     case SDL_CONTROLLER_BUTTON_BACK:
2634       key = KSYM_Escape;
2635       break;
2636
2637     default:
2638       return;
2639   }
2640
2641   HandleKey(key, key_status);
2642 }
2643
2644 void HandleSpecialGameControllerKeys(Key key, int key_status)
2645 {
2646 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2647   int button = SDL_CONTROLLER_BUTTON_INVALID;
2648
2649   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2650   if (key == KSYM_Rewind)
2651     button = SDL_CONTROLLER_BUTTON_A;
2652   else if (key == KSYM_FastForward || key == KSYM_Menu)
2653     button = SDL_CONTROLLER_BUTTON_B;
2654
2655   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2656   {
2657     Event event;
2658
2659     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2660                   SDL_CONTROLLERBUTTONUP);
2661
2662     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2663     event.cbutton.button = button;
2664     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2665                            SDL_RELEASED);
2666
2667     HandleJoystickEvent(&event);
2668   }
2669 #endif
2670 }
2671
2672 boolean DoKeysymAction(int keysym)
2673 {
2674   if (keysym < 0)
2675   {
2676     Key key = (Key)(-keysym);
2677
2678     HandleKey(key, KEY_PRESSED);
2679     HandleKey(key, KEY_RELEASED);
2680
2681     return TRUE;
2682   }
2683
2684   return FALSE;
2685 }