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