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