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