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