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