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