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