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