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