fixed new game control input method 'follow finger' for SDL 1.2 target
[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 /* event filter especially needed for SDL event filtering due to
43    delay problems with lots of mouse motion events when mouse button
44    not pressed (X11 can handle this with 'PointerMotionHintMask') */
45
46 /* event filter addition for SDL2: as SDL2 does not have a function to enable
47    or disable keyboard auto-repeat, filter repeated keyboard events instead */
48
49 static int FilterEvents(const Event *event)
50 {
51   MotionEvent *motion;
52
53 #if defined(TARGET_SDL2)
54   /* skip repeated key press events if keyboard auto-repeat is disabled */
55   if (event->type == EVENT_KEYPRESS &&
56       event->key.repeat &&
57       !keyrepeat_status)
58     return 0;
59 #endif
60
61   /* non-motion events are directly passed to event handler functions */
62   if (event->type != EVENT_MOTIONNOTIFY)
63     return 1;
64
65   motion = (MotionEvent *)event;
66   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
67                              motion->y >= SY && motion->y < SY + SYSIZE);
68
69   /* do no reset mouse cursor before all pending events have been processed */
70   if (gfx.cursor_mode == cursor_mode_last &&
71       ((game_status == GAME_MODE_TITLE &&
72         gfx.cursor_mode == CURSOR_NONE) ||
73        (game_status == GAME_MODE_PLAYING &&
74         gfx.cursor_mode == CURSOR_PLAYFIELD)))
75   {
76     SetMouseCursor(CURSOR_DEFAULT);
77
78     DelayReached(&special_cursor_delay, 0);
79
80     cursor_mode_last = CURSOR_DEFAULT;
81   }
82
83   /* skip mouse motion events without pressed button outside level editor */
84   if (button_status == MB_RELEASED &&
85       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
86     return 0;
87
88   return 1;
89 }
90
91 /* to prevent delay problems, skip mouse motion events if the very next
92    event is also a mouse motion event (and therefore effectively only
93    handling the last of a row of mouse motion events in the event queue) */
94
95 static boolean SkipPressedMouseMotionEvent(const Event *event)
96 {
97   /* nothing to do if the current event is not a mouse motion event */
98   if (event->type != EVENT_MOTIONNOTIFY)
99     return FALSE;
100
101   /* only skip motion events with pressed button outside the game */
102   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
103     return FALSE;
104
105   if (PendingEvent())
106   {
107     Event next_event;
108
109     PeekEvent(&next_event);
110
111     /* if next event is also a mouse motion event, skip the current one */
112     if (next_event.type == EVENT_MOTIONNOTIFY)
113       return TRUE;
114   }
115
116   return FALSE;
117 }
118
119 /* this is especially needed for event modifications for the Android target:
120    if mouse coordinates should be modified in the event filter function,
121    using a properly installed SDL event filter does not work, because in
122    the event filter, mouse coordinates in the event structure are still
123    physical pixel positions, not logical (scaled) screen positions, so this
124    has to be handled at a later stage in the event processing functions
125    (when device pixel positions are already converted to screen positions) */
126
127 boolean NextValidEvent(Event *event)
128 {
129   while (PendingEvent())
130   {
131     boolean handle_this_event = FALSE;
132
133     NextEvent(event);
134
135     if (FilterEvents(event))
136       handle_this_event = TRUE;
137
138     if (SkipPressedMouseMotionEvent(event))
139       handle_this_event = FALSE;
140
141     if (handle_this_event)
142       return TRUE;
143   }
144
145   return FALSE;
146 }
147
148 void HandleEvents()
149 {
150   Event event;
151   unsigned int event_frame_delay = 0;
152   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
153
154   ResetDelayCounter(&event_frame_delay);
155
156   while (NextValidEvent(&event))
157   {
158     switch (event.type)
159     {
160       case EVENT_BUTTONPRESS:
161       case EVENT_BUTTONRELEASE:
162         HandleButtonEvent((ButtonEvent *) &event);
163         break;
164
165       case EVENT_MOTIONNOTIFY:
166         HandleMotionEvent((MotionEvent *) &event);
167         break;
168
169 #if defined(TARGET_SDL2)
170       case EVENT_WHEELMOTION:
171         HandleWheelEvent((WheelEvent *) &event);
172         break;
173
174       case SDL_WINDOWEVENT:
175         HandleWindowEvent((WindowEvent *) &event);
176         break;
177
178       case EVENT_FINGERPRESS:
179       case EVENT_FINGERRELEASE:
180       case EVENT_FINGERMOTION:
181         HandleFingerEvent((FingerEvent *) &event);
182         break;
183
184       case EVENT_TEXTINPUT:
185         HandleTextEvent((TextEvent *) &event);
186         break;
187
188       case SDL_APP_WILLENTERBACKGROUND:
189       case SDL_APP_DIDENTERBACKGROUND:
190       case SDL_APP_WILLENTERFOREGROUND:
191       case SDL_APP_DIDENTERFOREGROUND:
192         HandlePauseResumeEvent((PauseResumeEvent *) &event);
193         break;
194 #endif
195
196       case EVENT_KEYPRESS:
197       case EVENT_KEYRELEASE:
198         HandleKeyEvent((KeyEvent *) &event);
199         break;
200
201       default:
202         HandleOtherEvents(&event);
203         break;
204     }
205
206     // do not handle events for longer than standard frame delay period
207     if (DelayReached(&event_frame_delay, event_frame_delay_value))
208       break;
209   }
210 }
211
212 void HandleOtherEvents(Event *event)
213 {
214   switch (event->type)
215   {
216     case EVENT_EXPOSE:
217       HandleExposeEvent((ExposeEvent *) event);
218       break;
219
220     case EVENT_UNMAPNOTIFY:
221 #if 0
222       /* This causes the game to stop not only when iconified, but also
223          when on another virtual desktop, which might be not desired. */
224       SleepWhileUnmapped();
225 #endif
226       break;
227
228     case EVENT_FOCUSIN:
229     case EVENT_FOCUSOUT:
230       HandleFocusEvent((FocusChangeEvent *) event);
231       break;
232
233     case EVENT_CLIENTMESSAGE:
234       HandleClientMessageEvent((ClientMessageEvent *) event);
235       break;
236
237 #if defined(TARGET_SDL)
238     case SDL_JOYAXISMOTION:
239     case SDL_JOYBUTTONDOWN:
240     case SDL_JOYBUTTONUP:
241       HandleJoystickEvent(event);
242       break;
243
244     case SDL_SYSWMEVENT:
245       HandleWindowManagerEvent(event);
246       break;
247 #endif
248
249     default:
250       break;
251   }
252 }
253
254 void HandleMouseCursor()
255 {
256   if (game_status == GAME_MODE_TITLE)
257   {
258     /* when showing title screens, hide mouse pointer (if not moved) */
259
260     if (gfx.cursor_mode != CURSOR_NONE &&
261         DelayReached(&special_cursor_delay, special_cursor_delay_value))
262     {
263       SetMouseCursor(CURSOR_NONE);
264     }
265   }
266   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
267                                                 tape.single_step))
268   {
269     /* when playing, display a special mouse pointer inside the playfield */
270
271     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
272         cursor_inside_playfield &&
273         DelayReached(&special_cursor_delay, special_cursor_delay_value))
274     {
275       SetMouseCursor(CURSOR_PLAYFIELD);
276     }
277   }
278   else if (gfx.cursor_mode != CURSOR_DEFAULT)
279   {
280     SetMouseCursor(CURSOR_DEFAULT);
281   }
282
283   /* this is set after all pending events have been processed */
284   cursor_mode_last = gfx.cursor_mode;
285 }
286
287 void EventLoop(void)
288 {
289   while (1)
290   {
291     if (PendingEvent())
292       HandleEvents();
293     else
294       HandleMouseCursor();
295
296     /* also execute after pending events have been processed before */
297     HandleNoEvent();
298
299     /* don't use all CPU time when idle; the main loop while playing
300        has its own synchronization and is CPU friendly, too */
301
302     if (game_status == GAME_MODE_PLAYING)
303       HandleGameActions();
304
305     /* always copy backbuffer to visible screen for every video frame */
306     BackToFront();
307
308     /* reset video frame delay to default (may change again while playing) */
309     SetVideoFrameDelay(MenuFrameDelay);
310
311     if (game_status == GAME_MODE_QUIT)
312       return;
313   }
314 }
315
316 void ClearEventQueue()
317 {
318   while (PendingEvent())
319   {
320     Event event;
321
322     NextEvent(&event);
323
324     switch (event.type)
325     {
326       case EVENT_BUTTONRELEASE:
327         button_status = MB_RELEASED;
328         break;
329
330       case EVENT_KEYRELEASE:
331         ClearPlayerAction();
332         break;
333
334       default:
335         HandleOtherEvents(&event);
336         break;
337     }
338   }
339 }
340
341 void ClearPlayerAction()
342 {
343   int i;
344
345   /* simulate key release events for still pressed keys */
346   key_joystick_mapping = 0;
347   for (i = 0; i < MAX_PLAYERS; i++)
348     stored_player[i].action = 0;
349 }
350
351 void SleepWhileUnmapped()
352 {
353   boolean window_unmapped = TRUE;
354
355   KeyboardAutoRepeatOn();
356
357   while (window_unmapped)
358   {
359     Event event;
360
361     NextEvent(&event);
362
363     switch (event.type)
364     {
365       case EVENT_BUTTONRELEASE:
366         button_status = MB_RELEASED;
367         break;
368
369       case EVENT_KEYRELEASE:
370         key_joystick_mapping = 0;
371         break;
372
373       case EVENT_MAPNOTIFY:
374         window_unmapped = FALSE;
375         break;
376
377       case EVENT_UNMAPNOTIFY:
378         /* this is only to surely prevent the 'should not happen' case
379          * of recursively looping between 'SleepWhileUnmapped()' and
380          * 'HandleOtherEvents()' which usually calls this funtion.
381          */
382         break;
383
384       default:
385         HandleOtherEvents(&event);
386         break;
387     }
388   }
389
390   if (game_status == GAME_MODE_PLAYING)
391     KeyboardAutoRepeatOffUnlessAutoplay();
392 }
393
394 void HandleExposeEvent(ExposeEvent *event)
395 {
396 }
397
398 void HandleButtonEvent(ButtonEvent *event)
399 {
400 #if DEBUG_EVENTS_BUTTON
401   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
402         event->button,
403         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
404         event->x, event->y);
405 #endif
406
407 #if defined(HAS_SCREEN_KEYBOARD)
408   if (video.shifted_up)
409     event->y += video.shifted_up_pos;
410 #endif
411
412   motion_status = FALSE;
413
414   if (event->type == EVENT_BUTTONPRESS)
415     button_status = event->button;
416   else
417     button_status = MB_RELEASED;
418
419   HandleButton(event->x, event->y, button_status, event->button);
420 }
421
422 void HandleMotionEvent(MotionEvent *event)
423 {
424   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
425     return;
426
427   motion_status = TRUE;
428
429 #if DEBUG_EVENTS_MOTION
430   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
431         button_status, event->x, event->y);
432 #endif
433
434   HandleButton(event->x, event->y, button_status, button_status);
435 }
436
437 #if defined(TARGET_SDL2)
438
439 void HandleWheelEvent(WheelEvent *event)
440 {
441   int button_nr;
442
443 #if DEBUG_EVENTS_WHEEL
444 #if 1
445   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
446         event->which, event->x, event->y);
447 #else
448   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
449   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
450         event->which, event->x, event->y,
451         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
452          "SDL_MOUSEWHEEL_FLIPPED"));
453 #endif
454 #endif
455
456   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
457                event->x > 0 ? MB_WHEEL_RIGHT :
458                event->y < 0 ? MB_WHEEL_DOWN :
459                event->y > 0 ? MB_WHEEL_UP : 0);
460
461 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
462   // accelerated mouse wheel available on Mac and Windows
463   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
464 #else
465   // no accelerated mouse wheel available on Unix/Linux
466   wheel_steps = DEFAULT_WHEEL_STEPS;
467 #endif
468
469   motion_status = FALSE;
470
471   button_status = button_nr;
472   HandleButton(0, 0, button_status, -button_nr);
473
474   button_status = MB_RELEASED;
475   HandleButton(0, 0, button_status, -button_nr);
476 }
477
478 void HandleWindowEvent(WindowEvent *event)
479 {
480 #if DEBUG_EVENTS_WINDOW
481   int subtype = event->event;
482
483   char *event_name =
484     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
485      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
486      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
487      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
488      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
489      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
490      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
491      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
492      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
493      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
494      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
495      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
496      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
497      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
498      "(UNKNOWN)");
499
500   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
501         event_name, event->data1, event->data2);
502 #endif
503
504 #if 0
505   // (not needed, as the screen gets redrawn every 20 ms anyway)
506   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
507       event->event == SDL_WINDOWEVENT_RESIZED ||
508       event->event == SDL_WINDOWEVENT_EXPOSED)
509     SDLRedrawWindow();
510 #endif
511
512   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
513   {
514     int new_window_width  = event->data1;
515     int new_window_height = event->data2;
516
517     // if window size has changed after resizing, calculate new scaling factor
518     if (new_window_width  != video.window_width ||
519         new_window_height != video.window_height)
520     {
521       int new_xpercent = (100 * new_window_width  / video.width);
522       int new_ypercent = (100 * new_window_height / video.height);
523
524       // (extreme window scaling allowed, but cannot be saved permanently)
525       video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
526       setup.window_scaling_percent =
527         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
528             MAX_WINDOW_SCALING_PERCENT);
529
530       video.window_width  = new_window_width;
531       video.window_height = new_window_height;
532
533       if (game_status == GAME_MODE_SETUP)
534         RedrawSetupScreenAfterFullscreenToggle();
535
536       SetWindowTitle();
537     }
538   }
539 }
540
541 #define NUM_TOUCH_FINGERS               3
542
543 static struct
544 {
545   boolean touched;
546   SDL_FingerID finger_id;
547   int counter;
548   Key key;
549 } touch_info[NUM_TOUCH_FINGERS];
550
551 void HandleFingerEvent(FingerEvent *event)
552 {
553   static Key motion_key_x = KSYM_UNDEFINED;
554   static Key motion_key_y = KSYM_UNDEFINED;
555   static Key button_key = KSYM_UNDEFINED;
556   static float motion_x1, motion_y1;
557   static float button_x1, button_y1;
558   static SDL_FingerID motion_id = -1;
559   static SDL_FingerID button_id = -1;
560   int move_trigger_distance_percent = setup.touch.move_distance;
561   int drop_trigger_distance_percent = setup.touch.drop_distance;
562   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
563   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
564   float event_x = event->x;
565   float event_y = event->y;
566
567 #if DEBUG_EVENTS_FINGER
568   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
569         event->type == EVENT_FINGERPRESS ? "pressed" :
570         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
571         event->touchId,
572         event->fingerId,
573         event->x, event->y,
574         event->dx, event->dy,
575         event->pressure);
576 #endif
577
578   if (game_status != GAME_MODE_PLAYING)
579     return;
580
581   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
582     return;
583
584   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
585   {
586     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
587                       KEY_PRESSED);
588     Key key = (event->x < 1.0 / 3.0 ?
589                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
590                 setup.input[0].key.drop) :
591                event->x > 2.0 / 3.0 ?
592                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
593                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
594                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
595                 setup.input[0].key.right) :
596                KSYM_UNDEFINED);
597     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
598                              "KEY_PRESSED");
599     int i;
600
601     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
602           getKeyNameFromKey(key), key_status_name, event->fingerId);
603
604     // check if we already know this touch event's finger id
605     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
606     {
607       if (touch_info[i].touched &&
608           touch_info[i].finger_id == event->fingerId)
609       {
610         // Error(ERR_DEBUG, "MARK 1: %d", i);
611
612         break;
613       }
614     }
615
616     if (i >= NUM_TOUCH_FINGERS)
617     {
618       if (key_status == KEY_PRESSED)
619       {
620         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
621
622         // unknown finger id -- get new, empty slot, if available
623         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
624         {
625           if (touch_info[i].counter < oldest_counter)
626           {
627             oldest_pos = i;
628             oldest_counter = touch_info[i].counter;
629
630             // Error(ERR_DEBUG, "MARK 2: %d", i);
631           }
632
633           if (!touch_info[i].touched)
634           {
635             // Error(ERR_DEBUG, "MARK 3: %d", i);
636
637             break;
638           }
639         }
640
641         if (i >= NUM_TOUCH_FINGERS)
642         {
643           // all slots allocated -- use oldest slot
644           i = oldest_pos;
645
646           // Error(ERR_DEBUG, "MARK 4: %d", i);
647         }
648       }
649       else
650       {
651         // release of previously unknown key (should not happen)
652
653         if (key != KSYM_UNDEFINED)
654         {
655           HandleKey(key, KEY_RELEASED);
656
657           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
658                 getKeyNameFromKey(key), "KEY_RELEASED", i);
659         }
660       }
661     }
662
663     if (i < NUM_TOUCH_FINGERS)
664     {
665       if (key_status == KEY_PRESSED)
666       {
667         if (touch_info[i].key != key)
668         {
669           if (touch_info[i].key != KSYM_UNDEFINED)
670           {
671             HandleKey(touch_info[i].key, KEY_RELEASED);
672
673             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
674                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
675           }
676
677           if (key != KSYM_UNDEFINED)
678           {
679             HandleKey(key, KEY_PRESSED);
680
681             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
682                   getKeyNameFromKey(key), "KEY_PRESSED", i);
683           }
684         }
685
686         touch_info[i].touched = TRUE;
687         touch_info[i].finger_id = event->fingerId;
688         touch_info[i].counter = Counter();
689         touch_info[i].key = key;
690       }
691       else
692       {
693         if (touch_info[i].key != KSYM_UNDEFINED)
694         {
695           HandleKey(touch_info[i].key, KEY_RELEASED);
696
697           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
698                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
699         }
700
701         touch_info[i].touched = FALSE;
702         touch_info[i].finger_id = 0;
703         touch_info[i].counter = 0;
704         touch_info[i].key = 0;
705       }
706     }
707
708     return;
709   }
710
711   // use touch direction control
712
713   if (event->type == EVENT_FINGERPRESS)
714   {
715     if (event_x > 1.0 / 3.0)
716     {
717       // motion area
718
719       motion_id = event->fingerId;
720
721       motion_x1 = event_x;
722       motion_y1 = event_y;
723
724       motion_key_x = KSYM_UNDEFINED;
725       motion_key_y = KSYM_UNDEFINED;
726
727       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
728     }
729     else
730     {
731       // button area
732
733       button_id = event->fingerId;
734
735       button_x1 = event_x;
736       button_y1 = event_y;
737
738       button_key = setup.input[0].key.snap;
739
740       HandleKey(button_key, KEY_PRESSED);
741
742       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
743     }
744   }
745   else if (event->type == EVENT_FINGERRELEASE)
746   {
747     if (event->fingerId == motion_id)
748     {
749       motion_id = -1;
750
751       if (motion_key_x != KSYM_UNDEFINED)
752         HandleKey(motion_key_x, KEY_RELEASED);
753       if (motion_key_y != KSYM_UNDEFINED)
754         HandleKey(motion_key_y, KEY_RELEASED);
755
756       motion_key_x = KSYM_UNDEFINED;
757       motion_key_y = KSYM_UNDEFINED;
758
759       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
760     }
761     else if (event->fingerId == button_id)
762     {
763       button_id = -1;
764
765       if (button_key != KSYM_UNDEFINED)
766         HandleKey(button_key, KEY_RELEASED);
767
768       button_key = KSYM_UNDEFINED;
769
770       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
771     }
772   }
773   else if (event->type == EVENT_FINGERMOTION)
774   {
775     if (event->fingerId == motion_id)
776     {
777       float distance_x = ABS(event_x - motion_x1);
778       float distance_y = ABS(event_y - motion_y1);
779       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
780                               event_x > motion_x1 ? setup.input[0].key.right :
781                               KSYM_UNDEFINED);
782       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
783                               event_y > motion_y1 ? setup.input[0].key.down :
784                               KSYM_UNDEFINED);
785
786       if (distance_x < move_trigger_distance / 2 ||
787           distance_x < distance_y)
788         new_motion_key_x = KSYM_UNDEFINED;
789
790       if (distance_y < move_trigger_distance / 2 ||
791           distance_y < distance_x)
792         new_motion_key_y = KSYM_UNDEFINED;
793
794       if (distance_x > move_trigger_distance ||
795           distance_y > move_trigger_distance)
796       {
797         if (new_motion_key_x != motion_key_x)
798         {
799           if (motion_key_x != KSYM_UNDEFINED)
800             HandleKey(motion_key_x, KEY_RELEASED);
801           if (new_motion_key_x != KSYM_UNDEFINED)
802             HandleKey(new_motion_key_x, KEY_PRESSED);
803         }
804
805         if (new_motion_key_y != motion_key_y)
806         {
807           if (motion_key_y != KSYM_UNDEFINED)
808             HandleKey(motion_key_y, KEY_RELEASED);
809           if (new_motion_key_y != KSYM_UNDEFINED)
810             HandleKey(new_motion_key_y, KEY_PRESSED);
811         }
812
813         motion_x1 = event_x;
814         motion_y1 = event_y;
815
816         motion_key_x = new_motion_key_x;
817         motion_key_y = new_motion_key_y;
818
819         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
820       }
821     }
822     else if (event->fingerId == button_id)
823     {
824       float distance_x = ABS(event_x - button_x1);
825       float distance_y = ABS(event_y - button_y1);
826
827       if (distance_x < drop_trigger_distance / 2 &&
828           distance_y > drop_trigger_distance)
829       {
830         if (button_key == setup.input[0].key.snap)
831           HandleKey(button_key, KEY_RELEASED);
832
833         button_x1 = event_x;
834         button_y1 = event_y;
835
836         button_key = setup.input[0].key.drop;
837
838         HandleKey(button_key, KEY_PRESSED);
839
840         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
841       }
842     }
843   }
844 }
845
846 static void HandleFollowFinger(int mx, int my, int button)
847 {
848   static int old_mx = 0, old_my = 0;
849   static Key motion_key_x = KSYM_UNDEFINED;
850   static Key motion_key_y = KSYM_UNDEFINED;
851   static boolean started_on_player = FALSE;
852   static boolean player_is_dropping = FALSE;
853   static int player_drop_count = 0;
854   static int last_player_x = -1;
855   static int last_player_y = -1;
856
857   if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
858     return;
859
860   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
861   {
862     touch_info[0].touched = TRUE;
863     touch_info[0].key = 0;
864
865     old_mx = mx;
866     old_my = my;
867
868     if (!motion_status)
869     {
870       started_on_player = FALSE;
871       player_is_dropping = FALSE;
872       player_drop_count = 0;
873       last_player_x = -1;
874       last_player_y = -1;
875
876       motion_key_x = KSYM_UNDEFINED;
877       motion_key_y = KSYM_UNDEFINED;
878
879       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
880     }
881   }
882   else if (button == MB_RELEASED && touch_info[0].touched)
883   {
884     touch_info[0].touched = FALSE;
885     touch_info[0].key = 0;
886
887     old_mx = 0;
888     old_my = 0;
889
890     if (motion_key_x != KSYM_UNDEFINED)
891       HandleKey(motion_key_x, KEY_RELEASED);
892     if (motion_key_y != KSYM_UNDEFINED)
893       HandleKey(motion_key_y, KEY_RELEASED);
894
895     if (started_on_player)
896     {
897       if (player_is_dropping)
898       {
899         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
900
901         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
902       }
903       else
904       {
905         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
906
907         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
908       }
909     }
910
911     motion_key_x = KSYM_UNDEFINED;
912     motion_key_y = KSYM_UNDEFINED;
913
914     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
915   }
916
917   if (touch_info[0].touched)
918   {
919     int src_x = local_player->jx;
920     int src_y = local_player->jy;
921     int dst_x = getLevelFromScreenX(old_mx);
922     int dst_y = getLevelFromScreenY(old_my);
923     int dx = dst_x - src_x;
924     int dy = dst_y - src_y;
925     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
926                             dx > 0 ? setup.input[0].key.right :
927                             KSYM_UNDEFINED);
928     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
929                             dy > 0 ? setup.input[0].key.down :
930                             KSYM_UNDEFINED);
931
932     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
933         (last_player_x != local_player->jx ||
934          last_player_y != local_player->jy))
935     {
936       // in case of asymmetric diagonal movement, use "preferred" direction
937
938       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
939
940       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
941         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
942       else
943         local_player->last_move_dir = last_move_dir;
944
945       // (required to prevent accidentally forcing direction for next movement)
946       last_player_x = local_player->jx;
947       last_player_y = local_player->jy;
948     }
949
950     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
951     {
952       started_on_player = TRUE;
953       player_drop_count = getPlayerInventorySize(0);
954       player_is_dropping = (player_drop_count > 0);
955
956       if (player_is_dropping)
957       {
958         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
959
960         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
961       }
962       else
963       {
964         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
965
966         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
967       }
968     }
969     else if (dx != 0 || dy != 0)
970     {
971       if (player_is_dropping &&
972           player_drop_count == getPlayerInventorySize(0))
973       {
974         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
975
976         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
977         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
978
979         player_is_dropping = FALSE;
980       }
981     }
982
983     if (new_motion_key_x != motion_key_x)
984     {
985       Error(ERR_DEBUG, "---------- %s %s ----------",
986             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
987             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
988
989       if (motion_key_x != KSYM_UNDEFINED)
990         HandleKey(motion_key_x, KEY_RELEASED);
991       if (new_motion_key_x != KSYM_UNDEFINED)
992         HandleKey(new_motion_key_x, KEY_PRESSED);
993     }
994
995     if (new_motion_key_y != motion_key_y)
996     {
997       Error(ERR_DEBUG, "---------- %s %s ----------",
998             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
999             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1000
1001       if (motion_key_y != KSYM_UNDEFINED)
1002         HandleKey(motion_key_y, KEY_RELEASED);
1003       if (new_motion_key_y != KSYM_UNDEFINED)
1004         HandleKey(new_motion_key_y, KEY_PRESSED);
1005     }
1006
1007     motion_key_x = new_motion_key_x;
1008     motion_key_y = new_motion_key_y;
1009   }
1010 }
1011
1012 static boolean checkTextInputKeyModState()
1013 {
1014   // when playing, only handle raw key events and ignore text input
1015   if (game_status == GAME_MODE_PLAYING)
1016     return FALSE;
1017
1018   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1019 }
1020
1021 void HandleTextEvent(TextEvent *event)
1022 {
1023   char *text = event->text;
1024   Key key = getKeyFromKeyName(text);
1025
1026 #if DEBUG_EVENTS_TEXT
1027   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1028         text,
1029         strlen(text),
1030         text[0], (int)(text[0]),
1031         key,
1032         getKeyNameFromKey(key),
1033         GetKeyModState());
1034 #endif
1035
1036 #if !defined(HAS_SCREEN_KEYBOARD)
1037   // non-mobile devices: only handle key input with modifier keys pressed here
1038   // (every other key input is handled directly as physical key input event)
1039   if (!checkTextInputKeyModState())
1040     return;
1041 #endif
1042
1043   // process text input as "classic" (with uppercase etc.) key input event
1044   HandleKey(key, KEY_PRESSED);
1045   HandleKey(key, KEY_RELEASED);
1046 }
1047
1048 void HandlePauseResumeEvent(PauseResumeEvent *event)
1049 {
1050   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1051   {
1052     Mix_PauseMusic();
1053   }
1054   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1055   {
1056     Mix_ResumeMusic();
1057   }
1058 }
1059
1060 #endif
1061
1062 void HandleKeyEvent(KeyEvent *event)
1063 {
1064   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1065   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1066   Key key = GetEventKey(event, with_modifiers);
1067   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1068
1069 #if DEBUG_EVENTS_KEY
1070   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1071         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1072         event->keysym.scancode,
1073         event->keysym.sym,
1074         keymod,
1075         GetKeyModState(),
1076         key,
1077         getKeyNameFromKey(key));
1078 #endif
1079
1080 #if defined(PLATFORM_ANDROID)
1081   // always map the "back" button to the "escape" key on Android devices
1082   if (key == KSYM_Back)
1083     key = KSYM_Escape;
1084 #endif
1085
1086   HandleKeyModState(keymod, key_status);
1087
1088 #if defined(TARGET_SDL2)
1089   // only handle raw key input without text modifier keys pressed
1090   if (!checkTextInputKeyModState())
1091     HandleKey(key, key_status);
1092 #else
1093   HandleKey(key, key_status);
1094 #endif
1095 }
1096
1097 void HandleFocusEvent(FocusChangeEvent *event)
1098 {
1099   static int old_joystick_status = -1;
1100
1101   if (event->type == EVENT_FOCUSOUT)
1102   {
1103     KeyboardAutoRepeatOn();
1104     old_joystick_status = joystick.status;
1105     joystick.status = JOYSTICK_NOT_AVAILABLE;
1106
1107     ClearPlayerAction();
1108   }
1109   else if (event->type == EVENT_FOCUSIN)
1110   {
1111     /* When there are two Rocks'n'Diamonds windows which overlap and
1112        the player moves the pointer from one game window to the other,
1113        a 'FocusOut' event is generated for the window the pointer is
1114        leaving and a 'FocusIn' event is generated for the window the
1115        pointer is entering. In some cases, it can happen that the
1116        'FocusIn' event is handled by the one game process before the
1117        'FocusOut' event by the other game process. In this case the
1118        X11 environment would end up with activated keyboard auto repeat,
1119        because unfortunately this is a global setting and not (which
1120        would be far better) set for each X11 window individually.
1121        The effect would be keyboard auto repeat while playing the game
1122        (game_status == GAME_MODE_PLAYING), which is not desired.
1123        To avoid this special case, we just wait 1/10 second before
1124        processing the 'FocusIn' event.
1125     */
1126
1127     if (game_status == GAME_MODE_PLAYING)
1128     {
1129       Delay(100);
1130       KeyboardAutoRepeatOffUnlessAutoplay();
1131     }
1132
1133     if (old_joystick_status != -1)
1134       joystick.status = old_joystick_status;
1135   }
1136 }
1137
1138 void HandleClientMessageEvent(ClientMessageEvent *event)
1139 {
1140   if (CheckCloseWindowEvent(event))
1141     CloseAllAndExit(0);
1142 }
1143
1144 void HandleWindowManagerEvent(Event *event)
1145 {
1146 #if defined(TARGET_SDL)
1147   SDLHandleWindowManagerEvent(event);
1148 #endif
1149 }
1150
1151 void HandleButton(int mx, int my, int button, int button_nr)
1152 {
1153   static int old_mx = 0, old_my = 0;
1154   boolean button_hold = FALSE;
1155
1156   if (button_nr < 0)
1157   {
1158     mx = old_mx;
1159     my = old_my;
1160     button_nr = -button_nr;
1161     button_hold = TRUE;
1162   }
1163   else
1164   {
1165     old_mx = mx;
1166     old_my = my;
1167   }
1168
1169 #if defined(PLATFORM_ANDROID)
1170   // when playing, only handle gadgets when using "follow finger" controls
1171   boolean handle_gadgets =
1172     (game_status != GAME_MODE_PLAYING ||
1173      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1174
1175   if (handle_gadgets &&
1176       HandleGadgets(mx, my, button))
1177   {
1178     /* do not handle this button event anymore */
1179     mx = my = -32;      /* force mouse event to be outside screen tiles */
1180   }
1181 #else
1182   if (HandleGadgets(mx, my, button))
1183   {
1184     /* do not handle this button event anymore */
1185     mx = my = -32;      /* force mouse event to be outside screen tiles */
1186   }
1187 #endif
1188
1189   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1190     return;
1191
1192   /* do not use scroll wheel button events for anything other than gadgets */
1193   if (IS_WHEEL_BUTTON(button_nr))
1194     return;
1195
1196   switch (game_status)
1197   {
1198     case GAME_MODE_TITLE:
1199       HandleTitleScreen(mx, my, 0, 0, button);
1200       break;
1201
1202     case GAME_MODE_MAIN:
1203       HandleMainMenu(mx, my, 0, 0, button);
1204       break;
1205
1206     case GAME_MODE_PSEUDO_TYPENAME:
1207       HandleTypeName(0, KSYM_Return);
1208       break;
1209
1210     case GAME_MODE_LEVELS:
1211       HandleChooseLevelSet(mx, my, 0, 0, button);
1212       break;
1213
1214     case GAME_MODE_LEVELNR:
1215       HandleChooseLevelNr(mx, my, 0, 0, button);
1216       break;
1217
1218     case GAME_MODE_SCORES:
1219       HandleHallOfFame(0, 0, 0, 0, button);
1220       break;
1221
1222     case GAME_MODE_EDITOR:
1223       HandleLevelEditorIdle();
1224       break;
1225
1226     case GAME_MODE_INFO:
1227       HandleInfoScreen(mx, my, 0, 0, button);
1228       break;
1229
1230     case GAME_MODE_SETUP:
1231       HandleSetupScreen(mx, my, 0, 0, button);
1232       break;
1233
1234 #if defined(TARGET_SDL2)
1235     case GAME_MODE_PLAYING:
1236       HandleFollowFinger(mx, my, button);
1237 #endif
1238
1239 #ifdef DEBUG
1240       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my) &&
1241           GetKeyModState() & KMOD_Control)
1242         DumpTileFromScreen(mx, my);
1243 #endif
1244
1245       break;
1246
1247     default:
1248       break;
1249   }
1250 }
1251
1252 static boolean is_string_suffix(char *string, char *suffix)
1253 {
1254   int string_len = strlen(string);
1255   int suffix_len = strlen(suffix);
1256
1257   if (suffix_len > string_len)
1258     return FALSE;
1259
1260   return (strEqual(&string[string_len - suffix_len], suffix));
1261 }
1262
1263 #define MAX_CHEAT_INPUT_LEN     32
1264
1265 static void HandleKeysSpecial(Key key)
1266 {
1267   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1268   char letter = getCharFromKey(key);
1269   int cheat_input_len = strlen(cheat_input);
1270   int i;
1271
1272   if (letter == 0)
1273     return;
1274
1275   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1276   {
1277     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1278       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1279
1280     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1281   }
1282
1283   cheat_input[cheat_input_len++] = letter;
1284   cheat_input[cheat_input_len] = '\0';
1285
1286 #if DEBUG_EVENTS_KEY
1287   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1288 #endif
1289
1290   if (game_status == GAME_MODE_MAIN)
1291   {
1292     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1293         is_string_suffix(cheat_input, ":ist"))
1294     {
1295       InsertSolutionTape();
1296     }
1297     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1298              is_string_suffix(cheat_input, ":rg"))
1299     {
1300       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1301       DrawMainMenu();
1302     }
1303     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1304              is_string_suffix(cheat_input, ":rs"))
1305     {
1306       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1307       DrawMainMenu();
1308     }
1309     else if (is_string_suffix(cheat_input, ":reload-music") ||
1310              is_string_suffix(cheat_input, ":rm"))
1311     {
1312       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1313       DrawMainMenu();
1314     }
1315     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1316              is_string_suffix(cheat_input, ":ra"))
1317     {
1318       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1319                           1 << ARTWORK_TYPE_SOUNDS |
1320                           1 << ARTWORK_TYPE_MUSIC);
1321       DrawMainMenu();
1322     }
1323     else if (is_string_suffix(cheat_input, ":dump-level") ||
1324              is_string_suffix(cheat_input, ":dl"))
1325     {
1326       DumpLevel(&level);
1327     }
1328     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1329              is_string_suffix(cheat_input, ":dt"))
1330     {
1331       DumpTape(&tape);
1332     }
1333     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1334              is_string_suffix(cheat_input, ":ft"))
1335     {
1336       /* fix single-player tapes that contain player input for more than one
1337          player (due to a bug in 3.3.1.2 and earlier versions), which results
1338          in playing levels with more than one player in multi-player mode,
1339          even though the tape was originally recorded in single-player mode */
1340
1341       /* remove player input actions for all players but the first one */
1342       for (i = 1; i < MAX_PLAYERS; i++)
1343         tape.player_participates[i] = FALSE;
1344
1345       tape.changed = TRUE;
1346     }
1347     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1348              is_string_suffix(cheat_input, ":snl"))
1349     {
1350       SaveNativeLevel(&level);
1351     }
1352   }
1353   else if (game_status == GAME_MODE_PLAYING)
1354   {
1355 #ifdef DEBUG
1356     if (is_string_suffix(cheat_input, ".q"))
1357       DEBUG_SetMaximumDynamite();
1358 #endif
1359   }
1360   else if (game_status == GAME_MODE_EDITOR)
1361   {
1362     if (is_string_suffix(cheat_input, ":dump-brush") ||
1363         is_string_suffix(cheat_input, ":DB"))
1364     {
1365       DumpBrush();
1366     }
1367     else if (is_string_suffix(cheat_input, ":DDB"))
1368     {
1369       DumpBrush_Small();
1370     }
1371   }
1372 }
1373
1374 void HandleKeysDebug(Key key)
1375 {
1376 #ifdef DEBUG
1377   int i;
1378
1379   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1380   {
1381     boolean mod_key_pressed = (GetKeyModState() != KMOD_None);
1382
1383     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1384     {
1385       if (key == setup.debug.frame_delay_key[i] &&
1386           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1387       {
1388         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1389                           setup.debug.frame_delay[i] : GAME_FRAME_DELAY);
1390
1391         if (!setup.debug.frame_delay_game_only)
1392           MenuFrameDelay = GameFrameDelay;
1393
1394         SetVideoFrameDelay(GameFrameDelay);
1395
1396         if (GameFrameDelay > ONE_SECOND_DELAY)
1397           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1398         else if (GameFrameDelay != 0)
1399           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1400                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1401                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1402         else
1403           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1404
1405         break;
1406       }
1407     }
1408   }
1409
1410   if (game_status == GAME_MODE_PLAYING)
1411   {
1412     if (key == KSYM_d)
1413     {
1414       options.debug = !options.debug;
1415
1416       Error(ERR_DEBUG, "debug mode %s",
1417             (options.debug ? "enabled" : "disabled"));
1418     }
1419     else if (key == KSYM_v)
1420     {
1421       Error(ERR_DEBUG, "currently using game engine version %d",
1422             game.engine_version);
1423     }
1424   }
1425 #endif
1426 }
1427
1428 void HandleKey(Key key, int key_status)
1429 {
1430   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1431   static boolean ignore_repeated_key = FALSE;
1432   static struct SetupKeyboardInfo ski;
1433   static struct SetupShortcutInfo ssi;
1434   static struct
1435   {
1436     Key *key_custom;
1437     Key *key_snap;
1438     Key key_default;
1439     byte action;
1440   } key_info[] =
1441   {
1442     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1443     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1444     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1445     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1446     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1447     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1448   };
1449   int joy = 0;
1450   int i;
1451
1452   if (game_status == GAME_MODE_PLAYING)
1453   {
1454     /* only needed for single-step tape recording mode */
1455     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1456     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1457     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1458     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1459     int pnr;
1460
1461     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1462     {
1463       byte key_action = 0;
1464
1465       if (setup.input[pnr].use_joystick)
1466         continue;
1467
1468       ski = setup.input[pnr].key;
1469
1470       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1471         if (key == *key_info[i].key_custom)
1472           key_action |= key_info[i].action;
1473
1474       /* use combined snap+direction keys for the first player only */
1475       if (pnr == 0)
1476       {
1477         ssi = setup.shortcut;
1478
1479         for (i = 0; i < NUM_DIRECTIONS; i++)
1480           if (key == *key_info[i].key_snap)
1481             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1482       }
1483
1484       /* clear delayed snap and drop actions in single step mode (see below) */
1485       if (tape.single_step)
1486       {
1487         if (clear_snap_button[pnr])
1488         {
1489           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1490           clear_snap_button[pnr] = FALSE;
1491         }
1492
1493         if (clear_drop_button[pnr])
1494         {
1495           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1496           clear_drop_button[pnr] = FALSE;
1497         }
1498       }
1499
1500       if (key_status == KEY_PRESSED)
1501         stored_player[pnr].action |= key_action;
1502       else
1503         stored_player[pnr].action &= ~key_action;
1504
1505       if (tape.single_step && tape.recording && tape.pausing)
1506       {
1507         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1508         {
1509           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1510
1511           /* if snap key already pressed, don't snap when releasing (below) */
1512           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1513             element_snapped[pnr] = TRUE;
1514
1515           /* if drop key already pressed, don't drop when releasing (below) */
1516           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1517             element_dropped[pnr] = TRUE;
1518         }
1519         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1520         {
1521           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1522               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1523           {
1524
1525             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1526                 getRedDiskReleaseFlag_SP() == 0)
1527               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1528
1529             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1530           }
1531         }
1532         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1533         {
1534           if (key_action & KEY_BUTTON_SNAP)
1535           {
1536             /* if snap key was released without moving (see above), snap now */
1537             if (!element_snapped[pnr])
1538             {
1539               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1540
1541               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1542
1543               /* clear delayed snap button on next event */
1544               clear_snap_button[pnr] = TRUE;
1545             }
1546
1547             element_snapped[pnr] = FALSE;
1548           }
1549
1550           if (key_action & KEY_BUTTON_DROP &&
1551               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1552           {
1553             /* if drop key was released without moving (see above), drop now */
1554             if (!element_dropped[pnr])
1555             {
1556               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1557
1558               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1559                   getRedDiskReleaseFlag_SP() != 0)
1560                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1561
1562               /* clear delayed drop button on next event */
1563               clear_drop_button[pnr] = TRUE;
1564             }
1565
1566             element_dropped[pnr] = FALSE;
1567           }
1568         }
1569       }
1570       else if (tape.recording && tape.pausing)
1571       {
1572         /* prevent key release events from un-pausing a paused game */
1573         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1574           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1575       }
1576     }
1577   }
1578   else
1579   {
1580     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1581       if (key == key_info[i].key_default)
1582         joy |= key_info[i].action;
1583   }
1584
1585   if (joy)
1586   {
1587     if (key_status == KEY_PRESSED)
1588       key_joystick_mapping |= joy;
1589     else
1590       key_joystick_mapping &= ~joy;
1591
1592     HandleJoystick();
1593   }
1594
1595   if (game_status != GAME_MODE_PLAYING)
1596     key_joystick_mapping = 0;
1597
1598   if (key_status == KEY_RELEASED)
1599   {
1600     // reset flag to ignore repeated "key pressed" events after key release
1601     ignore_repeated_key = FALSE;
1602
1603     return;
1604   }
1605
1606   if ((key == KSYM_F11 ||
1607        ((key == KSYM_Return ||
1608          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1609       video.fullscreen_available &&
1610       !ignore_repeated_key)
1611   {
1612     setup.fullscreen = !setup.fullscreen;
1613
1614     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1615
1616     if (game_status == GAME_MODE_SETUP)
1617       RedrawSetupScreenAfterFullscreenToggle();
1618
1619     // set flag to ignore repeated "key pressed" events
1620     ignore_repeated_key = TRUE;
1621
1622     return;
1623   }
1624
1625   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1626        key == KSYM_minus || key == KSYM_KP_Subtract ||
1627        key == KSYM_plus  || key == KSYM_KP_Add ||
1628        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1629       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1630       video.window_scaling_available &&
1631       !video.fullscreen_enabled)
1632   {
1633     if (key == KSYM_0 || key == KSYM_KP_0)
1634       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1635     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1636       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1637     else
1638       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1639
1640     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1641       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1642     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1643       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1644
1645     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1646
1647     if (game_status == GAME_MODE_SETUP)
1648       RedrawSetupScreenAfterFullscreenToggle();
1649
1650     return;
1651   }
1652
1653   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1654       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1655   {
1656     GameEnd();
1657
1658     return;
1659   }
1660
1661   if (game_status == GAME_MODE_MAIN &&
1662       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1663   {
1664     StartGameActions(options.network, setup.autorecord, level.random_seed);
1665
1666     return;
1667   }
1668
1669   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1670   {
1671     if (key == setup.shortcut.save_game)
1672       TapeQuickSave();
1673     else if (key == setup.shortcut.load_game)
1674       TapeQuickLoad();
1675     else if (key == setup.shortcut.toggle_pause)
1676       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1677
1678     HandleTapeButtonKeys(key);
1679     HandleSoundButtonKeys(key);
1680   }
1681
1682   if (game_status == GAME_MODE_PLAYING && !network_playing)
1683   {
1684     int centered_player_nr_next = -999;
1685
1686     if (key == setup.shortcut.focus_player_all)
1687       centered_player_nr_next = -1;
1688     else
1689       for (i = 0; i < MAX_PLAYERS; i++)
1690         if (key == setup.shortcut.focus_player[i])
1691           centered_player_nr_next = i;
1692
1693     if (centered_player_nr_next != -999)
1694     {
1695       game.centered_player_nr_next = centered_player_nr_next;
1696       game.set_centered_player = TRUE;
1697
1698       if (tape.recording)
1699       {
1700         tape.centered_player_nr_next = game.centered_player_nr_next;
1701         tape.set_centered_player = TRUE;
1702       }
1703     }
1704   }
1705
1706   HandleKeysSpecial(key);
1707
1708   if (HandleGadgetsKeyInput(key))
1709   {
1710     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1711       key = KSYM_UNDEFINED;
1712   }
1713
1714   switch (game_status)
1715   {
1716     case GAME_MODE_PSEUDO_TYPENAME:
1717       HandleTypeName(0, key);
1718       break;
1719
1720     case GAME_MODE_TITLE:
1721     case GAME_MODE_MAIN:
1722     case GAME_MODE_LEVELS:
1723     case GAME_MODE_LEVELNR:
1724     case GAME_MODE_SETUP:
1725     case GAME_MODE_INFO:
1726     case GAME_MODE_SCORES:
1727       switch (key)
1728       {
1729         case KSYM_space:
1730         case KSYM_Return:
1731           if (game_status == GAME_MODE_TITLE)
1732             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1733           else if (game_status == GAME_MODE_MAIN)
1734             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1735           else if (game_status == GAME_MODE_LEVELS)
1736             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1737           else if (game_status == GAME_MODE_LEVELNR)
1738             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1739           else if (game_status == GAME_MODE_SETUP)
1740             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1741           else if (game_status == GAME_MODE_INFO)
1742             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1743           else if (game_status == GAME_MODE_SCORES)
1744             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1745           break;
1746
1747         case KSYM_Escape:
1748           if (game_status != GAME_MODE_MAIN)
1749             FadeSkipNextFadeIn();
1750
1751           if (game_status == GAME_MODE_TITLE)
1752             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1753           else if (game_status == GAME_MODE_LEVELS)
1754             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1755           else if (game_status == GAME_MODE_LEVELNR)
1756             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1757           else if (game_status == GAME_MODE_SETUP)
1758             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1759           else if (game_status == GAME_MODE_INFO)
1760             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1761           else if (game_status == GAME_MODE_SCORES)
1762             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1763           break;
1764
1765         case KSYM_Page_Up:
1766           if (game_status == GAME_MODE_LEVELS)
1767             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1768           else if (game_status == GAME_MODE_LEVELNR)
1769             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1770           else if (game_status == GAME_MODE_SETUP)
1771             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1772           else if (game_status == GAME_MODE_INFO)
1773             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1774           else if (game_status == GAME_MODE_SCORES)
1775             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1776           break;
1777
1778         case KSYM_Page_Down:
1779           if (game_status == GAME_MODE_LEVELS)
1780             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1781           else if (game_status == GAME_MODE_LEVELNR)
1782             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1783           else if (game_status == GAME_MODE_SETUP)
1784             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1785           else if (game_status == GAME_MODE_INFO)
1786             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1787           else if (game_status == GAME_MODE_SCORES)
1788             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1789           break;
1790
1791         default:
1792           break;
1793       }
1794       break;
1795
1796     case GAME_MODE_EDITOR:
1797       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1798         HandleLevelEditorKeyInput(key);
1799       break;
1800
1801     case GAME_MODE_PLAYING:
1802     {
1803       switch (key)
1804       {
1805         case KSYM_Escape:
1806           RequestQuitGame(setup.ask_on_escape);
1807           break;
1808
1809         default:
1810           break;
1811       }
1812       break;
1813     }
1814
1815     default:
1816       if (key == KSYM_Escape)
1817       {
1818         SetGameStatus(GAME_MODE_MAIN);
1819
1820         DrawMainMenu();
1821
1822         return;
1823       }
1824   }
1825
1826   HandleKeysDebug(key);
1827 }
1828
1829 void HandleNoEvent()
1830 {
1831   // if (button_status && game_status != GAME_MODE_PLAYING)
1832   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1833   {
1834     HandleButton(0, 0, button_status, -button_status);
1835   }
1836   else
1837   {
1838     HandleJoystick();
1839   }
1840
1841 #if defined(NETWORK_AVALIABLE)
1842   if (options.network)
1843     HandleNetworking();
1844 #endif
1845
1846   switch (game_status)
1847   {
1848     case GAME_MODE_MAIN:
1849       DrawPreviewLevelAnimation();
1850       break;
1851
1852     case GAME_MODE_EDITOR:
1853       HandleLevelEditorIdle();
1854       break;
1855
1856 #if defined(TARGET_SDL2)
1857     case GAME_MODE_PLAYING:
1858       HandleFollowFinger(-1, -1, -1);
1859       break;
1860 #endif
1861
1862     default:
1863       break;
1864   }
1865 }
1866
1867 static int HandleJoystickForAllPlayers()
1868 {
1869   int i;
1870   int result = 0;
1871
1872   for (i = 0; i < MAX_PLAYERS; i++)
1873   {
1874     byte joy_action = 0;
1875
1876     /*
1877     if (!setup.input[i].use_joystick)
1878       continue;
1879       */
1880
1881     joy_action = Joystick(i);
1882     result |= joy_action;
1883
1884     if (!setup.input[i].use_joystick)
1885       continue;
1886
1887     stored_player[i].action = joy_action;
1888   }
1889
1890   return result;
1891 }
1892
1893 void HandleJoystick()
1894 {
1895   int joystick  = HandleJoystickForAllPlayers();
1896   int keyboard  = key_joystick_mapping;
1897   int joy       = (joystick | keyboard);
1898   int left      = joy & JOY_LEFT;
1899   int right     = joy & JOY_RIGHT;
1900   int up        = joy & JOY_UP;
1901   int down      = joy & JOY_DOWN;
1902   int button    = joy & JOY_BUTTON;
1903   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1904   int dx        = (left ? -1    : right ? 1     : 0);
1905   int dy        = (up   ? -1    : down  ? 1     : 0);
1906
1907   switch (game_status)
1908   {
1909     case GAME_MODE_TITLE:
1910     case GAME_MODE_MAIN:
1911     case GAME_MODE_LEVELS:
1912     case GAME_MODE_LEVELNR:
1913     case GAME_MODE_SETUP:
1914     case GAME_MODE_INFO:
1915     {
1916       static unsigned int joystickmove_delay = 0;
1917
1918       if (joystick && !button &&
1919           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1920         newbutton = dx = dy = 0;
1921
1922       if (game_status == GAME_MODE_TITLE)
1923         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1924       else if (game_status == GAME_MODE_MAIN)
1925         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1926       else if (game_status == GAME_MODE_LEVELS)
1927         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1928       else if (game_status == GAME_MODE_LEVELNR)
1929         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1930       else if (game_status == GAME_MODE_SETUP)
1931         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1932       else if (game_status == GAME_MODE_INFO)
1933         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1934       break;
1935     }
1936
1937     case GAME_MODE_SCORES:
1938       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1939       break;
1940
1941     case GAME_MODE_PLAYING:
1942       if (tape.playing || keyboard)
1943         newbutton = ((joy & JOY_BUTTON) != 0);
1944
1945       if (newbutton && AllPlayersGone)
1946       {
1947         GameEnd();
1948
1949         return;
1950       }
1951
1952       break;
1953
1954     default:
1955       break;
1956   }
1957 }