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