added new game control input method 'follow finger' for touch devices
[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 #if defined(HAS_SCREEN_KEYBOARD)
416   if (video.shifted_up)
417     event->y += video.shifted_up_pos;
418 #endif
419
420   motion_status = FALSE;
421
422   if (event->type == EVENT_BUTTONPRESS)
423     button_status = event->button;
424   else
425     button_status = MB_RELEASED;
426
427   HandleButton(event->x, event->y, button_status, event->button);
428 }
429
430 void HandleMotionEvent(MotionEvent *event)
431 {
432   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
433     return;
434
435   motion_status = TRUE;
436
437 #if DEBUG_EVENTS_MOTION
438   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
439         button_status, event->x, event->y);
440 #endif
441
442   HandleButton(event->x, event->y, button_status, button_status);
443 }
444
445 #if defined(TARGET_SDL2)
446
447 void HandleWheelEvent(WheelEvent *event)
448 {
449   int button_nr;
450
451 #if DEBUG_EVENTS_WHEEL
452 #if 1
453   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
454         event->which, event->x, event->y);
455 #else
456   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
457   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
458         event->which, event->x, event->y,
459         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
460          "SDL_MOUSEWHEEL_FLIPPED"));
461 #endif
462 #endif
463
464   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
465                event->x > 0 ? MB_WHEEL_RIGHT :
466                event->y < 0 ? MB_WHEEL_DOWN :
467                event->y > 0 ? MB_WHEEL_UP : 0);
468
469 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
470   // accelerated mouse wheel available on Mac and Windows
471   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
472 #else
473   // no accelerated mouse wheel available on Unix/Linux
474   wheel_steps = DEFAULT_WHEEL_STEPS;
475 #endif
476
477   motion_status = FALSE;
478
479   button_status = button_nr;
480   HandleButton(0, 0, button_status, -button_nr);
481
482   button_status = MB_RELEASED;
483   HandleButton(0, 0, button_status, -button_nr);
484 }
485
486 void HandleWindowEvent(WindowEvent *event)
487 {
488 #if DEBUG_EVENTS_WINDOW
489   int subtype = event->event;
490
491   char *event_name =
492     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
493      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
494      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
495      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
496      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
497      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
498      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
499      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
500      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
501      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
502      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
503      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
504      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
505      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
506      "(UNKNOWN)");
507
508   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
509         event_name, event->data1, event->data2);
510 #endif
511
512 #if 0
513   // (not needed, as the screen gets redrawn every 20 ms anyway)
514   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
515       event->event == SDL_WINDOWEVENT_RESIZED ||
516       event->event == SDL_WINDOWEVENT_EXPOSED)
517     SDLRedrawWindow();
518 #endif
519
520   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
521   {
522     int new_window_width  = event->data1;
523     int new_window_height = event->data2;
524
525     // if window size has changed after resizing, calculate new scaling factor
526     if (new_window_width  != video.window_width ||
527         new_window_height != video.window_height)
528     {
529       int new_xpercent = (100 * new_window_width  / video.width);
530       int new_ypercent = (100 * new_window_height / video.height);
531
532       // (extreme window scaling allowed, but cannot be saved permanently)
533       video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
534       setup.window_scaling_percent =
535         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
536             MAX_WINDOW_SCALING_PERCENT);
537
538       video.window_width  = new_window_width;
539       video.window_height = new_window_height;
540
541       if (game_status == GAME_MODE_SETUP)
542         RedrawSetupScreenAfterFullscreenToggle();
543
544       SetWindowTitle();
545     }
546   }
547 }
548
549 #define NUM_TOUCH_FINGERS               3
550
551 static struct
552 {
553   boolean touched;
554   SDL_FingerID finger_id;
555   int counter;
556   Key key;
557 } touch_info[NUM_TOUCH_FINGERS];
558
559 void HandleFingerEvent(FingerEvent *event)
560 {
561   static Key motion_key_x = KSYM_UNDEFINED;
562   static Key motion_key_y = KSYM_UNDEFINED;
563   static Key button_key = KSYM_UNDEFINED;
564   static float motion_x1, motion_y1;
565   static float button_x1, button_y1;
566   static SDL_FingerID motion_id = -1;
567   static SDL_FingerID button_id = -1;
568   int move_trigger_distance_percent = 2;   // percent of touchpad width/height
569   int drop_trigger_distance_percent = 5;   // percent of touchpad width/height
570   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
571   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
572   float event_x = event->x;
573   float event_y = event->y;
574
575 #if DEBUG_EVENTS_FINGER
576   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
577         event->type == EVENT_FINGERPRESS ? "pressed" :
578         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
579         event->touchId,
580         event->fingerId,
581         event->x, event->y,
582         event->dx, event->dy,
583         event->pressure);
584 #endif
585
586   if (game_status != GAME_MODE_PLAYING)
587     return;
588
589   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
590     return;
591
592   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
593   {
594     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
595                       KEY_PRESSED);
596     Key key = (event->x < 1.0 / 3.0 ?
597                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
598                 setup.input[0].key.drop) :
599                event->x > 2.0 / 3.0 ?
600                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
601                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
602                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
603                 setup.input[0].key.right) :
604                KSYM_UNDEFINED);
605     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
606                              "KEY_PRESSED");
607     int i;
608
609     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
610           getKeyNameFromKey(key), key_status_name, event->fingerId);
611
612     // check if we already know this touch event's finger id
613     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
614     {
615       if (touch_info[i].touched &&
616           touch_info[i].finger_id == event->fingerId)
617       {
618         // Error(ERR_DEBUG, "MARK 1: %d", i);
619
620         break;
621       }
622     }
623
624     if (i >= NUM_TOUCH_FINGERS)
625     {
626       if (key_status == KEY_PRESSED)
627       {
628         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
629
630         // unknown finger id -- get new, empty slot, if available
631         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
632         {
633           if (touch_info[i].counter < oldest_counter)
634           {
635             oldest_pos = i;
636             oldest_counter = touch_info[i].counter;
637
638             // Error(ERR_DEBUG, "MARK 2: %d", i);
639           }
640
641           if (!touch_info[i].touched)
642           {
643             // Error(ERR_DEBUG, "MARK 3: %d", i);
644
645             break;
646           }
647         }
648
649         if (i >= NUM_TOUCH_FINGERS)
650         {
651           // all slots allocated -- use oldest slot
652           i = oldest_pos;
653
654           // Error(ERR_DEBUG, "MARK 4: %d", i);
655         }
656       }
657       else
658       {
659         // release of previously unknown key (should not happen)
660
661         if (key != KSYM_UNDEFINED)
662         {
663           HandleKey(key, KEY_RELEASED);
664
665           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
666                 getKeyNameFromKey(key), "KEY_RELEASED", i);
667         }
668       }
669     }
670
671     if (i < NUM_TOUCH_FINGERS)
672     {
673       if (key_status == KEY_PRESSED)
674       {
675         if (touch_info[i].key != key)
676         {
677           if (touch_info[i].key != KSYM_UNDEFINED)
678           {
679             HandleKey(touch_info[i].key, KEY_RELEASED);
680
681             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
682                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
683           }
684
685           if (key != KSYM_UNDEFINED)
686           {
687             HandleKey(key, KEY_PRESSED);
688
689             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
690                   getKeyNameFromKey(key), "KEY_PRESSED", i);
691           }
692         }
693
694         touch_info[i].touched = TRUE;
695         touch_info[i].finger_id = event->fingerId;
696         touch_info[i].counter = Counter();
697         touch_info[i].key = key;
698       }
699       else
700       {
701         if (touch_info[i].key != KSYM_UNDEFINED)
702         {
703           HandleKey(touch_info[i].key, KEY_RELEASED);
704
705           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
706                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
707         }
708
709         touch_info[i].touched = FALSE;
710         touch_info[i].finger_id = 0;
711         touch_info[i].counter = 0;
712         touch_info[i].key = 0;
713       }
714     }
715
716     return;
717   }
718
719   // use touch direction control
720
721   if (event->type == EVENT_FINGERPRESS)
722   {
723     if (event_x > 1.0 / 3.0)
724     {
725       // motion area
726
727       motion_id = event->fingerId;
728
729       motion_x1 = event_x;
730       motion_y1 = event_y;
731
732       motion_key_x = KSYM_UNDEFINED;
733       motion_key_y = KSYM_UNDEFINED;
734
735       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
736     }
737     else
738     {
739       // button area
740
741       button_id = event->fingerId;
742
743       button_x1 = event_x;
744       button_y1 = event_y;
745
746       button_key = setup.input[0].key.snap;
747
748       HandleKey(button_key, KEY_PRESSED);
749
750       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
751     }
752   }
753   else if (event->type == EVENT_FINGERRELEASE)
754   {
755     if (event->fingerId == motion_id)
756     {
757       motion_id = -1;
758
759       if (motion_key_x != KSYM_UNDEFINED)
760         HandleKey(motion_key_x, KEY_RELEASED);
761       if (motion_key_y != KSYM_UNDEFINED)
762         HandleKey(motion_key_y, KEY_RELEASED);
763
764       motion_key_x = KSYM_UNDEFINED;
765       motion_key_y = KSYM_UNDEFINED;
766
767       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
768     }
769     else if (event->fingerId == button_id)
770     {
771       button_id = -1;
772
773       if (button_key != KSYM_UNDEFINED)
774         HandleKey(button_key, KEY_RELEASED);
775
776       button_key = KSYM_UNDEFINED;
777
778       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
779     }
780   }
781   else if (event->type == EVENT_FINGERMOTION)
782   {
783     if (event->fingerId == motion_id)
784     {
785       float distance_x = ABS(event_x - motion_x1);
786       float distance_y = ABS(event_y - motion_y1);
787       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
788                               event_x > motion_x1 ? setup.input[0].key.right :
789                               KSYM_UNDEFINED);
790       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
791                               event_y > motion_y1 ? setup.input[0].key.down :
792                               KSYM_UNDEFINED);
793
794       if (distance_x < move_trigger_distance / 2 ||
795           distance_x < distance_y)
796         new_motion_key_x = KSYM_UNDEFINED;
797
798       if (distance_y < move_trigger_distance / 2 ||
799           distance_y < distance_x)
800         new_motion_key_y = KSYM_UNDEFINED;
801
802       if (distance_x > move_trigger_distance ||
803           distance_y > move_trigger_distance)
804       {
805         if (new_motion_key_x != motion_key_x)
806         {
807           if (motion_key_x != KSYM_UNDEFINED)
808             HandleKey(motion_key_x, KEY_RELEASED);
809           if (new_motion_key_x != KSYM_UNDEFINED)
810             HandleKey(new_motion_key_x, KEY_PRESSED);
811         }
812
813         if (new_motion_key_y != motion_key_y)
814         {
815           if (motion_key_y != KSYM_UNDEFINED)
816             HandleKey(motion_key_y, KEY_RELEASED);
817           if (new_motion_key_y != KSYM_UNDEFINED)
818             HandleKey(new_motion_key_y, KEY_PRESSED);
819         }
820
821         motion_x1 = event_x;
822         motion_y1 = event_y;
823
824         motion_key_x = new_motion_key_x;
825         motion_key_y = new_motion_key_y;
826
827         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
828       }
829     }
830     else if (event->fingerId == button_id)
831     {
832       float distance_x = ABS(event_x - button_x1);
833       float distance_y = ABS(event_y - button_y1);
834
835       if (distance_x < drop_trigger_distance / 2 &&
836           distance_y > drop_trigger_distance)
837       {
838         if (button_key == setup.input[0].key.snap)
839           HandleKey(button_key, KEY_RELEASED);
840
841         button_x1 = event_x;
842         button_y1 = event_y;
843
844         button_key = setup.input[0].key.drop;
845
846         HandleKey(button_key, KEY_PRESSED);
847
848         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
849       }
850     }
851   }
852 }
853
854 static void HandleFollowFinger(int mx, int my, int button)
855 {
856   static int old_mx = 0, old_my = 0;
857   static Key motion_key_x = KSYM_UNDEFINED;
858   static Key motion_key_y = KSYM_UNDEFINED;
859   static boolean started_on_player = FALSE;
860   static boolean player_is_dropping = FALSE;
861   static int player_drop_count = 0;
862   static int last_player_x = -1;
863   static int last_player_y = -1;
864
865   if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
866     return;
867
868   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
869   {
870     touch_info[0].touched = TRUE;
871     touch_info[0].key = 0;
872
873     old_mx = mx;
874     old_my = my;
875
876     if (!motion_status)
877     {
878       started_on_player = FALSE;
879       player_is_dropping = FALSE;
880       player_drop_count = 0;
881       last_player_x = -1;
882       last_player_y = -1;
883
884       motion_key_x = KSYM_UNDEFINED;
885       motion_key_y = KSYM_UNDEFINED;
886
887       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
888     }
889   }
890   else if (button == MB_RELEASED && touch_info[0].touched)
891   {
892     touch_info[0].touched = FALSE;
893     touch_info[0].key = 0;
894
895     old_mx = 0;
896     old_my = 0;
897
898     if (motion_key_x != KSYM_UNDEFINED)
899       HandleKey(motion_key_x, KEY_RELEASED);
900     if (motion_key_y != KSYM_UNDEFINED)
901       HandleKey(motion_key_y, KEY_RELEASED);
902
903     if (started_on_player)
904     {
905       if (player_is_dropping)
906       {
907         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
908
909         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
910       }
911       else
912       {
913         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
914
915         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
916       }
917     }
918
919     motion_key_x = KSYM_UNDEFINED;
920     motion_key_y = KSYM_UNDEFINED;
921
922     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
923   }
924
925   if (touch_info[0].touched)
926   {
927     int src_x = local_player->jx;
928     int src_y = local_player->jy;
929     int dst_x = getLevelFromScreenX(old_mx);
930     int dst_y = getLevelFromScreenY(old_my);
931     int dx = dst_x - src_x;
932     int dy = dst_y - src_y;
933     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
934                             dx > 0 ? setup.input[0].key.right :
935                             KSYM_UNDEFINED);
936     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
937                             dy > 0 ? setup.input[0].key.down :
938                             KSYM_UNDEFINED);
939
940     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
941         (last_player_x != local_player->jx ||
942          last_player_y != local_player->jy))
943     {
944       // in case of asymmetric diagonal movement, use "preferred" direction
945
946       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
947
948       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
949         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
950       else
951         local_player->last_move_dir = last_move_dir;
952
953       // (required to prevent accidentally forcing direction for next movement)
954       last_player_x = local_player->jx;
955       last_player_y = local_player->jy;
956     }
957
958     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
959     {
960       started_on_player = TRUE;
961       player_drop_count = getPlayerInventorySize(0);
962       player_is_dropping = (player_drop_count > 0);
963
964       if (player_is_dropping)
965       {
966         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
967
968         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
969       }
970       else
971       {
972         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
973
974         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
975       }
976     }
977     else if (dx != 0 || dy != 0)
978     {
979       if (player_is_dropping &&
980           player_drop_count == getPlayerInventorySize(0))
981       {
982         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
983
984         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
985         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
986
987         player_is_dropping = FALSE;
988       }
989     }
990
991     if (new_motion_key_x != motion_key_x)
992     {
993       Error(ERR_DEBUG, "---------- %s %s ----------",
994             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
995             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
996
997       if (motion_key_x != KSYM_UNDEFINED)
998         HandleKey(motion_key_x, KEY_RELEASED);
999       if (new_motion_key_x != KSYM_UNDEFINED)
1000         HandleKey(new_motion_key_x, KEY_PRESSED);
1001     }
1002
1003     if (new_motion_key_y != motion_key_y)
1004     {
1005       Error(ERR_DEBUG, "---------- %s %s ----------",
1006             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1007             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1008
1009       if (motion_key_y != KSYM_UNDEFINED)
1010         HandleKey(motion_key_y, KEY_RELEASED);
1011       if (new_motion_key_y != KSYM_UNDEFINED)
1012         HandleKey(new_motion_key_y, KEY_PRESSED);
1013     }
1014
1015     motion_key_x = new_motion_key_x;
1016     motion_key_y = new_motion_key_y;
1017   }
1018 }
1019
1020 static boolean checkTextInputKeyModState()
1021 {
1022   // when playing, only handle raw key events and ignore text input
1023   if (game_status == GAME_MODE_PLAYING)
1024     return FALSE;
1025
1026   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1027 }
1028
1029 void HandleTextEvent(TextEvent *event)
1030 {
1031   char *text = event->text;
1032   Key key = getKeyFromKeyName(text);
1033
1034 #if DEBUG_EVENTS_TEXT
1035   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1036         text,
1037         strlen(text),
1038         text[0], (int)(text[0]),
1039         key,
1040         getKeyNameFromKey(key),
1041         GetKeyModState());
1042 #endif
1043
1044 #if !defined(HAS_SCREEN_KEYBOARD)
1045   // non-mobile devices: only handle key input with modifier keys pressed here
1046   // (every other key input is handled directly as physical key input event)
1047   if (!checkTextInputKeyModState())
1048     return;
1049 #endif
1050
1051   // process text input as "classic" (with uppercase etc.) key input event
1052   HandleKey(key, KEY_PRESSED);
1053   HandleKey(key, KEY_RELEASED);
1054 }
1055
1056 void HandlePauseResumeEvent(PauseResumeEvent *event)
1057 {
1058   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1059   {
1060     Mix_PauseMusic();
1061   }
1062   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1063   {
1064     Mix_ResumeMusic();
1065   }
1066 }
1067
1068 #endif
1069
1070 void HandleKeyEvent(KeyEvent *event)
1071 {
1072   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1073   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1074   Key key = GetEventKey(event, with_modifiers);
1075   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1076
1077 #if DEBUG_EVENTS_KEY
1078   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1079         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1080         event->keysym.scancode,
1081         event->keysym.sym,
1082         keymod,
1083         GetKeyModState(),
1084         key,
1085         getKeyNameFromKey(key));
1086 #endif
1087
1088 #if defined(PLATFORM_ANDROID)
1089   // always map the "back" button to the "escape" key on Android devices
1090   if (key == KSYM_Back)
1091     key = KSYM_Escape;
1092 #endif
1093
1094   HandleKeyModState(keymod, key_status);
1095
1096 #if defined(TARGET_SDL2)
1097   // only handle raw key input without text modifier keys pressed
1098   if (!checkTextInputKeyModState())
1099     HandleKey(key, key_status);
1100 #else
1101   HandleKey(key, key_status);
1102 #endif
1103 }
1104
1105 void HandleFocusEvent(FocusChangeEvent *event)
1106 {
1107   static int old_joystick_status = -1;
1108
1109   if (event->type == EVENT_FOCUSOUT)
1110   {
1111     KeyboardAutoRepeatOn();
1112     old_joystick_status = joystick.status;
1113     joystick.status = JOYSTICK_NOT_AVAILABLE;
1114
1115     ClearPlayerAction();
1116   }
1117   else if (event->type == EVENT_FOCUSIN)
1118   {
1119     /* When there are two Rocks'n'Diamonds windows which overlap and
1120        the player moves the pointer from one game window to the other,
1121        a 'FocusOut' event is generated for the window the pointer is
1122        leaving and a 'FocusIn' event is generated for the window the
1123        pointer is entering. In some cases, it can happen that the
1124        'FocusIn' event is handled by the one game process before the
1125        'FocusOut' event by the other game process. In this case the
1126        X11 environment would end up with activated keyboard auto repeat,
1127        because unfortunately this is a global setting and not (which
1128        would be far better) set for each X11 window individually.
1129        The effect would be keyboard auto repeat while playing the game
1130        (game_status == GAME_MODE_PLAYING), which is not desired.
1131        To avoid this special case, we just wait 1/10 second before
1132        processing the 'FocusIn' event.
1133     */
1134
1135     if (game_status == GAME_MODE_PLAYING)
1136     {
1137       Delay(100);
1138       KeyboardAutoRepeatOffUnlessAutoplay();
1139     }
1140
1141     if (old_joystick_status != -1)
1142       joystick.status = old_joystick_status;
1143   }
1144 }
1145
1146 void HandleClientMessageEvent(ClientMessageEvent *event)
1147 {
1148   if (CheckCloseWindowEvent(event))
1149     CloseAllAndExit(0);
1150 }
1151
1152 void HandleWindowManagerEvent(Event *event)
1153 {
1154 #if defined(TARGET_SDL)
1155   SDLHandleWindowManagerEvent(event);
1156 #endif
1157 }
1158
1159 void HandleButton(int mx, int my, int button, int button_nr)
1160 {
1161   static int old_mx = 0, old_my = 0;
1162   boolean button_hold = FALSE;
1163
1164   if (button_nr < 0)
1165   {
1166     mx = old_mx;
1167     my = old_my;
1168     button_nr = -button_nr;
1169     button_hold = TRUE;
1170   }
1171   else
1172   {
1173     old_mx = mx;
1174     old_my = my;
1175   }
1176
1177 #if defined(PLATFORM_ANDROID)
1178   // when playing, only handle gadgets when using "follow finger" controls
1179   boolean handle_gadgets =
1180     (game_status != GAME_MODE_PLAYING ||
1181      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1182
1183   if (handle_gadgets &&
1184       HandleGadgets(mx, my, button))
1185   {
1186     /* do not handle this button event anymore */
1187     mx = my = -32;      /* force mouse event to be outside screen tiles */
1188   }
1189 #else
1190   if (HandleGadgets(mx, my, button))
1191   {
1192     /* do not handle this button event anymore */
1193     mx = my = -32;      /* force mouse event to be outside screen tiles */
1194   }
1195 #endif
1196
1197   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1198     return;
1199
1200   /* do not use scroll wheel button events for anything other than gadgets */
1201   if (IS_WHEEL_BUTTON(button_nr))
1202     return;
1203
1204   switch (game_status)
1205   {
1206     case GAME_MODE_TITLE:
1207       HandleTitleScreen(mx, my, 0, 0, button);
1208       break;
1209
1210     case GAME_MODE_MAIN:
1211       HandleMainMenu(mx, my, 0, 0, button);
1212       break;
1213
1214     case GAME_MODE_PSEUDO_TYPENAME:
1215       HandleTypeName(0, KSYM_Return);
1216       break;
1217
1218     case GAME_MODE_LEVELS:
1219       HandleChooseLevelSet(mx, my, 0, 0, button);
1220       break;
1221
1222     case GAME_MODE_LEVELNR:
1223       HandleChooseLevelNr(mx, my, 0, 0, button);
1224       break;
1225
1226     case GAME_MODE_SCORES:
1227       HandleHallOfFame(0, 0, 0, 0, button);
1228       break;
1229
1230     case GAME_MODE_EDITOR:
1231       HandleLevelEditorIdle();
1232       break;
1233
1234     case GAME_MODE_INFO:
1235       HandleInfoScreen(mx, my, 0, 0, button);
1236       break;
1237
1238     case GAME_MODE_SETUP:
1239       HandleSetupScreen(mx, my, 0, 0, button);
1240       break;
1241
1242     case GAME_MODE_PLAYING:
1243       HandleFollowFinger(mx, my, button);
1244
1245 #ifdef DEBUG
1246       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my) &&
1247           GetKeyModState() & KMOD_Control)
1248         DumpTileFromScreen(mx, my);
1249 #endif
1250
1251       break;
1252
1253     default:
1254       break;
1255   }
1256 }
1257
1258 static boolean is_string_suffix(char *string, char *suffix)
1259 {
1260   int string_len = strlen(string);
1261   int suffix_len = strlen(suffix);
1262
1263   if (suffix_len > string_len)
1264     return FALSE;
1265
1266   return (strEqual(&string[string_len - suffix_len], suffix));
1267 }
1268
1269 #define MAX_CHEAT_INPUT_LEN     32
1270
1271 static void HandleKeysSpecial(Key key)
1272 {
1273   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1274   char letter = getCharFromKey(key);
1275   int cheat_input_len = strlen(cheat_input);
1276   int i;
1277
1278   if (letter == 0)
1279     return;
1280
1281   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1282   {
1283     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1284       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1285
1286     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1287   }
1288
1289   cheat_input[cheat_input_len++] = letter;
1290   cheat_input[cheat_input_len] = '\0';
1291
1292 #if DEBUG_EVENTS_KEY
1293   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1294 #endif
1295
1296   if (game_status == GAME_MODE_MAIN)
1297   {
1298     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1299         is_string_suffix(cheat_input, ":ist"))
1300     {
1301       InsertSolutionTape();
1302     }
1303     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1304              is_string_suffix(cheat_input, ":rg"))
1305     {
1306       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1307       DrawMainMenu();
1308     }
1309     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1310              is_string_suffix(cheat_input, ":rs"))
1311     {
1312       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1313       DrawMainMenu();
1314     }
1315     else if (is_string_suffix(cheat_input, ":reload-music") ||
1316              is_string_suffix(cheat_input, ":rm"))
1317     {
1318       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1319       DrawMainMenu();
1320     }
1321     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1322              is_string_suffix(cheat_input, ":ra"))
1323     {
1324       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1325                           1 << ARTWORK_TYPE_SOUNDS |
1326                           1 << ARTWORK_TYPE_MUSIC);
1327       DrawMainMenu();
1328     }
1329     else if (is_string_suffix(cheat_input, ":dump-level") ||
1330              is_string_suffix(cheat_input, ":dl"))
1331     {
1332       DumpLevel(&level);
1333     }
1334     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1335              is_string_suffix(cheat_input, ":dt"))
1336     {
1337       DumpTape(&tape);
1338     }
1339     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1340              is_string_suffix(cheat_input, ":ft"))
1341     {
1342       /* fix single-player tapes that contain player input for more than one
1343          player (due to a bug in 3.3.1.2 and earlier versions), which results
1344          in playing levels with more than one player in multi-player mode,
1345          even though the tape was originally recorded in single-player mode */
1346
1347       /* remove player input actions for all players but the first one */
1348       for (i = 1; i < MAX_PLAYERS; i++)
1349         tape.player_participates[i] = FALSE;
1350
1351       tape.changed = TRUE;
1352     }
1353     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1354              is_string_suffix(cheat_input, ":snl"))
1355     {
1356       SaveNativeLevel(&level);
1357     }
1358   }
1359   else if (game_status == GAME_MODE_PLAYING)
1360   {
1361 #ifdef DEBUG
1362     if (is_string_suffix(cheat_input, ".q"))
1363       DEBUG_SetMaximumDynamite();
1364 #endif
1365   }
1366   else if (game_status == GAME_MODE_EDITOR)
1367   {
1368     if (is_string_suffix(cheat_input, ":dump-brush") ||
1369         is_string_suffix(cheat_input, ":DB"))
1370     {
1371       DumpBrush();
1372     }
1373     else if (is_string_suffix(cheat_input, ":DDB"))
1374     {
1375       DumpBrush_Small();
1376     }
1377   }
1378 }
1379
1380 void HandleKeysDebug(Key key)
1381 {
1382 #ifdef DEBUG
1383   int i;
1384
1385   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1386   {
1387     boolean mod_key_pressed = (GetKeyModState() != KMOD_None);
1388
1389     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1390     {
1391       if (key == setup.debug.frame_delay_key[i] &&
1392           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1393       {
1394         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1395                           setup.debug.frame_delay[i] : GAME_FRAME_DELAY);
1396
1397         if (!setup.debug.frame_delay_game_only)
1398           MenuFrameDelay = GameFrameDelay;
1399
1400         SetVideoFrameDelay(GameFrameDelay);
1401
1402         if (GameFrameDelay > ONE_SECOND_DELAY)
1403           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1404         else if (GameFrameDelay != 0)
1405           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1406                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1407                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1408         else
1409           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1410
1411         break;
1412       }
1413     }
1414   }
1415
1416   if (game_status == GAME_MODE_PLAYING)
1417   {
1418     if (key == KSYM_d)
1419     {
1420       options.debug = !options.debug;
1421
1422       Error(ERR_DEBUG, "debug mode %s",
1423             (options.debug ? "enabled" : "disabled"));
1424     }
1425     else if (key == KSYM_v)
1426     {
1427       Error(ERR_DEBUG, "currently using game engine version %d",
1428             game.engine_version);
1429     }
1430   }
1431 #endif
1432 }
1433
1434 void HandleKey(Key key, int key_status)
1435 {
1436   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1437   static boolean ignore_repeated_key = FALSE;
1438   static struct SetupKeyboardInfo ski;
1439   static struct SetupShortcutInfo ssi;
1440   static struct
1441   {
1442     Key *key_custom;
1443     Key *key_snap;
1444     Key key_default;
1445     byte action;
1446   } key_info[] =
1447   {
1448     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1449     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1450     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1451     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1452     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1453     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1454   };
1455   int joy = 0;
1456   int i;
1457
1458   if (game_status == GAME_MODE_PLAYING)
1459   {
1460     /* only needed for single-step tape recording mode */
1461     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1462     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1463     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1464     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1465     int pnr;
1466
1467     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1468     {
1469       byte key_action = 0;
1470
1471       if (setup.input[pnr].use_joystick)
1472         continue;
1473
1474       ski = setup.input[pnr].key;
1475
1476       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1477         if (key == *key_info[i].key_custom)
1478           key_action |= key_info[i].action;
1479
1480       /* use combined snap+direction keys for the first player only */
1481       if (pnr == 0)
1482       {
1483         ssi = setup.shortcut;
1484
1485         for (i = 0; i < NUM_DIRECTIONS; i++)
1486           if (key == *key_info[i].key_snap)
1487             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1488       }
1489
1490       /* clear delayed snap and drop actions in single step mode (see below) */
1491       if (tape.single_step)
1492       {
1493         if (clear_snap_button[pnr])
1494         {
1495           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1496           clear_snap_button[pnr] = FALSE;
1497         }
1498
1499         if (clear_drop_button[pnr])
1500         {
1501           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1502           clear_drop_button[pnr] = FALSE;
1503         }
1504       }
1505
1506       if (key_status == KEY_PRESSED)
1507         stored_player[pnr].action |= key_action;
1508       else
1509         stored_player[pnr].action &= ~key_action;
1510
1511       if (tape.single_step && tape.recording && tape.pausing)
1512       {
1513         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1514         {
1515           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1516
1517           /* if snap key already pressed, don't snap when releasing (below) */
1518           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1519             element_snapped[pnr] = TRUE;
1520
1521           /* if drop key already pressed, don't drop when releasing (below) */
1522           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1523             element_dropped[pnr] = TRUE;
1524         }
1525         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1526         {
1527           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1528               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1529           {
1530
1531             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1532                 getRedDiskReleaseFlag_SP() == 0)
1533               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1534
1535             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1536           }
1537         }
1538         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1539         {
1540           if (key_action & KEY_BUTTON_SNAP)
1541           {
1542             /* if snap key was released without moving (see above), snap now */
1543             if (!element_snapped[pnr])
1544             {
1545               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1546
1547               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1548
1549               /* clear delayed snap button on next event */
1550               clear_snap_button[pnr] = TRUE;
1551             }
1552
1553             element_snapped[pnr] = FALSE;
1554           }
1555
1556           if (key_action & KEY_BUTTON_DROP &&
1557               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1558           {
1559             /* if drop key was released without moving (see above), drop now */
1560             if (!element_dropped[pnr])
1561             {
1562               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1563
1564               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1565                   getRedDiskReleaseFlag_SP() != 0)
1566                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1567
1568               /* clear delayed drop button on next event */
1569               clear_drop_button[pnr] = TRUE;
1570             }
1571
1572             element_dropped[pnr] = FALSE;
1573           }
1574         }
1575       }
1576       else if (tape.recording && tape.pausing)
1577       {
1578         /* prevent key release events from un-pausing a paused game */
1579         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1580           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1581       }
1582     }
1583   }
1584   else
1585   {
1586     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1587       if (key == key_info[i].key_default)
1588         joy |= key_info[i].action;
1589   }
1590
1591   if (joy)
1592   {
1593     if (key_status == KEY_PRESSED)
1594       key_joystick_mapping |= joy;
1595     else
1596       key_joystick_mapping &= ~joy;
1597
1598     HandleJoystick();
1599   }
1600
1601   if (game_status != GAME_MODE_PLAYING)
1602     key_joystick_mapping = 0;
1603
1604   if (key_status == KEY_RELEASED)
1605   {
1606     // reset flag to ignore repeated "key pressed" events after key release
1607     ignore_repeated_key = FALSE;
1608
1609     return;
1610   }
1611
1612   if ((key == KSYM_F11 ||
1613        ((key == KSYM_Return ||
1614          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1615       video.fullscreen_available &&
1616       !ignore_repeated_key)
1617   {
1618     setup.fullscreen = !setup.fullscreen;
1619
1620     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1621
1622     if (game_status == GAME_MODE_SETUP)
1623       RedrawSetupScreenAfterFullscreenToggle();
1624
1625     // set flag to ignore repeated "key pressed" events
1626     ignore_repeated_key = TRUE;
1627
1628     return;
1629   }
1630
1631   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1632        key == KSYM_minus || key == KSYM_KP_Subtract ||
1633        key == KSYM_plus  || key == KSYM_KP_Add ||
1634        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1635       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1636       video.window_scaling_available &&
1637       !video.fullscreen_enabled)
1638   {
1639     if (key == KSYM_0 || key == KSYM_KP_0)
1640       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1641     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1642       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1643     else
1644       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1645
1646     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1647       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1648     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1649       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1650
1651     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1652
1653     if (game_status == GAME_MODE_SETUP)
1654       RedrawSetupScreenAfterFullscreenToggle();
1655
1656     return;
1657   }
1658
1659   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1660       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1661   {
1662     GameEnd();
1663
1664     return;
1665   }
1666
1667   if (game_status == GAME_MODE_MAIN &&
1668       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1669   {
1670     StartGameActions(options.network, setup.autorecord, level.random_seed);
1671
1672     return;
1673   }
1674
1675   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1676   {
1677     if (key == setup.shortcut.save_game)
1678       TapeQuickSave();
1679     else if (key == setup.shortcut.load_game)
1680       TapeQuickLoad();
1681     else if (key == setup.shortcut.toggle_pause)
1682       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1683
1684     HandleTapeButtonKeys(key);
1685     HandleSoundButtonKeys(key);
1686   }
1687
1688   if (game_status == GAME_MODE_PLAYING && !network_playing)
1689   {
1690     int centered_player_nr_next = -999;
1691
1692     if (key == setup.shortcut.focus_player_all)
1693       centered_player_nr_next = -1;
1694     else
1695       for (i = 0; i < MAX_PLAYERS; i++)
1696         if (key == setup.shortcut.focus_player[i])
1697           centered_player_nr_next = i;
1698
1699     if (centered_player_nr_next != -999)
1700     {
1701       game.centered_player_nr_next = centered_player_nr_next;
1702       game.set_centered_player = TRUE;
1703
1704       if (tape.recording)
1705       {
1706         tape.centered_player_nr_next = game.centered_player_nr_next;
1707         tape.set_centered_player = TRUE;
1708       }
1709     }
1710   }
1711
1712   HandleKeysSpecial(key);
1713
1714   if (HandleGadgetsKeyInput(key))
1715   {
1716     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1717       key = KSYM_UNDEFINED;
1718   }
1719
1720   switch (game_status)
1721   {
1722     case GAME_MODE_PSEUDO_TYPENAME:
1723       HandleTypeName(0, key);
1724       break;
1725
1726     case GAME_MODE_TITLE:
1727     case GAME_MODE_MAIN:
1728     case GAME_MODE_LEVELS:
1729     case GAME_MODE_LEVELNR:
1730     case GAME_MODE_SETUP:
1731     case GAME_MODE_INFO:
1732     case GAME_MODE_SCORES:
1733       switch (key)
1734       {
1735         case KSYM_space:
1736         case KSYM_Return:
1737           if (game_status == GAME_MODE_TITLE)
1738             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1739           else if (game_status == GAME_MODE_MAIN)
1740             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1741           else if (game_status == GAME_MODE_LEVELS)
1742             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1743           else if (game_status == GAME_MODE_LEVELNR)
1744             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1745           else if (game_status == GAME_MODE_SETUP)
1746             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1747           else if (game_status == GAME_MODE_INFO)
1748             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1749           else if (game_status == GAME_MODE_SCORES)
1750             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1751           break;
1752
1753         case KSYM_Escape:
1754           if (game_status != GAME_MODE_MAIN)
1755             FadeSkipNextFadeIn();
1756
1757           if (game_status == GAME_MODE_TITLE)
1758             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1759           else if (game_status == GAME_MODE_LEVELS)
1760             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1761           else if (game_status == GAME_MODE_LEVELNR)
1762             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1763           else if (game_status == GAME_MODE_SETUP)
1764             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1765           else if (game_status == GAME_MODE_INFO)
1766             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1767           else if (game_status == GAME_MODE_SCORES)
1768             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1769           break;
1770
1771         case KSYM_Page_Up:
1772           if (game_status == GAME_MODE_LEVELS)
1773             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1774           else if (game_status == GAME_MODE_LEVELNR)
1775             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1776           else if (game_status == GAME_MODE_SETUP)
1777             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1778           else if (game_status == GAME_MODE_INFO)
1779             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1780           else if (game_status == GAME_MODE_SCORES)
1781             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1782           break;
1783
1784         case KSYM_Page_Down:
1785           if (game_status == GAME_MODE_LEVELS)
1786             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1787           else if (game_status == GAME_MODE_LEVELNR)
1788             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1789           else if (game_status == GAME_MODE_SETUP)
1790             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1791           else if (game_status == GAME_MODE_INFO)
1792             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1793           else if (game_status == GAME_MODE_SCORES)
1794             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1795           break;
1796
1797         default:
1798           break;
1799       }
1800       break;
1801
1802     case GAME_MODE_EDITOR:
1803       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1804         HandleLevelEditorKeyInput(key);
1805       break;
1806
1807     case GAME_MODE_PLAYING:
1808     {
1809       switch (key)
1810       {
1811         case KSYM_Escape:
1812           RequestQuitGame(setup.ask_on_escape);
1813           break;
1814
1815         default:
1816           break;
1817       }
1818       break;
1819     }
1820
1821     default:
1822       if (key == KSYM_Escape)
1823       {
1824         SetGameStatus(GAME_MODE_MAIN);
1825
1826         DrawMainMenu();
1827
1828         return;
1829       }
1830   }
1831
1832   HandleKeysDebug(key);
1833 }
1834
1835 void HandleNoEvent()
1836 {
1837   // if (button_status && game_status != GAME_MODE_PLAYING)
1838   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1839   {
1840     HandleButton(0, 0, button_status, -button_status);
1841   }
1842   else
1843   {
1844     HandleJoystick();
1845   }
1846
1847 #if defined(NETWORK_AVALIABLE)
1848   if (options.network)
1849     HandleNetworking();
1850 #endif
1851
1852   switch (game_status)
1853   {
1854     case GAME_MODE_MAIN:
1855       DrawPreviewLevelAnimation();
1856       break;
1857
1858     case GAME_MODE_EDITOR:
1859       HandleLevelEditorIdle();
1860       break;
1861
1862     case GAME_MODE_PLAYING:
1863       HandleFollowFinger(-1, -1, -1);
1864       break;
1865
1866     default:
1867       break;
1868   }
1869 }
1870
1871 static int HandleJoystickForAllPlayers()
1872 {
1873   int i;
1874   int result = 0;
1875
1876   for (i = 0; i < MAX_PLAYERS; i++)
1877   {
1878     byte joy_action = 0;
1879
1880     /*
1881     if (!setup.input[i].use_joystick)
1882       continue;
1883       */
1884
1885     joy_action = Joystick(i);
1886     result |= joy_action;
1887
1888     if (!setup.input[i].use_joystick)
1889       continue;
1890
1891     stored_player[i].action = joy_action;
1892   }
1893
1894   return result;
1895 }
1896
1897 void HandleJoystick()
1898 {
1899   int joystick  = HandleJoystickForAllPlayers();
1900   int keyboard  = key_joystick_mapping;
1901   int joy       = (joystick | keyboard);
1902   int left      = joy & JOY_LEFT;
1903   int right     = joy & JOY_RIGHT;
1904   int up        = joy & JOY_UP;
1905   int down      = joy & JOY_DOWN;
1906   int button    = joy & JOY_BUTTON;
1907   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1908   int dx        = (left ? -1    : right ? 1     : 0);
1909   int dy        = (up   ? -1    : down  ? 1     : 0);
1910
1911   switch (game_status)
1912   {
1913     case GAME_MODE_TITLE:
1914     case GAME_MODE_MAIN:
1915     case GAME_MODE_LEVELS:
1916     case GAME_MODE_LEVELNR:
1917     case GAME_MODE_SETUP:
1918     case GAME_MODE_INFO:
1919     {
1920       static unsigned int joystickmove_delay = 0;
1921
1922       if (joystick && !button &&
1923           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1924         newbutton = dx = dy = 0;
1925
1926       if (game_status == GAME_MODE_TITLE)
1927         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1928       else if (game_status == GAME_MODE_MAIN)
1929         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1930       else if (game_status == GAME_MODE_LEVELS)
1931         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1932       else if (game_status == GAME_MODE_LEVELNR)
1933         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1934       else if (game_status == GAME_MODE_SETUP)
1935         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1936       else if (game_status == GAME_MODE_INFO)
1937         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1938       break;
1939     }
1940
1941     case GAME_MODE_SCORES:
1942       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1943       break;
1944
1945     case GAME_MODE_PLAYING:
1946       if (tape.playing || keyboard)
1947         newbutton = ((joy & JOY_BUTTON) != 0);
1948
1949       if (newbutton && AllPlayersGone)
1950       {
1951         GameEnd();
1952
1953         return;
1954       }
1955
1956       break;
1957
1958     default:
1959       break;
1960   }
1961 }