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