changed event filter to not set as SDL event filter, but call it manually
[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     case GAME_MODE_PLAYING:
1235       HandleFollowFinger(mx, my, button);
1236
1237 #ifdef DEBUG
1238       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my) &&
1239           GetKeyModState() & KMOD_Control)
1240         DumpTileFromScreen(mx, my);
1241 #endif
1242
1243       break;
1244
1245     default:
1246       break;
1247   }
1248 }
1249
1250 static boolean is_string_suffix(char *string, char *suffix)
1251 {
1252   int string_len = strlen(string);
1253   int suffix_len = strlen(suffix);
1254
1255   if (suffix_len > string_len)
1256     return FALSE;
1257
1258   return (strEqual(&string[string_len - suffix_len], suffix));
1259 }
1260
1261 #define MAX_CHEAT_INPUT_LEN     32
1262
1263 static void HandleKeysSpecial(Key key)
1264 {
1265   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1266   char letter = getCharFromKey(key);
1267   int cheat_input_len = strlen(cheat_input);
1268   int i;
1269
1270   if (letter == 0)
1271     return;
1272
1273   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1274   {
1275     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1276       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1277
1278     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1279   }
1280
1281   cheat_input[cheat_input_len++] = letter;
1282   cheat_input[cheat_input_len] = '\0';
1283
1284 #if DEBUG_EVENTS_KEY
1285   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1286 #endif
1287
1288   if (game_status == GAME_MODE_MAIN)
1289   {
1290     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1291         is_string_suffix(cheat_input, ":ist"))
1292     {
1293       InsertSolutionTape();
1294     }
1295     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1296              is_string_suffix(cheat_input, ":rg"))
1297     {
1298       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1299       DrawMainMenu();
1300     }
1301     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1302              is_string_suffix(cheat_input, ":rs"))
1303     {
1304       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1305       DrawMainMenu();
1306     }
1307     else if (is_string_suffix(cheat_input, ":reload-music") ||
1308              is_string_suffix(cheat_input, ":rm"))
1309     {
1310       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1311       DrawMainMenu();
1312     }
1313     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1314              is_string_suffix(cheat_input, ":ra"))
1315     {
1316       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1317                           1 << ARTWORK_TYPE_SOUNDS |
1318                           1 << ARTWORK_TYPE_MUSIC);
1319       DrawMainMenu();
1320     }
1321     else if (is_string_suffix(cheat_input, ":dump-level") ||
1322              is_string_suffix(cheat_input, ":dl"))
1323     {
1324       DumpLevel(&level);
1325     }
1326     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1327              is_string_suffix(cheat_input, ":dt"))
1328     {
1329       DumpTape(&tape);
1330     }
1331     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1332              is_string_suffix(cheat_input, ":ft"))
1333     {
1334       /* fix single-player tapes that contain player input for more than one
1335          player (due to a bug in 3.3.1.2 and earlier versions), which results
1336          in playing levels with more than one player in multi-player mode,
1337          even though the tape was originally recorded in single-player mode */
1338
1339       /* remove player input actions for all players but the first one */
1340       for (i = 1; i < MAX_PLAYERS; i++)
1341         tape.player_participates[i] = FALSE;
1342
1343       tape.changed = TRUE;
1344     }
1345     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1346              is_string_suffix(cheat_input, ":snl"))
1347     {
1348       SaveNativeLevel(&level);
1349     }
1350   }
1351   else if (game_status == GAME_MODE_PLAYING)
1352   {
1353 #ifdef DEBUG
1354     if (is_string_suffix(cheat_input, ".q"))
1355       DEBUG_SetMaximumDynamite();
1356 #endif
1357   }
1358   else if (game_status == GAME_MODE_EDITOR)
1359   {
1360     if (is_string_suffix(cheat_input, ":dump-brush") ||
1361         is_string_suffix(cheat_input, ":DB"))
1362     {
1363       DumpBrush();
1364     }
1365     else if (is_string_suffix(cheat_input, ":DDB"))
1366     {
1367       DumpBrush_Small();
1368     }
1369   }
1370 }
1371
1372 void HandleKeysDebug(Key key)
1373 {
1374 #ifdef DEBUG
1375   int i;
1376
1377   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1378   {
1379     boolean mod_key_pressed = (GetKeyModState() != KMOD_None);
1380
1381     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1382     {
1383       if (key == setup.debug.frame_delay_key[i] &&
1384           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1385       {
1386         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1387                           setup.debug.frame_delay[i] : GAME_FRAME_DELAY);
1388
1389         if (!setup.debug.frame_delay_game_only)
1390           MenuFrameDelay = GameFrameDelay;
1391
1392         SetVideoFrameDelay(GameFrameDelay);
1393
1394         if (GameFrameDelay > ONE_SECOND_DELAY)
1395           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1396         else if (GameFrameDelay != 0)
1397           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1398                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1399                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1400         else
1401           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1402
1403         break;
1404       }
1405     }
1406   }
1407
1408   if (game_status == GAME_MODE_PLAYING)
1409   {
1410     if (key == KSYM_d)
1411     {
1412       options.debug = !options.debug;
1413
1414       Error(ERR_DEBUG, "debug mode %s",
1415             (options.debug ? "enabled" : "disabled"));
1416     }
1417     else if (key == KSYM_v)
1418     {
1419       Error(ERR_DEBUG, "currently using game engine version %d",
1420             game.engine_version);
1421     }
1422   }
1423 #endif
1424 }
1425
1426 void HandleKey(Key key, int key_status)
1427 {
1428   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1429   static boolean ignore_repeated_key = FALSE;
1430   static struct SetupKeyboardInfo ski;
1431   static struct SetupShortcutInfo ssi;
1432   static struct
1433   {
1434     Key *key_custom;
1435     Key *key_snap;
1436     Key key_default;
1437     byte action;
1438   } key_info[] =
1439   {
1440     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1441     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1442     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1443     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1444     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1445     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1446   };
1447   int joy = 0;
1448   int i;
1449
1450   if (game_status == GAME_MODE_PLAYING)
1451   {
1452     /* only needed for single-step tape recording mode */
1453     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1454     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1455     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1456     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1457     int pnr;
1458
1459     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1460     {
1461       byte key_action = 0;
1462
1463       if (setup.input[pnr].use_joystick)
1464         continue;
1465
1466       ski = setup.input[pnr].key;
1467
1468       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1469         if (key == *key_info[i].key_custom)
1470           key_action |= key_info[i].action;
1471
1472       /* use combined snap+direction keys for the first player only */
1473       if (pnr == 0)
1474       {
1475         ssi = setup.shortcut;
1476
1477         for (i = 0; i < NUM_DIRECTIONS; i++)
1478           if (key == *key_info[i].key_snap)
1479             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1480       }
1481
1482       /* clear delayed snap and drop actions in single step mode (see below) */
1483       if (tape.single_step)
1484       {
1485         if (clear_snap_button[pnr])
1486         {
1487           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1488           clear_snap_button[pnr] = FALSE;
1489         }
1490
1491         if (clear_drop_button[pnr])
1492         {
1493           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1494           clear_drop_button[pnr] = FALSE;
1495         }
1496       }
1497
1498       if (key_status == KEY_PRESSED)
1499         stored_player[pnr].action |= key_action;
1500       else
1501         stored_player[pnr].action &= ~key_action;
1502
1503       if (tape.single_step && tape.recording && tape.pausing)
1504       {
1505         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1506         {
1507           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1508
1509           /* if snap key already pressed, don't snap when releasing (below) */
1510           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1511             element_snapped[pnr] = TRUE;
1512
1513           /* if drop key already pressed, don't drop when releasing (below) */
1514           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1515             element_dropped[pnr] = TRUE;
1516         }
1517         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1518         {
1519           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1520               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1521           {
1522
1523             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1524                 getRedDiskReleaseFlag_SP() == 0)
1525               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1526
1527             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1528           }
1529         }
1530         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1531         {
1532           if (key_action & KEY_BUTTON_SNAP)
1533           {
1534             /* if snap key was released without moving (see above), snap now */
1535             if (!element_snapped[pnr])
1536             {
1537               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1538
1539               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1540
1541               /* clear delayed snap button on next event */
1542               clear_snap_button[pnr] = TRUE;
1543             }
1544
1545             element_snapped[pnr] = FALSE;
1546           }
1547
1548           if (key_action & KEY_BUTTON_DROP &&
1549               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1550           {
1551             /* if drop key was released without moving (see above), drop now */
1552             if (!element_dropped[pnr])
1553             {
1554               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1555
1556               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1557                   getRedDiskReleaseFlag_SP() != 0)
1558                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1559
1560               /* clear delayed drop button on next event */
1561               clear_drop_button[pnr] = TRUE;
1562             }
1563
1564             element_dropped[pnr] = FALSE;
1565           }
1566         }
1567       }
1568       else if (tape.recording && tape.pausing)
1569       {
1570         /* prevent key release events from un-pausing a paused game */
1571         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1572           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1573       }
1574     }
1575   }
1576   else
1577   {
1578     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1579       if (key == key_info[i].key_default)
1580         joy |= key_info[i].action;
1581   }
1582
1583   if (joy)
1584   {
1585     if (key_status == KEY_PRESSED)
1586       key_joystick_mapping |= joy;
1587     else
1588       key_joystick_mapping &= ~joy;
1589
1590     HandleJoystick();
1591   }
1592
1593   if (game_status != GAME_MODE_PLAYING)
1594     key_joystick_mapping = 0;
1595
1596   if (key_status == KEY_RELEASED)
1597   {
1598     // reset flag to ignore repeated "key pressed" events after key release
1599     ignore_repeated_key = FALSE;
1600
1601     return;
1602   }
1603
1604   if ((key == KSYM_F11 ||
1605        ((key == KSYM_Return ||
1606          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1607       video.fullscreen_available &&
1608       !ignore_repeated_key)
1609   {
1610     setup.fullscreen = !setup.fullscreen;
1611
1612     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1613
1614     if (game_status == GAME_MODE_SETUP)
1615       RedrawSetupScreenAfterFullscreenToggle();
1616
1617     // set flag to ignore repeated "key pressed" events
1618     ignore_repeated_key = TRUE;
1619
1620     return;
1621   }
1622
1623   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1624        key == KSYM_minus || key == KSYM_KP_Subtract ||
1625        key == KSYM_plus  || key == KSYM_KP_Add ||
1626        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1627       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1628       video.window_scaling_available &&
1629       !video.fullscreen_enabled)
1630   {
1631     if (key == KSYM_0 || key == KSYM_KP_0)
1632       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1633     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1634       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1635     else
1636       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1637
1638     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1639       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1640     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1641       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1642
1643     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1644
1645     if (game_status == GAME_MODE_SETUP)
1646       RedrawSetupScreenAfterFullscreenToggle();
1647
1648     return;
1649   }
1650
1651   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1652       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1653   {
1654     GameEnd();
1655
1656     return;
1657   }
1658
1659   if (game_status == GAME_MODE_MAIN &&
1660       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1661   {
1662     StartGameActions(options.network, setup.autorecord, level.random_seed);
1663
1664     return;
1665   }
1666
1667   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1668   {
1669     if (key == setup.shortcut.save_game)
1670       TapeQuickSave();
1671     else if (key == setup.shortcut.load_game)
1672       TapeQuickLoad();
1673     else if (key == setup.shortcut.toggle_pause)
1674       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1675
1676     HandleTapeButtonKeys(key);
1677     HandleSoundButtonKeys(key);
1678   }
1679
1680   if (game_status == GAME_MODE_PLAYING && !network_playing)
1681   {
1682     int centered_player_nr_next = -999;
1683
1684     if (key == setup.shortcut.focus_player_all)
1685       centered_player_nr_next = -1;
1686     else
1687       for (i = 0; i < MAX_PLAYERS; i++)
1688         if (key == setup.shortcut.focus_player[i])
1689           centered_player_nr_next = i;
1690
1691     if (centered_player_nr_next != -999)
1692     {
1693       game.centered_player_nr_next = centered_player_nr_next;
1694       game.set_centered_player = TRUE;
1695
1696       if (tape.recording)
1697       {
1698         tape.centered_player_nr_next = game.centered_player_nr_next;
1699         tape.set_centered_player = TRUE;
1700       }
1701     }
1702   }
1703
1704   HandleKeysSpecial(key);
1705
1706   if (HandleGadgetsKeyInput(key))
1707   {
1708     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1709       key = KSYM_UNDEFINED;
1710   }
1711
1712   switch (game_status)
1713   {
1714     case GAME_MODE_PSEUDO_TYPENAME:
1715       HandleTypeName(0, key);
1716       break;
1717
1718     case GAME_MODE_TITLE:
1719     case GAME_MODE_MAIN:
1720     case GAME_MODE_LEVELS:
1721     case GAME_MODE_LEVELNR:
1722     case GAME_MODE_SETUP:
1723     case GAME_MODE_INFO:
1724     case GAME_MODE_SCORES:
1725       switch (key)
1726       {
1727         case KSYM_space:
1728         case KSYM_Return:
1729           if (game_status == GAME_MODE_TITLE)
1730             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1731           else if (game_status == GAME_MODE_MAIN)
1732             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1733           else if (game_status == GAME_MODE_LEVELS)
1734             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1735           else if (game_status == GAME_MODE_LEVELNR)
1736             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1737           else if (game_status == GAME_MODE_SETUP)
1738             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1739           else if (game_status == GAME_MODE_INFO)
1740             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1741           else if (game_status == GAME_MODE_SCORES)
1742             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1743           break;
1744
1745         case KSYM_Escape:
1746           if (game_status != GAME_MODE_MAIN)
1747             FadeSkipNextFadeIn();
1748
1749           if (game_status == GAME_MODE_TITLE)
1750             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1751           else if (game_status == GAME_MODE_LEVELS)
1752             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1753           else if (game_status == GAME_MODE_LEVELNR)
1754             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1755           else if (game_status == GAME_MODE_SETUP)
1756             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1757           else if (game_status == GAME_MODE_INFO)
1758             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1759           else if (game_status == GAME_MODE_SCORES)
1760             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1761           break;
1762
1763         case KSYM_Page_Up:
1764           if (game_status == GAME_MODE_LEVELS)
1765             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1766           else if (game_status == GAME_MODE_LEVELNR)
1767             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1768           else if (game_status == GAME_MODE_SETUP)
1769             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1770           else if (game_status == GAME_MODE_INFO)
1771             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1772           else if (game_status == GAME_MODE_SCORES)
1773             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1774           break;
1775
1776         case KSYM_Page_Down:
1777           if (game_status == GAME_MODE_LEVELS)
1778             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1779           else if (game_status == GAME_MODE_LEVELNR)
1780             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1781           else if (game_status == GAME_MODE_SETUP)
1782             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1783           else if (game_status == GAME_MODE_INFO)
1784             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1785           else if (game_status == GAME_MODE_SCORES)
1786             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1787           break;
1788
1789         default:
1790           break;
1791       }
1792       break;
1793
1794     case GAME_MODE_EDITOR:
1795       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1796         HandleLevelEditorKeyInput(key);
1797       break;
1798
1799     case GAME_MODE_PLAYING:
1800     {
1801       switch (key)
1802       {
1803         case KSYM_Escape:
1804           RequestQuitGame(setup.ask_on_escape);
1805           break;
1806
1807         default:
1808           break;
1809       }
1810       break;
1811     }
1812
1813     default:
1814       if (key == KSYM_Escape)
1815       {
1816         SetGameStatus(GAME_MODE_MAIN);
1817
1818         DrawMainMenu();
1819
1820         return;
1821       }
1822   }
1823
1824   HandleKeysDebug(key);
1825 }
1826
1827 void HandleNoEvent()
1828 {
1829   // if (button_status && game_status != GAME_MODE_PLAYING)
1830   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1831   {
1832     HandleButton(0, 0, button_status, -button_status);
1833   }
1834   else
1835   {
1836     HandleJoystick();
1837   }
1838
1839 #if defined(NETWORK_AVALIABLE)
1840   if (options.network)
1841     HandleNetworking();
1842 #endif
1843
1844   switch (game_status)
1845   {
1846     case GAME_MODE_MAIN:
1847       DrawPreviewLevelAnimation();
1848       break;
1849
1850     case GAME_MODE_EDITOR:
1851       HandleLevelEditorIdle();
1852       break;
1853
1854     case GAME_MODE_PLAYING:
1855       HandleFollowFinger(-1, -1, -1);
1856       break;
1857
1858     default:
1859       break;
1860   }
1861 }
1862
1863 static int HandleJoystickForAllPlayers()
1864 {
1865   int i;
1866   int result = 0;
1867
1868   for (i = 0; i < MAX_PLAYERS; i++)
1869   {
1870     byte joy_action = 0;
1871
1872     /*
1873     if (!setup.input[i].use_joystick)
1874       continue;
1875       */
1876
1877     joy_action = Joystick(i);
1878     result |= joy_action;
1879
1880     if (!setup.input[i].use_joystick)
1881       continue;
1882
1883     stored_player[i].action = joy_action;
1884   }
1885
1886   return result;
1887 }
1888
1889 void HandleJoystick()
1890 {
1891   int joystick  = HandleJoystickForAllPlayers();
1892   int keyboard  = key_joystick_mapping;
1893   int joy       = (joystick | keyboard);
1894   int left      = joy & JOY_LEFT;
1895   int right     = joy & JOY_RIGHT;
1896   int up        = joy & JOY_UP;
1897   int down      = joy & JOY_DOWN;
1898   int button    = joy & JOY_BUTTON;
1899   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1900   int dx        = (left ? -1    : right ? 1     : 0);
1901   int dy        = (up   ? -1    : down  ? 1     : 0);
1902
1903   switch (game_status)
1904   {
1905     case GAME_MODE_TITLE:
1906     case GAME_MODE_MAIN:
1907     case GAME_MODE_LEVELS:
1908     case GAME_MODE_LEVELNR:
1909     case GAME_MODE_SETUP:
1910     case GAME_MODE_INFO:
1911     {
1912       static unsigned int joystickmove_delay = 0;
1913
1914       if (joystick && !button &&
1915           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1916         newbutton = dx = dy = 0;
1917
1918       if (game_status == GAME_MODE_TITLE)
1919         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1920       else if (game_status == GAME_MODE_MAIN)
1921         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1922       else if (game_status == GAME_MODE_LEVELS)
1923         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1924       else if (game_status == GAME_MODE_LEVELNR)
1925         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1926       else if (game_status == GAME_MODE_SETUP)
1927         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1928       else if (game_status == GAME_MODE_INFO)
1929         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1930       break;
1931     }
1932
1933     case GAME_MODE_SCORES:
1934       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1935       break;
1936
1937     case GAME_MODE_PLAYING:
1938       if (tape.playing || keyboard)
1939         newbutton = ((joy & JOY_BUTTON) != 0);
1940
1941       if (newbutton && AllPlayersGone)
1942       {
1943         GameEnd();
1944
1945         return;
1946       }
1947
1948       break;
1949
1950     default:
1951       break;
1952   }
1953 }