cleanup of obsolete client message event from SDL 1.2
[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 static boolean virtual_button_pressed = FALSE;
43
44
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
48
49
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
53
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
56
57 static int FilterEvents(const Event *event)
58 {
59   MotionEvent *motion;
60
61   // skip repeated key press events if keyboard auto-repeat is disabled
62   if (event->type == EVENT_KEYPRESS &&
63       event->key.repeat &&
64       !keyrepeat_status)
65     return 0;
66
67   if (event->type == EVENT_BUTTONPRESS ||
68       event->type == EVENT_BUTTONRELEASE)
69   {
70     ((ButtonEvent *)event)->x -= video.screen_xoffset;
71     ((ButtonEvent *)event)->y -= video.screen_yoffset;
72   }
73   else if (event->type == EVENT_MOTIONNOTIFY)
74   {
75     ((MotionEvent *)event)->x -= video.screen_xoffset;
76     ((MotionEvent *)event)->y -= video.screen_yoffset;
77
78     gfx.mouse_x = ((MotionEvent *)event)->x;
79     gfx.mouse_y = ((MotionEvent *)event)->y;
80   }
81
82   // non-motion events are directly passed to event handler functions
83   if (event->type != EVENT_MOTIONNOTIFY)
84     return 1;
85
86   motion = (MotionEvent *)event;
87   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
88                              motion->y >= SY && motion->y < SY + SYSIZE);
89
90   // do no reset mouse cursor before all pending events have been processed
91   if (gfx.cursor_mode == cursor_mode_last &&
92       ((game_status == GAME_MODE_TITLE &&
93         gfx.cursor_mode == CURSOR_NONE) ||
94        (game_status == GAME_MODE_PLAYING &&
95         gfx.cursor_mode == CURSOR_PLAYFIELD)))
96   {
97     SetMouseCursor(CURSOR_DEFAULT);
98
99     DelayReached(&special_cursor_delay, 0);
100
101     cursor_mode_last = CURSOR_DEFAULT;
102   }
103
104   // skip mouse motion events without pressed button outside level editor
105   if (button_status == MB_RELEASED &&
106       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
107     return 0;
108
109   return 1;
110 }
111
112 // to prevent delay problems, skip mouse motion events if the very next
113 // event is also a mouse motion event (and therefore effectively only
114 // handling the last of a row of mouse motion events in the event queue)
115
116 static boolean SkipPressedMouseMotionEvent(const Event *event)
117 {
118   // nothing to do if the current event is not a mouse motion event
119   if (event->type != EVENT_MOTIONNOTIFY)
120     return FALSE;
121
122   // only skip motion events with pressed button outside the game
123   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
124     return FALSE;
125
126   if (PendingEvent())
127   {
128     Event next_event;
129
130     PeekEvent(&next_event);
131
132     // if next event is also a mouse motion event, skip the current one
133     if (next_event.type == EVENT_MOTIONNOTIFY)
134       return TRUE;
135   }
136
137   return FALSE;
138 }
139
140 static boolean WaitValidEvent(Event *event)
141 {
142   WaitEvent(event);
143
144   if (!FilterEvents(event))
145     return FALSE;
146
147   if (SkipPressedMouseMotionEvent(event))
148     return FALSE;
149
150   return TRUE;
151 }
152
153 /* this is especially needed for event modifications for the Android target:
154    if mouse coordinates should be modified in the event filter function,
155    using a properly installed SDL event filter does not work, because in
156    the event filter, mouse coordinates in the event structure are still
157    physical pixel positions, not logical (scaled) screen positions, so this
158    has to be handled at a later stage in the event processing functions
159    (when device pixel positions are already converted to screen positions) */
160
161 boolean NextValidEvent(Event *event)
162 {
163   while (PendingEvent())
164     if (WaitValidEvent(event))
165       return TRUE;
166
167   return FALSE;
168 }
169
170 static void HandleEvents(void)
171 {
172   Event event;
173   unsigned int event_frame_delay = 0;
174   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
175
176   ResetDelayCounter(&event_frame_delay);
177
178   while (NextValidEvent(&event))
179   {
180     switch (event.type)
181     {
182       case EVENT_BUTTONPRESS:
183       case EVENT_BUTTONRELEASE:
184         HandleButtonEvent((ButtonEvent *) &event);
185         break;
186
187       case EVENT_MOTIONNOTIFY:
188         HandleMotionEvent((MotionEvent *) &event);
189         break;
190
191       case EVENT_WHEELMOTION:
192         HandleWheelEvent((WheelEvent *) &event);
193         break;
194
195       case SDL_WINDOWEVENT:
196         HandleWindowEvent((WindowEvent *) &event);
197         break;
198
199       case EVENT_FINGERPRESS:
200       case EVENT_FINGERRELEASE:
201       case EVENT_FINGERMOTION:
202         HandleFingerEvent((FingerEvent *) &event);
203         break;
204
205       case EVENT_TEXTINPUT:
206         HandleTextEvent((TextEvent *) &event);
207         break;
208
209       case SDL_APP_WILLENTERBACKGROUND:
210       case SDL_APP_DIDENTERBACKGROUND:
211       case SDL_APP_WILLENTERFOREGROUND:
212       case SDL_APP_DIDENTERFOREGROUND:
213         HandlePauseResumeEvent((PauseResumeEvent *) &event);
214         break;
215
216       case EVENT_KEYPRESS:
217       case EVENT_KEYRELEASE:
218         HandleKeyEvent((KeyEvent *) &event);
219         break;
220
221       default:
222         HandleOtherEvents(&event);
223         break;
224     }
225
226     // do not handle events for longer than standard frame delay period
227     if (DelayReached(&event_frame_delay, event_frame_delay_value))
228       break;
229   }
230 }
231
232 void HandleOtherEvents(Event *event)
233 {
234   switch (event->type)
235   {
236     case SDL_CONTROLLERBUTTONDOWN:
237     case SDL_CONTROLLERBUTTONUP:
238       // for any game controller button event, disable overlay buttons
239       SetOverlayEnabled(FALSE);
240
241       HandleSpecialGameControllerButtons(event);
242
243       // FALL THROUGH
244     case SDL_CONTROLLERDEVICEADDED:
245     case SDL_CONTROLLERDEVICEREMOVED:
246     case SDL_CONTROLLERAXISMOTION:
247     case SDL_JOYAXISMOTION:
248     case SDL_JOYBUTTONDOWN:
249     case SDL_JOYBUTTONUP:
250       HandleJoystickEvent(event);
251       break;
252
253     case SDL_DROPBEGIN:
254     case SDL_DROPCOMPLETE:
255     case SDL_DROPFILE:
256     case SDL_DROPTEXT:
257       HandleDropEvent(event);
258       break;
259
260     case EVENT_QUIT:
261       CloseAllAndExit(0);
262       break;
263
264     default:
265       break;
266   }
267 }
268
269 static void HandleMouseCursor(void)
270 {
271   if (game_status == GAME_MODE_TITLE)
272   {
273     // when showing title screens, hide mouse pointer (if not moved)
274
275     if (gfx.cursor_mode != CURSOR_NONE &&
276         DelayReached(&special_cursor_delay, special_cursor_delay_value))
277     {
278       SetMouseCursor(CURSOR_NONE);
279     }
280   }
281   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
282                                                 tape.single_step))
283   {
284     // when playing, display a special mouse pointer inside the playfield
285
286     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
287         cursor_inside_playfield &&
288         DelayReached(&special_cursor_delay, special_cursor_delay_value))
289     {
290       if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
291           tile_cursor.enabled)
292         SetMouseCursor(CURSOR_PLAYFIELD);
293     }
294   }
295   else if (gfx.cursor_mode != CURSOR_DEFAULT)
296   {
297     SetMouseCursor(CURSOR_DEFAULT);
298   }
299
300   // this is set after all pending events have been processed
301   cursor_mode_last = gfx.cursor_mode;
302 }
303
304 void EventLoop(void)
305 {
306   while (1)
307   {
308     if (PendingEvent())
309       HandleEvents();
310     else
311       HandleNoEvent();
312
313     // execute event related actions after pending events have been processed
314     HandleEventActions();
315
316     // don't use all CPU time when idle; the main loop while playing
317     // has its own synchronization and is CPU friendly, too
318
319     if (game_status == GAME_MODE_PLAYING)
320       HandleGameActions();
321
322     // always copy backbuffer to visible screen for every video frame
323     BackToFront();
324
325     // reset video frame delay to default (may change again while playing)
326     SetVideoFrameDelay(MenuFrameDelay);
327
328     if (game_status == GAME_MODE_QUIT)
329       return;
330   }
331 }
332
333 void ClearAutoRepeatKeyEvents(void)
334 {
335   while (PendingEvent())
336   {
337     Event next_event;
338
339     PeekEvent(&next_event);
340
341     // if event is repeated key press event, remove it from event queue
342     if (next_event.type == EVENT_KEYPRESS &&
343         next_event.key.repeat)
344       WaitEvent(&next_event);
345     else
346       break;
347   }
348 }
349
350 void ClearEventQueue(void)
351 {
352   Event event;
353
354   while (NextValidEvent(&event))
355   {
356     switch (event.type)
357     {
358       case EVENT_BUTTONRELEASE:
359         button_status = MB_RELEASED;
360         break;
361
362       case EVENT_KEYRELEASE:
363         ClearPlayerAction();
364         break;
365
366       case SDL_CONTROLLERBUTTONUP:
367         HandleJoystickEvent(&event);
368         ClearPlayerAction();
369         break;
370
371       default:
372         HandleOtherEvents(&event);
373         break;
374     }
375   }
376 }
377
378 static void ClearPlayerMouseAction(void)
379 {
380   local_player->mouse_action.lx = 0;
381   local_player->mouse_action.ly = 0;
382   local_player->mouse_action.button = 0;
383 }
384
385 void ClearPlayerAction(void)
386 {
387   int i;
388
389   // simulate key release events for still pressed keys
390   key_joystick_mapping = 0;
391   for (i = 0; i < MAX_PLAYERS; i++)
392   {
393     stored_player[i].action = 0;
394     stored_player[i].snap_action = 0;
395   }
396
397   ClearJoystickState();
398   ClearPlayerMouseAction();
399 }
400
401 static void SetPlayerMouseAction(int mx, int my, int button)
402 {
403   int lx = getLevelFromScreenX(mx);
404   int ly = getLevelFromScreenY(my);
405   int new_button = (!local_player->mouse_action.button && button);
406
407   if (local_player->mouse_action.button_hint)
408     button = local_player->mouse_action.button_hint;
409
410   ClearPlayerMouseAction();
411
412   if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
413     return;
414
415   local_player->mouse_action.lx = lx;
416   local_player->mouse_action.ly = ly;
417   local_player->mouse_action.button = button;
418
419   if (tape.recording && tape.pausing && tape.use_mouse)
420   {
421     // un-pause a paused game only if mouse button was newly pressed down
422     if (new_button)
423       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
424   }
425
426   SetTileCursorXY(lx, ly);
427 }
428
429 void HandleButtonEvent(ButtonEvent *event)
430 {
431 #if DEBUG_EVENTS_BUTTON
432   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
433         event->button,
434         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
435         event->x, event->y);
436 #endif
437
438   // for any mouse button event, disable playfield tile cursor
439   SetTileCursorEnabled(FALSE);
440
441 #if defined(HAS_SCREEN_KEYBOARD)
442   if (video.shifted_up)
443     event->y += video.shifted_up_pos;
444 #endif
445
446   motion_status = FALSE;
447
448   if (event->type == EVENT_BUTTONPRESS)
449     button_status = event->button;
450   else
451     button_status = MB_RELEASED;
452
453   HandleButton(event->x, event->y, button_status, event->button);
454 }
455
456 void HandleMotionEvent(MotionEvent *event)
457 {
458   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
459     return;
460
461   motion_status = TRUE;
462
463 #if DEBUG_EVENTS_MOTION
464   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
465         button_status, event->x, event->y);
466 #endif
467
468   HandleButton(event->x, event->y, button_status, button_status);
469 }
470
471 void HandleWheelEvent(WheelEvent *event)
472 {
473   int button_nr;
474
475 #if DEBUG_EVENTS_WHEEL
476 #if 1
477   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
478         event->which, event->x, event->y);
479 #else
480   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
481   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
482         event->which, event->x, event->y,
483         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
484          "SDL_MOUSEWHEEL_FLIPPED"));
485 #endif
486 #endif
487
488   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
489                event->x > 0 ? MB_WHEEL_RIGHT :
490                event->y < 0 ? MB_WHEEL_DOWN :
491                event->y > 0 ? MB_WHEEL_UP : 0);
492
493 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
494   // accelerated mouse wheel available on Mac and Windows
495   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
496 #else
497   // no accelerated mouse wheel available on Unix/Linux
498   wheel_steps = DEFAULT_WHEEL_STEPS;
499 #endif
500
501   motion_status = FALSE;
502
503   button_status = button_nr;
504   HandleButton(0, 0, button_status, -button_nr);
505
506   button_status = MB_RELEASED;
507   HandleButton(0, 0, button_status, -button_nr);
508 }
509
510 void HandleWindowEvent(WindowEvent *event)
511 {
512 #if DEBUG_EVENTS_WINDOW
513   int subtype = event->event;
514
515   char *event_name =
516     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
517      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
518      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
519      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
520      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
521      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
522      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
523      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
524      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
525      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
526      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
527      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
528      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
529      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
530      "(UNKNOWN)");
531
532   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
533         event_name, event->data1, event->data2);
534 #endif
535
536 #if 0
537   // (not needed, as the screen gets redrawn every 20 ms anyway)
538   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
539       event->event == SDL_WINDOWEVENT_RESIZED ||
540       event->event == SDL_WINDOWEVENT_EXPOSED)
541     SDLRedrawWindow();
542 #endif
543
544   if (event->event == SDL_WINDOWEVENT_RESIZED)
545   {
546     if (!video.fullscreen_enabled)
547     {
548       int new_window_width  = event->data1;
549       int new_window_height = event->data2;
550
551       // if window size has changed after resizing, calculate new scaling factor
552       if (new_window_width  != video.window_width ||
553           new_window_height != video.window_height)
554       {
555         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
556         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
557
558         // (extreme window scaling allowed, but cannot be saved permanently)
559         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
560         setup.window_scaling_percent =
561           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
562               MAX_WINDOW_SCALING_PERCENT);
563
564         video.window_width  = new_window_width;
565         video.window_height = new_window_height;
566
567         if (game_status == GAME_MODE_SETUP)
568           RedrawSetupScreenAfterFullscreenToggle();
569
570         SetWindowTitle();
571       }
572     }
573 #if defined(PLATFORM_ANDROID)
574     else
575     {
576       int new_display_width  = event->data1;
577       int new_display_height = event->data2;
578
579       // if fullscreen display size has changed, device has been rotated
580       if (new_display_width  != video.display_width ||
581           new_display_height != video.display_height)
582       {
583         int nr = GRID_ACTIVE_NR();      // previous screen orientation
584
585         video.display_width  = new_display_width;
586         video.display_height = new_display_height;
587
588         SDLSetScreenProperties();
589
590         // check if screen orientation has changed (should always be true here)
591         if (nr != GRID_ACTIVE_NR())
592         {
593           int x, y;
594
595           if (game_status == GAME_MODE_SETUP)
596             RedrawSetupScreenAfterScreenRotation(nr);
597
598           nr = GRID_ACTIVE_NR();
599
600           overlay.grid_xsize = setup.touch.grid_xsize[nr];
601           overlay.grid_ysize = setup.touch.grid_ysize[nr];
602
603           for (x = 0; x < MAX_GRID_XSIZE; x++)
604             for (y = 0; y < MAX_GRID_YSIZE; y++)
605               overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
606         }
607       }
608     }
609 #endif
610   }
611 }
612
613 #define NUM_TOUCH_FINGERS               3
614
615 static struct
616 {
617   boolean touched;
618   SDL_FingerID finger_id;
619   int counter;
620   Key key;
621   byte action;
622 } touch_info[NUM_TOUCH_FINGERS];
623
624 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
625 {
626 #if 1
627   int x = event->x * overlay.grid_xsize;
628   int y = event->y * overlay.grid_ysize;
629   int grid_button = overlay.grid_button[x][y];
630   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
631   Key key = (grid_button == CHAR_GRID_BUTTON_LEFT  ? setup.input[0].key.left :
632              grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
633              grid_button == CHAR_GRID_BUTTON_UP    ? setup.input[0].key.up :
634              grid_button == CHAR_GRID_BUTTON_DOWN  ? setup.input[0].key.down :
635              grid_button == CHAR_GRID_BUTTON_SNAP  ? setup.input[0].key.snap :
636              grid_button == CHAR_GRID_BUTTON_DROP  ? setup.input[0].key.drop :
637              KSYM_UNDEFINED);
638 #else
639   float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
640   float event_x = (event->x);
641   float event_y = (event->y - ypos) / (1 - ypos);
642   Key key = (event_x > 0         && event_x < 1.0 / 6.0 &&
643              event_y > 2.0 / 3.0 && event_y < 1 ?
644              setup.input[0].key.snap :
645              event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
646              event_y > 2.0 / 3.0 && event_y < 1 ?
647              setup.input[0].key.drop :
648              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
649              event_y > 0         && event_y < 1.0 / 3.0 ?
650              setup.input[0].key.up :
651              event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
652              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
653              setup.input[0].key.left :
654              event_x > 8.0 / 9.0 && event_x < 1 &&
655              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
656              setup.input[0].key.right :
657              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
658              event_y > 2.0 / 3.0 && event_y < 1 ?
659              setup.input[0].key.down :
660              KSYM_UNDEFINED);
661 #endif
662   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
663                     KEY_PRESSED);
664   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
665                            "KEY_PRESSED");
666   int i;
667
668   virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
669
670   // for any touch input event, enable overlay buttons (if activated)
671   SetOverlayEnabled(TRUE);
672
673   Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
674         getKeyNameFromKey(key), key_status_name, event->fingerId);
675
676   if (key_status == KEY_PRESSED)
677     overlay.grid_button_action |= grid_button_action;
678   else
679     overlay.grid_button_action &= ~grid_button_action;
680
681   // check if we already know this touch event's finger id
682   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
683   {
684     if (touch_info[i].touched &&
685         touch_info[i].finger_id == event->fingerId)
686     {
687       // Error(ERR_DEBUG, "MARK 1: %d", i);
688
689       break;
690     }
691   }
692
693   if (i >= NUM_TOUCH_FINGERS)
694   {
695     if (key_status == KEY_PRESSED)
696     {
697       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
698
699       // unknown finger id -- get new, empty slot, if available
700       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
701       {
702         if (touch_info[i].counter < oldest_counter)
703         {
704           oldest_pos = i;
705           oldest_counter = touch_info[i].counter;
706
707           // Error(ERR_DEBUG, "MARK 2: %d", i);
708         }
709
710         if (!touch_info[i].touched)
711         {
712           // Error(ERR_DEBUG, "MARK 3: %d", i);
713
714           break;
715         }
716       }
717
718       if (i >= NUM_TOUCH_FINGERS)
719       {
720         // all slots allocated -- use oldest slot
721         i = oldest_pos;
722
723         // Error(ERR_DEBUG, "MARK 4: %d", i);
724       }
725     }
726     else
727     {
728       // release of previously unknown key (should not happen)
729
730       if (key != KSYM_UNDEFINED)
731       {
732         HandleKey(key, KEY_RELEASED);
733
734         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
735               getKeyNameFromKey(key), "KEY_RELEASED", i);
736       }
737     }
738   }
739
740   if (i < NUM_TOUCH_FINGERS)
741   {
742     if (key_status == KEY_PRESSED)
743     {
744       if (touch_info[i].key != key)
745       {
746         if (touch_info[i].key != KSYM_UNDEFINED)
747         {
748           HandleKey(touch_info[i].key, KEY_RELEASED);
749
750           // undraw previous grid button when moving finger away
751           overlay.grid_button_action &= ~touch_info[i].action;
752
753           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
754                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
755         }
756
757         if (key != KSYM_UNDEFINED)
758         {
759           HandleKey(key, KEY_PRESSED);
760
761           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
762                 getKeyNameFromKey(key), "KEY_PRESSED", i);
763         }
764       }
765
766       touch_info[i].touched = TRUE;
767       touch_info[i].finger_id = event->fingerId;
768       touch_info[i].counter = Counter();
769       touch_info[i].key = key;
770       touch_info[i].action = grid_button_action;
771     }
772     else
773     {
774       if (touch_info[i].key != KSYM_UNDEFINED)
775       {
776         HandleKey(touch_info[i].key, KEY_RELEASED);
777
778         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
779               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
780       }
781
782       touch_info[i].touched = FALSE;
783       touch_info[i].finger_id = 0;
784       touch_info[i].counter = 0;
785       touch_info[i].key = 0;
786       touch_info[i].action = JOY_NO_ACTION;
787     }
788   }
789 }
790
791 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
792 {
793   static Key motion_key_x = KSYM_UNDEFINED;
794   static Key motion_key_y = KSYM_UNDEFINED;
795   static Key button_key = KSYM_UNDEFINED;
796   static float motion_x1, motion_y1;
797   static float button_x1, button_y1;
798   static SDL_FingerID motion_id = -1;
799   static SDL_FingerID button_id = -1;
800   int move_trigger_distance_percent = setup.touch.move_distance;
801   int drop_trigger_distance_percent = setup.touch.drop_distance;
802   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
803   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
804   float event_x = event->x;
805   float event_y = event->y;
806
807   if (event->type == EVENT_FINGERPRESS)
808   {
809     if (event_x > 1.0 / 3.0)
810     {
811       // motion area
812
813       motion_id = event->fingerId;
814
815       motion_x1 = event_x;
816       motion_y1 = event_y;
817
818       motion_key_x = KSYM_UNDEFINED;
819       motion_key_y = KSYM_UNDEFINED;
820
821       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
822     }
823     else
824     {
825       // button area
826
827       button_id = event->fingerId;
828
829       button_x1 = event_x;
830       button_y1 = event_y;
831
832       button_key = setup.input[0].key.snap;
833
834       HandleKey(button_key, KEY_PRESSED);
835
836       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
837     }
838   }
839   else if (event->type == EVENT_FINGERRELEASE)
840   {
841     if (event->fingerId == motion_id)
842     {
843       motion_id = -1;
844
845       if (motion_key_x != KSYM_UNDEFINED)
846         HandleKey(motion_key_x, KEY_RELEASED);
847       if (motion_key_y != KSYM_UNDEFINED)
848         HandleKey(motion_key_y, KEY_RELEASED);
849
850       motion_key_x = KSYM_UNDEFINED;
851       motion_key_y = KSYM_UNDEFINED;
852
853       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
854     }
855     else if (event->fingerId == button_id)
856     {
857       button_id = -1;
858
859       if (button_key != KSYM_UNDEFINED)
860         HandleKey(button_key, KEY_RELEASED);
861
862       button_key = KSYM_UNDEFINED;
863
864       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
865     }
866   }
867   else if (event->type == EVENT_FINGERMOTION)
868   {
869     if (event->fingerId == motion_id)
870     {
871       float distance_x = ABS(event_x - motion_x1);
872       float distance_y = ABS(event_y - motion_y1);
873       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
874                               event_x > motion_x1 ? setup.input[0].key.right :
875                               KSYM_UNDEFINED);
876       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
877                               event_y > motion_y1 ? setup.input[0].key.down :
878                               KSYM_UNDEFINED);
879
880       if (distance_x < move_trigger_distance / 2 ||
881           distance_x < distance_y)
882         new_motion_key_x = KSYM_UNDEFINED;
883
884       if (distance_y < move_trigger_distance / 2 ||
885           distance_y < distance_x)
886         new_motion_key_y = KSYM_UNDEFINED;
887
888       if (distance_x > move_trigger_distance ||
889           distance_y > move_trigger_distance)
890       {
891         if (new_motion_key_x != motion_key_x)
892         {
893           if (motion_key_x != KSYM_UNDEFINED)
894             HandleKey(motion_key_x, KEY_RELEASED);
895           if (new_motion_key_x != KSYM_UNDEFINED)
896             HandleKey(new_motion_key_x, KEY_PRESSED);
897         }
898
899         if (new_motion_key_y != motion_key_y)
900         {
901           if (motion_key_y != KSYM_UNDEFINED)
902             HandleKey(motion_key_y, KEY_RELEASED);
903           if (new_motion_key_y != KSYM_UNDEFINED)
904             HandleKey(new_motion_key_y, KEY_PRESSED);
905         }
906
907         motion_x1 = event_x;
908         motion_y1 = event_y;
909
910         motion_key_x = new_motion_key_x;
911         motion_key_y = new_motion_key_y;
912
913         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
914       }
915     }
916     else if (event->fingerId == button_id)
917     {
918       float distance_x = ABS(event_x - button_x1);
919       float distance_y = ABS(event_y - button_y1);
920
921       if (distance_x < drop_trigger_distance / 2 &&
922           distance_y > drop_trigger_distance)
923       {
924         if (button_key == setup.input[0].key.snap)
925           HandleKey(button_key, KEY_RELEASED);
926
927         button_x1 = event_x;
928         button_y1 = event_y;
929
930         button_key = setup.input[0].key.drop;
931
932         HandleKey(button_key, KEY_PRESSED);
933
934         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
935       }
936     }
937   }
938 }
939
940 void HandleFingerEvent(FingerEvent *event)
941 {
942 #if DEBUG_EVENTS_FINGER
943   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
944         event->type == EVENT_FINGERPRESS ? "pressed" :
945         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
946         event->touchId,
947         event->fingerId,
948         event->x, event->y,
949         event->dx, event->dy,
950         event->pressure);
951 #endif
952
953   runtime.uses_touch_device = TRUE;
954
955   if (game_status != GAME_MODE_PLAYING)
956     return;
957
958   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
959   {
960     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
961       local_player->mouse_action.button_hint =
962         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
963          event->x < 0.5                     ? MB_LEFTBUTTON  :
964          event->x > 0.5                     ? MB_RIGHTBUTTON :
965          MB_NOT_PRESSED);
966
967     return;
968   }
969
970   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
971     HandleFingerEvent_VirtualButtons(event);
972   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
973     HandleFingerEvent_WipeGestures(event);
974 }
975
976 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
977 {
978   static int old_mx = 0, old_my = 0;
979   static int last_button = MB_LEFTBUTTON;
980   static boolean touched = FALSE;
981   static boolean tapped = FALSE;
982
983   // screen tile was tapped (but finger not touching the screen anymore)
984   // (this point will also be reached without receiving a touch event)
985   if (tapped && !touched)
986   {
987     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
988
989     tapped = FALSE;
990   }
991
992   // stop here if this function was not triggered by a touch event
993   if (button == -1)
994     return;
995
996   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
997   {
998     // finger started touching the screen
999
1000     touched = TRUE;
1001     tapped = TRUE;
1002
1003     if (!motion_status)
1004     {
1005       old_mx = mx;
1006       old_my = my;
1007
1008       ClearPlayerMouseAction();
1009
1010       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1011     }
1012   }
1013   else if (button == MB_RELEASED && touched)
1014   {
1015     // finger stopped touching the screen
1016
1017     touched = FALSE;
1018
1019     if (tapped)
1020       SetPlayerMouseAction(old_mx, old_my, last_button);
1021     else
1022       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1023
1024     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1025   }
1026
1027   if (touched)
1028   {
1029     // finger moved while touching the screen
1030
1031     int old_x = getLevelFromScreenX(old_mx);
1032     int old_y = getLevelFromScreenY(old_my);
1033     int new_x = getLevelFromScreenX(mx);
1034     int new_y = getLevelFromScreenY(my);
1035
1036     if (new_x != old_x || new_y != old_y)
1037       tapped = FALSE;
1038
1039     if (new_x != old_x)
1040     {
1041       // finger moved left or right from (horizontal) starting position
1042
1043       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1044
1045       SetPlayerMouseAction(old_mx, old_my, button_nr);
1046
1047       last_button = button_nr;
1048
1049       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1050     }
1051     else
1052     {
1053       // finger stays at or returned to (horizontal) starting position
1054
1055       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1056
1057       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1058     }
1059   }
1060 }
1061
1062 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1063 {
1064   static int old_mx = 0, old_my = 0;
1065   static int last_button = MB_LEFTBUTTON;
1066   static boolean touched = FALSE;
1067   static boolean tapped = FALSE;
1068
1069   // screen tile was tapped (but finger not touching the screen anymore)
1070   // (this point will also be reached without receiving a touch event)
1071   if (tapped && !touched)
1072   {
1073     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1074
1075     tapped = FALSE;
1076   }
1077
1078   // stop here if this function was not triggered by a touch event
1079   if (button == -1)
1080     return;
1081
1082   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1083   {
1084     // finger started touching the screen
1085
1086     touched = TRUE;
1087     tapped = TRUE;
1088
1089     if (!motion_status)
1090     {
1091       old_mx = mx;
1092       old_my = my;
1093
1094       ClearPlayerMouseAction();
1095
1096       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1097     }
1098   }
1099   else if (button == MB_RELEASED && touched)
1100   {
1101     // finger stopped touching the screen
1102
1103     touched = FALSE;
1104
1105     if (tapped)
1106       SetPlayerMouseAction(old_mx, old_my, last_button);
1107     else
1108       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1109
1110     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1111   }
1112
1113   if (touched)
1114   {
1115     // finger moved while touching the screen
1116
1117     int old_x = getLevelFromScreenX(old_mx);
1118     int old_y = getLevelFromScreenY(old_my);
1119     int new_x = getLevelFromScreenX(mx);
1120     int new_y = getLevelFromScreenY(my);
1121
1122     if (new_x != old_x || new_y != old_y)
1123     {
1124       // finger moved away from starting position
1125
1126       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1127
1128       // quickly alternate between clicking and releasing for maximum speed
1129       if (FrameCounter % 2 == 0)
1130         button_nr = MB_RELEASED;
1131
1132       SetPlayerMouseAction(old_mx, old_my, button_nr);
1133
1134       if (button_nr)
1135         last_button = button_nr;
1136
1137       tapped = FALSE;
1138
1139       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1140     }
1141     else
1142     {
1143       // finger stays at or returned to starting position
1144
1145       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1146
1147       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1148     }
1149   }
1150 }
1151
1152 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1153 {
1154   static int old_mx = 0, old_my = 0;
1155   static Key motion_key_x = KSYM_UNDEFINED;
1156   static Key motion_key_y = KSYM_UNDEFINED;
1157   static boolean touched = FALSE;
1158   static boolean started_on_player = FALSE;
1159   static boolean player_is_dropping = FALSE;
1160   static int player_drop_count = 0;
1161   static int last_player_x = -1;
1162   static int last_player_y = -1;
1163
1164   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1165   {
1166     touched = TRUE;
1167
1168     old_mx = mx;
1169     old_my = my;
1170
1171     if (!motion_status)
1172     {
1173       started_on_player = FALSE;
1174       player_is_dropping = FALSE;
1175       player_drop_count = 0;
1176       last_player_x = -1;
1177       last_player_y = -1;
1178
1179       motion_key_x = KSYM_UNDEFINED;
1180       motion_key_y = KSYM_UNDEFINED;
1181
1182       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1183     }
1184   }
1185   else if (button == MB_RELEASED && touched)
1186   {
1187     touched = FALSE;
1188
1189     old_mx = 0;
1190     old_my = 0;
1191
1192     if (motion_key_x != KSYM_UNDEFINED)
1193       HandleKey(motion_key_x, KEY_RELEASED);
1194     if (motion_key_y != KSYM_UNDEFINED)
1195       HandleKey(motion_key_y, KEY_RELEASED);
1196
1197     if (started_on_player)
1198     {
1199       if (player_is_dropping)
1200       {
1201         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1202
1203         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1204       }
1205       else
1206       {
1207         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1208
1209         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1210       }
1211     }
1212
1213     motion_key_x = KSYM_UNDEFINED;
1214     motion_key_y = KSYM_UNDEFINED;
1215
1216     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1217   }
1218
1219   if (touched)
1220   {
1221     int src_x = local_player->jx;
1222     int src_y = local_player->jy;
1223     int dst_x = getLevelFromScreenX(old_mx);
1224     int dst_y = getLevelFromScreenY(old_my);
1225     int dx = dst_x - src_x;
1226     int dy = dst_y - src_y;
1227     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1228                             dx > 0 ? setup.input[0].key.right :
1229                             KSYM_UNDEFINED);
1230     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1231                             dy > 0 ? setup.input[0].key.down :
1232                             KSYM_UNDEFINED);
1233
1234     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1235         (last_player_x != local_player->jx ||
1236          last_player_y != local_player->jy))
1237     {
1238       // in case of asymmetric diagonal movement, use "preferred" direction
1239
1240       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1241
1242       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1243         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1244       else
1245         local_player->last_move_dir = last_move_dir;
1246
1247       // (required to prevent accidentally forcing direction for next movement)
1248       last_player_x = local_player->jx;
1249       last_player_y = local_player->jy;
1250     }
1251
1252     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1253     {
1254       started_on_player = TRUE;
1255       player_drop_count = getPlayerInventorySize(0);
1256       player_is_dropping = (player_drop_count > 0);
1257
1258       if (player_is_dropping)
1259       {
1260         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1261
1262         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1263       }
1264       else
1265       {
1266         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1267
1268         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1269       }
1270     }
1271     else if (dx != 0 || dy != 0)
1272     {
1273       if (player_is_dropping &&
1274           player_drop_count == getPlayerInventorySize(0))
1275       {
1276         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1277
1278         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1279         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1280
1281         player_is_dropping = FALSE;
1282       }
1283     }
1284
1285     if (new_motion_key_x != motion_key_x)
1286     {
1287       Error(ERR_DEBUG, "---------- %s %s ----------",
1288             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1289             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1290
1291       if (motion_key_x != KSYM_UNDEFINED)
1292         HandleKey(motion_key_x, KEY_RELEASED);
1293       if (new_motion_key_x != KSYM_UNDEFINED)
1294         HandleKey(new_motion_key_x, KEY_PRESSED);
1295     }
1296
1297     if (new_motion_key_y != motion_key_y)
1298     {
1299       Error(ERR_DEBUG, "---------- %s %s ----------",
1300             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1301             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1302
1303       if (motion_key_y != KSYM_UNDEFINED)
1304         HandleKey(motion_key_y, KEY_RELEASED);
1305       if (new_motion_key_y != KSYM_UNDEFINED)
1306         HandleKey(new_motion_key_y, KEY_PRESSED);
1307     }
1308
1309     motion_key_x = new_motion_key_x;
1310     motion_key_y = new_motion_key_y;
1311   }
1312 }
1313
1314 static void HandleButtonOrFinger(int mx, int my, int button)
1315 {
1316   if (game_status != GAME_MODE_PLAYING)
1317     return;
1318
1319   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1320   {
1321     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1322       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1323     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1324       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1325     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1326       SetPlayerMouseAction(mx, my, button);     // special case
1327   }
1328   else
1329   {
1330     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1331       HandleButtonOrFinger_FollowFinger(mx, my, button);
1332   }
1333 }
1334
1335 static boolean checkTextInputKeyModState(void)
1336 {
1337   // when playing, only handle raw key events and ignore text input
1338   if (game_status == GAME_MODE_PLAYING)
1339     return FALSE;
1340
1341   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1342 }
1343
1344 void HandleTextEvent(TextEvent *event)
1345 {
1346   char *text = event->text;
1347   Key key = getKeyFromKeyName(text);
1348
1349 #if DEBUG_EVENTS_TEXT
1350   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1351         text,
1352         strlen(text),
1353         text[0], (int)(text[0]),
1354         key,
1355         getKeyNameFromKey(key),
1356         GetKeyModState());
1357 #endif
1358
1359 #if !defined(HAS_SCREEN_KEYBOARD)
1360   // non-mobile devices: only handle key input with modifier keys pressed here
1361   // (every other key input is handled directly as physical key input event)
1362   if (!checkTextInputKeyModState())
1363     return;
1364 #endif
1365
1366   // process text input as "classic" (with uppercase etc.) key input event
1367   HandleKey(key, KEY_PRESSED);
1368   HandleKey(key, KEY_RELEASED);
1369 }
1370
1371 void HandlePauseResumeEvent(PauseResumeEvent *event)
1372 {
1373   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1374   {
1375     Mix_PauseMusic();
1376   }
1377   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1378   {
1379     Mix_ResumeMusic();
1380   }
1381 }
1382
1383 void HandleKeyEvent(KeyEvent *event)
1384 {
1385   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1386   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1387   Key key = GetEventKey(event, with_modifiers);
1388   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1389
1390 #if DEBUG_EVENTS_KEY
1391   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1392         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1393         event->keysym.scancode,
1394         event->keysym.sym,
1395         keymod,
1396         GetKeyModState(),
1397         key,
1398         getKeyNameFromKey(key));
1399 #endif
1400
1401 #if defined(PLATFORM_ANDROID)
1402   if (key == KSYM_Back)
1403   {
1404     // always map the "back" button to the "escape" key on Android devices
1405     key = KSYM_Escape;
1406   }
1407   else if (key == KSYM_Menu)
1408   {
1409     // the "menu" button can be used to toggle displaying virtual buttons
1410     if (key_status == KEY_PRESSED)
1411       SetOverlayEnabled(!GetOverlayEnabled());
1412   }
1413   else
1414   {
1415     // for any other "real" key event, disable virtual buttons
1416     SetOverlayEnabled(FALSE);
1417   }
1418 #endif
1419
1420   HandleKeyModState(keymod, key_status);
1421
1422   // only handle raw key input without text modifier keys pressed
1423   if (!checkTextInputKeyModState())
1424     HandleKey(key, key_status);
1425 }
1426
1427 static int HandleDropFileEvent(char *filename)
1428 {
1429   Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1430
1431   // check and extract dropped zip files into correct user data directory
1432   if (!strSuffixLower(filename, ".zip"))
1433   {
1434     Error(ERR_WARN, "file '%s' not supported", filename);
1435
1436     return TREE_TYPE_UNDEFINED;
1437   }
1438
1439   TreeInfo *tree_node = NULL;
1440   int tree_type = GetZipFileTreeType(filename);
1441   char *directory = TREE_USERDIR(tree_type);
1442
1443   if (directory == NULL)
1444   {
1445     Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1446
1447     return TREE_TYPE_UNDEFINED;
1448   }
1449
1450   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1451       game_status == GAME_MODE_LEVELS &&
1452       leveldir_current->node_parent != NULL)
1453   {
1454     // extract new level set next to currently selected level set
1455     tree_node = leveldir_current;
1456
1457     // get parent directory of currently selected level set directory
1458     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1459
1460     // use private level directory instead of top-level package level directory
1461     if (strPrefix(directory, options.level_directory) &&
1462         strEqual(leveldir_current->node_parent->fullpath, "."))
1463       directory = getUserLevelDir(NULL);
1464   }
1465
1466   // extract level or artwork set from zip file to target directory
1467   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1468
1469   if (top_dir == NULL)
1470   {
1471     // error message already issued by "ExtractZipFileIntoDirectory()"
1472
1473     return TREE_TYPE_UNDEFINED;
1474   }
1475
1476   // add extracted level or artwork set to tree info structure
1477   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1478
1479   // update menu screen (and possibly change current level set)
1480   DrawScreenAfterAddingSet(top_dir, tree_type);
1481
1482   return tree_type;
1483 }
1484
1485 static void HandleDropTextEvent(char *text)
1486 {
1487   Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1488 }
1489
1490 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1491                                     int num_artwork_sets_succeeded,
1492                                     int num_files_failed)
1493 {
1494   // only show request dialog if no other request dialog already active
1495   if (game.request_active)
1496     return;
1497
1498   // this case can happen with drag-and-drop with older SDL versions
1499   if (num_level_sets_succeeded == 0 &&
1500       num_artwork_sets_succeeded == 0 &&
1501       num_files_failed == 0)
1502     return;
1503
1504   char message[100];
1505
1506   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1507   {
1508     char message_part1[50];
1509
1510     sprintf(message_part1, "New %s set%s added",
1511             (num_artwork_sets_succeeded == 0 ? "level" :
1512              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1513             (num_level_sets_succeeded +
1514              num_artwork_sets_succeeded > 1 ? "s" : ""));
1515
1516     if (num_files_failed > 0)
1517       sprintf(message, "%s, but %d dropped file%s failed!",
1518               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1519     else
1520       sprintf(message, "%s!", message_part1);
1521   }
1522   else if (num_files_failed > 0)
1523   {
1524     sprintf(message, "Failed to process dropped file%s!",
1525             num_files_failed > 1 ? "s" : "");
1526   }
1527
1528   Request(message, REQ_CONFIRM);
1529 }
1530
1531 void HandleDropEvent(Event *event)
1532 {
1533   static boolean confirm_on_drop_complete = FALSE;
1534   static int num_level_sets_succeeded = 0;
1535   static int num_artwork_sets_succeeded = 0;
1536   static int num_files_failed = 0;
1537
1538   switch (event->type)
1539   {
1540     case SDL_DROPBEGIN:
1541     {
1542       confirm_on_drop_complete = TRUE;
1543       num_level_sets_succeeded = 0;
1544       num_artwork_sets_succeeded = 0;
1545       num_files_failed = 0;
1546
1547       break;
1548     }
1549
1550     case SDL_DROPFILE:
1551     {
1552       int tree_type = HandleDropFileEvent(event->drop.file);
1553
1554       if (tree_type == TREE_TYPE_LEVEL_DIR)
1555         num_level_sets_succeeded++;
1556       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1557                tree_type == TREE_TYPE_SOUNDS_DIR ||
1558                tree_type == TREE_TYPE_MUSIC_DIR)
1559         num_artwork_sets_succeeded++;
1560       else
1561         num_files_failed++;
1562
1563       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1564       if (!confirm_on_drop_complete)
1565       {
1566         // process all remaining events, including further SDL_DROPFILE events
1567         ClearEventQueue();
1568
1569         HandleDropCompleteEvent(num_level_sets_succeeded,
1570                                 num_artwork_sets_succeeded,
1571                                 num_files_failed);
1572
1573         num_level_sets_succeeded = 0;
1574         num_artwork_sets_succeeded = 0;
1575         num_files_failed = 0;
1576       }
1577
1578       break;
1579     }
1580
1581     case SDL_DROPTEXT:
1582     {
1583       HandleDropTextEvent(event->drop.file);
1584
1585       break;
1586     }
1587
1588     case SDL_DROPCOMPLETE:
1589     {
1590       HandleDropCompleteEvent(num_level_sets_succeeded,
1591                               num_artwork_sets_succeeded,
1592                               num_files_failed);
1593
1594       break;
1595     }
1596   }
1597
1598   if (event->drop.file != NULL)
1599     SDL_free(event->drop.file);
1600 }
1601
1602 void HandleButton(int mx, int my, int button, int button_nr)
1603 {
1604   static int old_mx = 0, old_my = 0;
1605   boolean button_hold = FALSE;
1606   boolean handle_gadgets = TRUE;
1607
1608   if (button_nr < 0)
1609   {
1610     mx = old_mx;
1611     my = old_my;
1612     button_nr = -button_nr;
1613     button_hold = TRUE;
1614   }
1615   else
1616   {
1617     old_mx = mx;
1618     old_my = my;
1619   }
1620
1621 #if defined(PLATFORM_ANDROID)
1622   // when playing, only handle gadgets when using "follow finger" controls
1623   // or when using touch controls in combination with the MM game engine
1624   // or when using gadgets that do not overlap with virtual buttons
1625   handle_gadgets =
1626     (game_status != GAME_MODE_PLAYING ||
1627      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1628      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1629      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1630       !virtual_button_pressed));
1631 #endif
1632
1633   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1634   {
1635     // do not handle this button event anymore
1636     return;             // force mouse event not to be handled at all
1637   }
1638
1639   if (handle_gadgets && HandleGadgets(mx, my, button))
1640   {
1641     // do not handle this button event anymore
1642     mx = my = -32;      // force mouse event to be outside screen tiles
1643   }
1644
1645   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1646     return;
1647
1648   // do not use scroll wheel button events for anything other than gadgets
1649   if (IS_WHEEL_BUTTON(button_nr))
1650     return;
1651
1652   switch (game_status)
1653   {
1654     case GAME_MODE_TITLE:
1655       HandleTitleScreen(mx, my, 0, 0, button);
1656       break;
1657
1658     case GAME_MODE_MAIN:
1659       HandleMainMenu(mx, my, 0, 0, button);
1660       break;
1661
1662     case GAME_MODE_PSEUDO_TYPENAME:
1663       HandleTypeName(0, KSYM_Return);
1664       break;
1665
1666     case GAME_MODE_LEVELS:
1667       HandleChooseLevelSet(mx, my, 0, 0, button);
1668       break;
1669
1670     case GAME_MODE_LEVELNR:
1671       HandleChooseLevelNr(mx, my, 0, 0, button);
1672       break;
1673
1674     case GAME_MODE_SCORES:
1675       HandleHallOfFame(0, 0, 0, 0, button);
1676       break;
1677
1678     case GAME_MODE_EDITOR:
1679       HandleLevelEditorIdle();
1680       break;
1681
1682     case GAME_MODE_INFO:
1683       HandleInfoScreen(mx, my, 0, 0, button);
1684       break;
1685
1686     case GAME_MODE_SETUP:
1687       HandleSetupScreen(mx, my, 0, 0, button);
1688       break;
1689
1690     case GAME_MODE_PLAYING:
1691       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1692         HandleButtonOrFinger(mx, my, button);
1693       else
1694         SetPlayerMouseAction(mx, my, button);
1695
1696 #ifdef DEBUG
1697       if (button == MB_PRESSED && !motion_status && !button_hold &&
1698           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1699         DumpTileFromScreen(mx, my);
1700 #endif
1701
1702       break;
1703
1704     default:
1705       break;
1706   }
1707 }
1708
1709 static boolean is_string_suffix(char *string, char *suffix)
1710 {
1711   int string_len = strlen(string);
1712   int suffix_len = strlen(suffix);
1713
1714   if (suffix_len > string_len)
1715     return FALSE;
1716
1717   return (strEqual(&string[string_len - suffix_len], suffix));
1718 }
1719
1720 #define MAX_CHEAT_INPUT_LEN     32
1721
1722 static void HandleKeysSpecial(Key key)
1723 {
1724   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1725   char letter = getCharFromKey(key);
1726   int cheat_input_len = strlen(cheat_input);
1727   int i;
1728
1729   if (letter == 0)
1730     return;
1731
1732   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1733   {
1734     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1735       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1736
1737     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1738   }
1739
1740   cheat_input[cheat_input_len++] = letter;
1741   cheat_input[cheat_input_len] = '\0';
1742
1743 #if DEBUG_EVENTS_KEY
1744   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1745 #endif
1746
1747   if (game_status == GAME_MODE_MAIN)
1748   {
1749     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1750         is_string_suffix(cheat_input, ":ist"))
1751     {
1752       InsertSolutionTape();
1753     }
1754     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1755              is_string_suffix(cheat_input, ":pst"))
1756     {
1757       PlaySolutionTape();
1758     }
1759     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1760              is_string_suffix(cheat_input, ":rg"))
1761     {
1762       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1763       DrawMainMenu();
1764     }
1765     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1766              is_string_suffix(cheat_input, ":rs"))
1767     {
1768       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1769       DrawMainMenu();
1770     }
1771     else if (is_string_suffix(cheat_input, ":reload-music") ||
1772              is_string_suffix(cheat_input, ":rm"))
1773     {
1774       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1775       DrawMainMenu();
1776     }
1777     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1778              is_string_suffix(cheat_input, ":ra"))
1779     {
1780       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1781                           1 << ARTWORK_TYPE_SOUNDS |
1782                           1 << ARTWORK_TYPE_MUSIC);
1783       DrawMainMenu();
1784     }
1785     else if (is_string_suffix(cheat_input, ":dump-level") ||
1786              is_string_suffix(cheat_input, ":dl"))
1787     {
1788       DumpLevel(&level);
1789     }
1790     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1791              is_string_suffix(cheat_input, ":dt"))
1792     {
1793       DumpTape(&tape);
1794     }
1795     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1796              is_string_suffix(cheat_input, ":ft"))
1797     {
1798       /* fix single-player tapes that contain player input for more than one
1799          player (due to a bug in 3.3.1.2 and earlier versions), which results
1800          in playing levels with more than one player in multi-player mode,
1801          even though the tape was originally recorded in single-player mode */
1802
1803       // remove player input actions for all players but the first one
1804       for (i = 1; i < MAX_PLAYERS; i++)
1805         tape.player_participates[i] = FALSE;
1806
1807       tape.changed = TRUE;
1808     }
1809     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1810              is_string_suffix(cheat_input, ":snl"))
1811     {
1812       SaveNativeLevel(&level);
1813     }
1814     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1815              is_string_suffix(cheat_input, ":fps"))
1816     {
1817       global.show_frames_per_second = !global.show_frames_per_second;
1818     }
1819   }
1820   else if (game_status == GAME_MODE_PLAYING)
1821   {
1822 #ifdef DEBUG
1823     if (is_string_suffix(cheat_input, ".q"))
1824       DEBUG_SetMaximumDynamite();
1825 #endif
1826   }
1827   else if (game_status == GAME_MODE_EDITOR)
1828   {
1829     if (is_string_suffix(cheat_input, ":dump-brush") ||
1830         is_string_suffix(cheat_input, ":DB"))
1831     {
1832       DumpBrush();
1833     }
1834     else if (is_string_suffix(cheat_input, ":DDB"))
1835     {
1836       DumpBrush_Small();
1837     }
1838
1839     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1840     {
1841       if (letter == 'x')        // copy brush to clipboard (small size)
1842       {
1843         CopyBrushToClipboard_Small();
1844       }
1845       else if (letter == 'c')   // copy brush to clipboard (normal size)
1846       {
1847         CopyBrushToClipboard();
1848       }
1849       else if (letter == 'v')   // paste brush from Clipboard
1850       {
1851         CopyClipboardToBrush();
1852       }
1853     }
1854   }
1855
1856   // special key shortcuts for all game modes
1857   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1858       is_string_suffix(cheat_input, ":dea") ||
1859       is_string_suffix(cheat_input, ":DEA"))
1860   {
1861     DumpGadgetIdentifiers();
1862     DumpScreenIdentifiers();
1863   }
1864 }
1865
1866 boolean HandleKeysDebug(Key key, int key_status)
1867 {
1868 #ifdef DEBUG
1869   int i;
1870
1871   if (key_status != KEY_PRESSED)
1872     return FALSE;
1873
1874   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1875   {
1876     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1877
1878     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1879     {
1880       if (key == setup.debug.frame_delay_key[i] &&
1881           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1882       {
1883         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1884                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1885
1886         if (!setup.debug.frame_delay_game_only)
1887           MenuFrameDelay = GameFrameDelay;
1888
1889         SetVideoFrameDelay(GameFrameDelay);
1890
1891         if (GameFrameDelay > ONE_SECOND_DELAY)
1892           Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1893         else if (GameFrameDelay != 0)
1894           Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1895                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1896                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1897         else
1898           Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1899
1900         return TRUE;
1901       }
1902     }
1903   }
1904
1905   if (game_status == GAME_MODE_PLAYING)
1906   {
1907     if (key == KSYM_d)
1908     {
1909       options.debug = !options.debug;
1910
1911       Error(ERR_INFO, "debug mode %s",
1912             (options.debug ? "enabled" : "disabled"));
1913
1914       return TRUE;
1915     }
1916     else if (key == KSYM_v)
1917     {
1918       Error(ERR_INFO, "currently using game engine version %d",
1919             game.engine_version);
1920
1921       return TRUE;
1922     }
1923   }
1924 #endif
1925
1926   return FALSE;
1927 }
1928
1929 void HandleKey(Key key, int key_status)
1930 {
1931   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1932   static boolean ignore_repeated_key = FALSE;
1933   static struct SetupKeyboardInfo ski;
1934   static struct SetupShortcutInfo ssi;
1935   static struct
1936   {
1937     Key *key_custom;
1938     Key *key_snap;
1939     Key key_default;
1940     byte action;
1941   } key_info[] =
1942   {
1943     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1944     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1945     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1946     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1947     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1948     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1949   };
1950   int joy = 0;
1951   int i;
1952
1953   if (HandleKeysDebug(key, key_status))
1954     return;             // do not handle already processed keys again
1955
1956   // map special keys (media keys / remote control buttons) to default keys
1957   if (key == KSYM_PlayPause)
1958     key = KSYM_space;
1959   else if (key == KSYM_Select)
1960     key = KSYM_Return;
1961
1962   HandleSpecialGameControllerKeys(key, key_status);
1963
1964   if (game_status == GAME_MODE_PLAYING)
1965   {
1966     // only needed for single-step tape recording mode
1967     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1968     int pnr;
1969
1970     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1971     {
1972       byte key_action = 0;
1973       byte key_snap_action = 0;
1974
1975       if (setup.input[pnr].use_joystick)
1976         continue;
1977
1978       ski = setup.input[pnr].key;
1979
1980       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1981         if (key == *key_info[i].key_custom)
1982           key_action |= key_info[i].action;
1983
1984       // use combined snap+direction keys for the first player only
1985       if (pnr == 0)
1986       {
1987         ssi = setup.shortcut;
1988
1989         // also remember normal snap key when handling snap+direction keys
1990         key_snap_action |= key_action & JOY_BUTTON_SNAP;
1991
1992         for (i = 0; i < NUM_DIRECTIONS; i++)
1993         {
1994           if (key == *key_info[i].key_snap)
1995           {
1996             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
1997             key_snap_action |= key_info[i].action;
1998           }
1999         }
2000       }
2001
2002       if (key_status == KEY_PRESSED)
2003       {
2004         stored_player[pnr].action      |= key_action;
2005         stored_player[pnr].snap_action |= key_snap_action;
2006       }
2007       else
2008       {
2009         stored_player[pnr].action      &= ~key_action;
2010         stored_player[pnr].snap_action &= ~key_snap_action;
2011       }
2012
2013       // restore snap action if one of several pressed snap keys was released
2014       if (stored_player[pnr].snap_action)
2015         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2016
2017       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2018       {
2019         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2020         {
2021           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2022
2023           // if snap key already pressed, keep pause mode when releasing
2024           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2025             has_snapped[pnr] = TRUE;
2026         }
2027         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2028         {
2029           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2030
2031           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2032               getRedDiskReleaseFlag_SP() == 0)
2033           {
2034             // add a single inactive frame before dropping starts
2035             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2036             stored_player[pnr].force_dropping = TRUE;
2037           }
2038         }
2039         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2040         {
2041           // if snap key was pressed without direction, leave pause mode
2042           if (!has_snapped[pnr])
2043             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2044
2045           has_snapped[pnr] = FALSE;
2046         }
2047       }
2048       else if (tape.recording && tape.pausing && !tape.use_mouse)
2049       {
2050         // prevent key release events from un-pausing a paused game
2051         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2052           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2053       }
2054
2055       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2056       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2057         joy |= key_action;
2058     }
2059   }
2060   else
2061   {
2062     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2063       if (key == key_info[i].key_default)
2064         joy |= key_info[i].action;
2065   }
2066
2067   if (joy)
2068   {
2069     if (key_status == KEY_PRESSED)
2070       key_joystick_mapping |= joy;
2071     else
2072       key_joystick_mapping &= ~joy;
2073
2074     HandleJoystick();
2075   }
2076
2077   if (game_status != GAME_MODE_PLAYING)
2078     key_joystick_mapping = 0;
2079
2080   if (key_status == KEY_RELEASED)
2081   {
2082     // reset flag to ignore repeated "key pressed" events after key release
2083     ignore_repeated_key = FALSE;
2084
2085     return;
2086   }
2087
2088   if ((key == KSYM_F11 ||
2089        ((key == KSYM_Return ||
2090          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2091       video.fullscreen_available &&
2092       !ignore_repeated_key)
2093   {
2094     setup.fullscreen = !setup.fullscreen;
2095
2096     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2097
2098     if (game_status == GAME_MODE_SETUP)
2099       RedrawSetupScreenAfterFullscreenToggle();
2100
2101     // set flag to ignore repeated "key pressed" events
2102     ignore_repeated_key = TRUE;
2103
2104     return;
2105   }
2106
2107   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2108        key == KSYM_minus || key == KSYM_KP_Subtract ||
2109        key == KSYM_plus  || key == KSYM_KP_Add ||
2110        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2111       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2112       video.window_scaling_available &&
2113       !video.fullscreen_enabled)
2114   {
2115     if (key == KSYM_0 || key == KSYM_KP_0)
2116       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2117     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2118       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2119     else
2120       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2121
2122     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2123       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2124     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2125       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2126
2127     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2128
2129     if (game_status == GAME_MODE_SETUP)
2130       RedrawSetupScreenAfterFullscreenToggle();
2131
2132     return;
2133   }
2134
2135   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2136                                       key == KSYM_Return ||
2137                                       key == KSYM_Escape), TRUE))
2138   {
2139     // do not handle this key event anymore
2140     if (key != KSYM_Escape)     // always allow ESC key to be handled
2141       return;
2142   }
2143
2144   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2145       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2146   {
2147     GameEnd();
2148
2149     return;
2150   }
2151
2152   if (game_status == GAME_MODE_MAIN &&
2153       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2154   {
2155     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2156
2157     return;
2158   }
2159
2160   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2161   {
2162     if (key == setup.shortcut.save_game)
2163       TapeQuickSave();
2164     else if (key == setup.shortcut.load_game)
2165       TapeQuickLoad();
2166     else if (key == setup.shortcut.toggle_pause)
2167       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2168
2169     HandleTapeButtonKeys(key);
2170     HandleSoundButtonKeys(key);
2171   }
2172
2173   if (game_status == GAME_MODE_PLAYING && !network_playing)
2174   {
2175     int centered_player_nr_next = -999;
2176
2177     if (key == setup.shortcut.focus_player_all)
2178       centered_player_nr_next = -1;
2179     else
2180       for (i = 0; i < MAX_PLAYERS; i++)
2181         if (key == setup.shortcut.focus_player[i])
2182           centered_player_nr_next = i;
2183
2184     if (centered_player_nr_next != -999)
2185     {
2186       game.centered_player_nr_next = centered_player_nr_next;
2187       game.set_centered_player = TRUE;
2188
2189       if (tape.recording)
2190       {
2191         tape.centered_player_nr_next = game.centered_player_nr_next;
2192         tape.set_centered_player = TRUE;
2193       }
2194     }
2195   }
2196
2197   HandleKeysSpecial(key);
2198
2199   if (HandleGadgetsKeyInput(key))
2200     return;             // do not handle already processed keys again
2201
2202   switch (game_status)
2203   {
2204     case GAME_MODE_PSEUDO_TYPENAME:
2205       HandleTypeName(0, key);
2206       break;
2207
2208     case GAME_MODE_TITLE:
2209     case GAME_MODE_MAIN:
2210     case GAME_MODE_LEVELS:
2211     case GAME_MODE_LEVELNR:
2212     case GAME_MODE_SETUP:
2213     case GAME_MODE_INFO:
2214     case GAME_MODE_SCORES:
2215
2216       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2217         break;
2218
2219       switch (key)
2220       {
2221         case KSYM_space:
2222         case KSYM_Return:
2223           if (game_status == GAME_MODE_TITLE)
2224             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2225           else if (game_status == GAME_MODE_MAIN)
2226             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2227           else if (game_status == GAME_MODE_LEVELS)
2228             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2229           else if (game_status == GAME_MODE_LEVELNR)
2230             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2231           else if (game_status == GAME_MODE_SETUP)
2232             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2233           else if (game_status == GAME_MODE_INFO)
2234             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2235           else if (game_status == GAME_MODE_SCORES)
2236             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2237           break;
2238
2239         case KSYM_Escape:
2240           if (game_status != GAME_MODE_MAIN)
2241             FadeSkipNextFadeIn();
2242
2243           if (game_status == GAME_MODE_TITLE)
2244             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2245           else if (game_status == GAME_MODE_LEVELS)
2246             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2247           else if (game_status == GAME_MODE_LEVELNR)
2248             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2249           else if (game_status == GAME_MODE_SETUP)
2250             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2251           else if (game_status == GAME_MODE_INFO)
2252             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2253           else if (game_status == GAME_MODE_SCORES)
2254             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2255           break;
2256
2257         case KSYM_Page_Up:
2258           if (game_status == GAME_MODE_LEVELS)
2259             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2260           else if (game_status == GAME_MODE_LEVELNR)
2261             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2262           else if (game_status == GAME_MODE_SETUP)
2263             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2264           else if (game_status == GAME_MODE_INFO)
2265             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2266           else if (game_status == GAME_MODE_SCORES)
2267             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2268           break;
2269
2270         case KSYM_Page_Down:
2271           if (game_status == GAME_MODE_LEVELS)
2272             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2273           else if (game_status == GAME_MODE_LEVELNR)
2274             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2275           else if (game_status == GAME_MODE_SETUP)
2276             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2277           else if (game_status == GAME_MODE_INFO)
2278             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2279           else if (game_status == GAME_MODE_SCORES)
2280             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2281           break;
2282
2283         default:
2284           break;
2285       }
2286       break;
2287
2288     case GAME_MODE_EDITOR:
2289       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2290         HandleLevelEditorKeyInput(key);
2291       break;
2292
2293     case GAME_MODE_PLAYING:
2294     {
2295       switch (key)
2296       {
2297         case KSYM_Escape:
2298           RequestQuitGame(setup.ask_on_escape);
2299           break;
2300
2301         default:
2302           break;
2303       }
2304       break;
2305     }
2306
2307     default:
2308       if (key == KSYM_Escape)
2309       {
2310         SetGameStatus(GAME_MODE_MAIN);
2311
2312         DrawMainMenu();
2313
2314         return;
2315       }
2316   }
2317 }
2318
2319 void HandleNoEvent(void)
2320 {
2321   HandleMouseCursor();
2322
2323   switch (game_status)
2324   {
2325     case GAME_MODE_PLAYING:
2326       HandleButtonOrFinger(-1, -1, -1);
2327       break;
2328   }
2329 }
2330
2331 void HandleEventActions(void)
2332 {
2333   // if (button_status && game_status != GAME_MODE_PLAYING)
2334   if (button_status && (game_status != GAME_MODE_PLAYING ||
2335                         tape.pausing ||
2336                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2337   {
2338     HandleButton(0, 0, button_status, -button_status);
2339   }
2340   else
2341   {
2342     HandleJoystick();
2343   }
2344
2345   if (network.enabled)
2346     HandleNetworking();
2347
2348   switch (game_status)
2349   {
2350     case GAME_MODE_MAIN:
2351       DrawPreviewLevelAnimation();
2352       break;
2353
2354     case GAME_MODE_EDITOR:
2355       HandleLevelEditorIdle();
2356       break;
2357
2358     default:
2359       break;
2360   }
2361 }
2362
2363 static void HandleTileCursor(int dx, int dy, int button)
2364 {
2365   if (!dx || !button)
2366     ClearPlayerMouseAction();
2367
2368   if (!dx && !dy)
2369     return;
2370
2371   if (button)
2372   {
2373     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2374                          (dx < 0 ? MB_LEFTBUTTON :
2375                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2376   }
2377   else if (!tile_cursor.moving)
2378   {
2379     int old_xpos = tile_cursor.xpos;
2380     int old_ypos = tile_cursor.ypos;
2381     int new_xpos = old_xpos;
2382     int new_ypos = old_ypos;
2383
2384     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2385       new_xpos = old_xpos + dx;
2386
2387     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2388       new_ypos = old_ypos + dy;
2389
2390     SetTileCursorTargetXY(new_xpos, new_ypos);
2391   }
2392 }
2393
2394 static int HandleJoystickForAllPlayers(void)
2395 {
2396   int i;
2397   int result = 0;
2398   boolean no_joysticks_configured = TRUE;
2399   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2400   static byte joy_action_last[MAX_PLAYERS];
2401
2402   for (i = 0; i < MAX_PLAYERS; i++)
2403     if (setup.input[i].use_joystick)
2404       no_joysticks_configured = FALSE;
2405
2406   // if no joysticks configured, map connected joysticks to players
2407   if (no_joysticks_configured)
2408     use_as_joystick_nr = TRUE;
2409
2410   for (i = 0; i < MAX_PLAYERS; i++)
2411   {
2412     byte joy_action = 0;
2413
2414     joy_action = JoystickExt(i, use_as_joystick_nr);
2415     result |= joy_action;
2416
2417     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2418         joy_action != joy_action_last[i])
2419       stored_player[i].action = joy_action;
2420
2421     joy_action_last[i] = joy_action;
2422   }
2423
2424   return result;
2425 }
2426
2427 void HandleJoystick(void)
2428 {
2429   static unsigned int joytest_delay = 0;
2430   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2431   static int joytest_last = 0;
2432   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2433   int delay_value       = GADGET_FRAME_DELAY;
2434   int joystick  = HandleJoystickForAllPlayers();
2435   int keyboard  = key_joystick_mapping;
2436   int joy       = (joystick | keyboard);
2437   int joytest   = joystick;
2438   int left      = joy & JOY_LEFT;
2439   int right     = joy & JOY_RIGHT;
2440   int up        = joy & JOY_UP;
2441   int down      = joy & JOY_DOWN;
2442   int button    = joy & JOY_BUTTON;
2443   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2444   int dx        = (left ? -1    : right ? 1     : 0);
2445   int dy        = (up   ? -1    : down  ? 1     : 0);
2446   boolean use_delay_value_first = (joytest != joytest_last);
2447
2448   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2449   {
2450     // do not handle this button event anymore
2451     return;
2452   }
2453
2454   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2455                     anyTextGadgetActive()))
2456   {
2457     // leave name input in main menu or text input gadget
2458     HandleKey(KSYM_Escape, KEY_PRESSED);
2459     HandleKey(KSYM_Escape, KEY_RELEASED);
2460
2461     return;
2462   }
2463
2464   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2465   {
2466     if (game_status == GAME_MODE_PLAYING)
2467     {
2468       // when playing MM style levels, also use delay for keyboard events
2469       joytest |= keyboard;
2470
2471       // only use first delay value for new events, but not for changed events
2472       use_delay_value_first = (!joytest != !joytest_last);
2473
2474       // only use delay after the initial keyboard event
2475       delay_value = 0;
2476     }
2477
2478     // for any joystick or keyboard event, enable playfield tile cursor
2479     if (dx || dy || button)
2480       SetTileCursorEnabled(TRUE);
2481   }
2482
2483   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2484   {
2485     // delay joystick/keyboard actions if axes/keys continually pressed
2486     newbutton = dx = dy = 0;
2487   }
2488   else
2489   {
2490     // first start with longer delay, then continue with shorter delay
2491     joytest_delay_value =
2492       (use_delay_value_first ? delay_value_first : delay_value);
2493   }
2494
2495   joytest_last = joytest;
2496
2497   switch (game_status)
2498   {
2499     case GAME_MODE_TITLE:
2500     case GAME_MODE_MAIN:
2501     case GAME_MODE_LEVELS:
2502     case GAME_MODE_LEVELNR:
2503     case GAME_MODE_SETUP:
2504     case GAME_MODE_INFO:
2505     case GAME_MODE_SCORES:
2506     {
2507       if (anyTextGadgetActive())
2508         break;
2509
2510       if (game_status == GAME_MODE_TITLE)
2511         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2512       else if (game_status == GAME_MODE_MAIN)
2513         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2514       else if (game_status == GAME_MODE_LEVELS)
2515         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2516       else if (game_status == GAME_MODE_LEVELNR)
2517         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2518       else if (game_status == GAME_MODE_SETUP)
2519         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2520       else if (game_status == GAME_MODE_INFO)
2521         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2522       else if (game_status == GAME_MODE_SCORES)
2523         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2524
2525       break;
2526     }
2527
2528     case GAME_MODE_PLAYING:
2529 #if 0
2530       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2531       if (tape.playing || keyboard)
2532         newbutton = ((joy & JOY_BUTTON) != 0);
2533 #endif
2534
2535       if (newbutton && game.all_players_gone)
2536       {
2537         GameEnd();
2538
2539         return;
2540       }
2541
2542       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2543       {
2544         if (joystick & JOY_ACTION)
2545           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2546       }
2547       else if (tape.recording && tape.pausing && !tape.use_mouse)
2548       {
2549         if (joystick & JOY_ACTION)
2550           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2551       }
2552
2553       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2554         HandleTileCursor(dx, dy, button);
2555
2556       break;
2557
2558     default:
2559       break;
2560   }
2561 }
2562
2563 void HandleSpecialGameControllerButtons(Event *event)
2564 {
2565   int key_status;
2566   Key key;
2567
2568   switch (event->type)
2569   {
2570     case SDL_CONTROLLERBUTTONDOWN:
2571       key_status = KEY_PRESSED;
2572       break;
2573
2574     case SDL_CONTROLLERBUTTONUP:
2575       key_status = KEY_RELEASED;
2576       break;
2577
2578     default:
2579       return;
2580   }
2581
2582   switch (event->cbutton.button)
2583   {
2584     case SDL_CONTROLLER_BUTTON_START:
2585       key = KSYM_space;
2586       break;
2587
2588     case SDL_CONTROLLER_BUTTON_BACK:
2589       key = KSYM_Escape;
2590       break;
2591
2592     default:
2593       return;
2594   }
2595
2596   HandleKey(key, key_status);
2597 }
2598
2599 void HandleSpecialGameControllerKeys(Key key, int key_status)
2600 {
2601 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2602   int button = SDL_CONTROLLER_BUTTON_INVALID;
2603
2604   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2605   if (key == KSYM_Rewind)
2606     button = SDL_CONTROLLER_BUTTON_A;
2607   else if (key == KSYM_FastForward || key == KSYM_Menu)
2608     button = SDL_CONTROLLER_BUTTON_B;
2609
2610   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2611   {
2612     Event event;
2613
2614     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2615                   SDL_CONTROLLERBUTTONUP);
2616
2617     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2618     event.cbutton.button = button;
2619     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2620                            SDL_RELEASED);
2621
2622     HandleJoystickEvent(&event);
2623   }
2624 #endif
2625 }
2626
2627 boolean DoKeysymAction(int keysym)
2628 {
2629   if (keysym < 0)
2630   {
2631     Key key = (Key)(-keysym);
2632
2633     HandleKey(key, KEY_PRESSED);
2634     HandleKey(key, KEY_RELEASED);
2635
2636     return TRUE;
2637   }
2638
2639   return FALSE;
2640 }