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