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