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