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