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