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