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