removed obsolete window events 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 EVENT_CLIENTMESSAGE:
237       HandleClientMessageEvent((ClientMessageEvent *) event);
238       break;
239
240     case SDL_CONTROLLERBUTTONDOWN:
241     case SDL_CONTROLLERBUTTONUP:
242       // for any game controller button event, disable overlay buttons
243       SetOverlayEnabled(FALSE);
244
245       HandleSpecialGameControllerButtons(event);
246
247       // FALL THROUGH
248     case SDL_CONTROLLERDEVICEADDED:
249     case SDL_CONTROLLERDEVICEREMOVED:
250     case SDL_CONTROLLERAXISMOTION:
251     case SDL_JOYAXISMOTION:
252     case SDL_JOYBUTTONDOWN:
253     case SDL_JOYBUTTONUP:
254       HandleJoystickEvent(event);
255       break;
256
257     case SDL_DROPBEGIN:
258     case SDL_DROPCOMPLETE:
259     case SDL_DROPFILE:
260     case SDL_DROPTEXT:
261       HandleDropEvent(event);
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 void HandleClientMessageEvent(ClientMessageEvent *event)
1428 {
1429   if (CheckCloseWindowEvent(event))
1430     CloseAllAndExit(0);
1431 }
1432
1433 static int HandleDropFileEvent(char *filename)
1434 {
1435   Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1436
1437   // check and extract dropped zip files into correct user data directory
1438   if (!strSuffixLower(filename, ".zip"))
1439   {
1440     Error(ERR_WARN, "file '%s' not supported", filename);
1441
1442     return TREE_TYPE_UNDEFINED;
1443   }
1444
1445   TreeInfo *tree_node = NULL;
1446   int tree_type = GetZipFileTreeType(filename);
1447   char *directory = TREE_USERDIR(tree_type);
1448
1449   if (directory == NULL)
1450   {
1451     Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1452
1453     return TREE_TYPE_UNDEFINED;
1454   }
1455
1456   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1457       game_status == GAME_MODE_LEVELS &&
1458       leveldir_current->node_parent != NULL)
1459   {
1460     // extract new level set next to currently selected level set
1461     tree_node = leveldir_current;
1462
1463     // get parent directory of currently selected level set directory
1464     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1465
1466     // use private level directory instead of top-level package level directory
1467     if (strPrefix(directory, options.level_directory) &&
1468         strEqual(leveldir_current->node_parent->fullpath, "."))
1469       directory = getUserLevelDir(NULL);
1470   }
1471
1472   // extract level or artwork set from zip file to target directory
1473   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1474
1475   if (top_dir == NULL)
1476   {
1477     // error message already issued by "ExtractZipFileIntoDirectory()"
1478
1479     return TREE_TYPE_UNDEFINED;
1480   }
1481
1482   // add extracted level or artwork set to tree info structure
1483   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1484
1485   // update menu screen (and possibly change current level set)
1486   DrawScreenAfterAddingSet(top_dir, tree_type);
1487
1488   return tree_type;
1489 }
1490
1491 static void HandleDropTextEvent(char *text)
1492 {
1493   Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1494 }
1495
1496 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1497                                     int num_artwork_sets_succeeded,
1498                                     int num_files_failed)
1499 {
1500   // only show request dialog if no other request dialog already active
1501   if (game.request_active)
1502     return;
1503
1504   // this case can happen with drag-and-drop with older SDL versions
1505   if (num_level_sets_succeeded == 0 &&
1506       num_artwork_sets_succeeded == 0 &&
1507       num_files_failed == 0)
1508     return;
1509
1510   char message[100];
1511
1512   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1513   {
1514     char message_part1[50];
1515
1516     sprintf(message_part1, "New %s set%s added",
1517             (num_artwork_sets_succeeded == 0 ? "level" :
1518              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1519             (num_level_sets_succeeded +
1520              num_artwork_sets_succeeded > 1 ? "s" : ""));
1521
1522     if (num_files_failed > 0)
1523       sprintf(message, "%s, but %d dropped file%s failed!",
1524               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1525     else
1526       sprintf(message, "%s!", message_part1);
1527   }
1528   else if (num_files_failed > 0)
1529   {
1530     sprintf(message, "Failed to process dropped file%s!",
1531             num_files_failed > 1 ? "s" : "");
1532   }
1533
1534   Request(message, REQ_CONFIRM);
1535 }
1536
1537 void HandleDropEvent(Event *event)
1538 {
1539   static boolean confirm_on_drop_complete = FALSE;
1540   static int num_level_sets_succeeded = 0;
1541   static int num_artwork_sets_succeeded = 0;
1542   static int num_files_failed = 0;
1543
1544   switch (event->type)
1545   {
1546     case SDL_DROPBEGIN:
1547     {
1548       confirm_on_drop_complete = TRUE;
1549       num_level_sets_succeeded = 0;
1550       num_artwork_sets_succeeded = 0;
1551       num_files_failed = 0;
1552
1553       break;
1554     }
1555
1556     case SDL_DROPFILE:
1557     {
1558       int tree_type = HandleDropFileEvent(event->drop.file);
1559
1560       if (tree_type == TREE_TYPE_LEVEL_DIR)
1561         num_level_sets_succeeded++;
1562       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1563                tree_type == TREE_TYPE_SOUNDS_DIR ||
1564                tree_type == TREE_TYPE_MUSIC_DIR)
1565         num_artwork_sets_succeeded++;
1566       else
1567         num_files_failed++;
1568
1569       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1570       if (!confirm_on_drop_complete)
1571       {
1572         // process all remaining events, including further SDL_DROPFILE events
1573         ClearEventQueue();
1574
1575         HandleDropCompleteEvent(num_level_sets_succeeded,
1576                                 num_artwork_sets_succeeded,
1577                                 num_files_failed);
1578
1579         num_level_sets_succeeded = 0;
1580         num_artwork_sets_succeeded = 0;
1581         num_files_failed = 0;
1582       }
1583
1584       break;
1585     }
1586
1587     case SDL_DROPTEXT:
1588     {
1589       HandleDropTextEvent(event->drop.file);
1590
1591       break;
1592     }
1593
1594     case SDL_DROPCOMPLETE:
1595     {
1596       HandleDropCompleteEvent(num_level_sets_succeeded,
1597                               num_artwork_sets_succeeded,
1598                               num_files_failed);
1599
1600       break;
1601     }
1602   }
1603
1604   if (event->drop.file != NULL)
1605     SDL_free(event->drop.file);
1606 }
1607
1608 void HandleButton(int mx, int my, int button, int button_nr)
1609 {
1610   static int old_mx = 0, old_my = 0;
1611   boolean button_hold = FALSE;
1612   boolean handle_gadgets = TRUE;
1613
1614   if (button_nr < 0)
1615   {
1616     mx = old_mx;
1617     my = old_my;
1618     button_nr = -button_nr;
1619     button_hold = TRUE;
1620   }
1621   else
1622   {
1623     old_mx = mx;
1624     old_my = my;
1625   }
1626
1627 #if defined(PLATFORM_ANDROID)
1628   // when playing, only handle gadgets when using "follow finger" controls
1629   // or when using touch controls in combination with the MM game engine
1630   // or when using gadgets that do not overlap with virtual buttons
1631   handle_gadgets =
1632     (game_status != GAME_MODE_PLAYING ||
1633      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1634      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1635      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1636       !virtual_button_pressed));
1637 #endif
1638
1639   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1640   {
1641     // do not handle this button event anymore
1642     return;             // force mouse event not to be handled at all
1643   }
1644
1645   if (handle_gadgets && HandleGadgets(mx, my, button))
1646   {
1647     // do not handle this button event anymore
1648     mx = my = -32;      // force mouse event to be outside screen tiles
1649   }
1650
1651   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1652     return;
1653
1654   // do not use scroll wheel button events for anything other than gadgets
1655   if (IS_WHEEL_BUTTON(button_nr))
1656     return;
1657
1658   switch (game_status)
1659   {
1660     case GAME_MODE_TITLE:
1661       HandleTitleScreen(mx, my, 0, 0, button);
1662       break;
1663
1664     case GAME_MODE_MAIN:
1665       HandleMainMenu(mx, my, 0, 0, button);
1666       break;
1667
1668     case GAME_MODE_PSEUDO_TYPENAME:
1669       HandleTypeName(0, KSYM_Return);
1670       break;
1671
1672     case GAME_MODE_LEVELS:
1673       HandleChooseLevelSet(mx, my, 0, 0, button);
1674       break;
1675
1676     case GAME_MODE_LEVELNR:
1677       HandleChooseLevelNr(mx, my, 0, 0, button);
1678       break;
1679
1680     case GAME_MODE_SCORES:
1681       HandleHallOfFame(0, 0, 0, 0, button);
1682       break;
1683
1684     case GAME_MODE_EDITOR:
1685       HandleLevelEditorIdle();
1686       break;
1687
1688     case GAME_MODE_INFO:
1689       HandleInfoScreen(mx, my, 0, 0, button);
1690       break;
1691
1692     case GAME_MODE_SETUP:
1693       HandleSetupScreen(mx, my, 0, 0, button);
1694       break;
1695
1696     case GAME_MODE_PLAYING:
1697       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1698         HandleButtonOrFinger(mx, my, button);
1699       else
1700         SetPlayerMouseAction(mx, my, button);
1701
1702 #ifdef DEBUG
1703       if (button == MB_PRESSED && !motion_status && !button_hold &&
1704           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1705         DumpTileFromScreen(mx, my);
1706 #endif
1707
1708       break;
1709
1710     default:
1711       break;
1712   }
1713 }
1714
1715 static boolean is_string_suffix(char *string, char *suffix)
1716 {
1717   int string_len = strlen(string);
1718   int suffix_len = strlen(suffix);
1719
1720   if (suffix_len > string_len)
1721     return FALSE;
1722
1723   return (strEqual(&string[string_len - suffix_len], suffix));
1724 }
1725
1726 #define MAX_CHEAT_INPUT_LEN     32
1727
1728 static void HandleKeysSpecial(Key key)
1729 {
1730   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1731   char letter = getCharFromKey(key);
1732   int cheat_input_len = strlen(cheat_input);
1733   int i;
1734
1735   if (letter == 0)
1736     return;
1737
1738   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1739   {
1740     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1741       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1742
1743     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1744   }
1745
1746   cheat_input[cheat_input_len++] = letter;
1747   cheat_input[cheat_input_len] = '\0';
1748
1749 #if DEBUG_EVENTS_KEY
1750   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1751 #endif
1752
1753   if (game_status == GAME_MODE_MAIN)
1754   {
1755     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1756         is_string_suffix(cheat_input, ":ist"))
1757     {
1758       InsertSolutionTape();
1759     }
1760     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1761              is_string_suffix(cheat_input, ":pst"))
1762     {
1763       PlaySolutionTape();
1764     }
1765     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1766              is_string_suffix(cheat_input, ":rg"))
1767     {
1768       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1769       DrawMainMenu();
1770     }
1771     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1772              is_string_suffix(cheat_input, ":rs"))
1773     {
1774       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1775       DrawMainMenu();
1776     }
1777     else if (is_string_suffix(cheat_input, ":reload-music") ||
1778              is_string_suffix(cheat_input, ":rm"))
1779     {
1780       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1781       DrawMainMenu();
1782     }
1783     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1784              is_string_suffix(cheat_input, ":ra"))
1785     {
1786       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1787                           1 << ARTWORK_TYPE_SOUNDS |
1788                           1 << ARTWORK_TYPE_MUSIC);
1789       DrawMainMenu();
1790     }
1791     else if (is_string_suffix(cheat_input, ":dump-level") ||
1792              is_string_suffix(cheat_input, ":dl"))
1793     {
1794       DumpLevel(&level);
1795     }
1796     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1797              is_string_suffix(cheat_input, ":dt"))
1798     {
1799       DumpTape(&tape);
1800     }
1801     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1802              is_string_suffix(cheat_input, ":ft"))
1803     {
1804       /* fix single-player tapes that contain player input for more than one
1805          player (due to a bug in 3.3.1.2 and earlier versions), which results
1806          in playing levels with more than one player in multi-player mode,
1807          even though the tape was originally recorded in single-player mode */
1808
1809       // remove player input actions for all players but the first one
1810       for (i = 1; i < MAX_PLAYERS; i++)
1811         tape.player_participates[i] = FALSE;
1812
1813       tape.changed = TRUE;
1814     }
1815     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1816              is_string_suffix(cheat_input, ":snl"))
1817     {
1818       SaveNativeLevel(&level);
1819     }
1820     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1821              is_string_suffix(cheat_input, ":fps"))
1822     {
1823       global.show_frames_per_second = !global.show_frames_per_second;
1824     }
1825   }
1826   else if (game_status == GAME_MODE_PLAYING)
1827   {
1828 #ifdef DEBUG
1829     if (is_string_suffix(cheat_input, ".q"))
1830       DEBUG_SetMaximumDynamite();
1831 #endif
1832   }
1833   else if (game_status == GAME_MODE_EDITOR)
1834   {
1835     if (is_string_suffix(cheat_input, ":dump-brush") ||
1836         is_string_suffix(cheat_input, ":DB"))
1837     {
1838       DumpBrush();
1839     }
1840     else if (is_string_suffix(cheat_input, ":DDB"))
1841     {
1842       DumpBrush_Small();
1843     }
1844
1845     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1846     {
1847       if (letter == 'x')        // copy brush to clipboard (small size)
1848       {
1849         CopyBrushToClipboard_Small();
1850       }
1851       else if (letter == 'c')   // copy brush to clipboard (normal size)
1852       {
1853         CopyBrushToClipboard();
1854       }
1855       else if (letter == 'v')   // paste brush from Clipboard
1856       {
1857         CopyClipboardToBrush();
1858       }
1859     }
1860   }
1861
1862   // special key shortcuts for all game modes
1863   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1864       is_string_suffix(cheat_input, ":dea") ||
1865       is_string_suffix(cheat_input, ":DEA"))
1866   {
1867     DumpGadgetIdentifiers();
1868     DumpScreenIdentifiers();
1869   }
1870 }
1871
1872 boolean HandleKeysDebug(Key key, int key_status)
1873 {
1874 #ifdef DEBUG
1875   int i;
1876
1877   if (key_status != KEY_PRESSED)
1878     return FALSE;
1879
1880   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1881   {
1882     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1883
1884     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1885     {
1886       if (key == setup.debug.frame_delay_key[i] &&
1887           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1888       {
1889         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1890                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1891
1892         if (!setup.debug.frame_delay_game_only)
1893           MenuFrameDelay = GameFrameDelay;
1894
1895         SetVideoFrameDelay(GameFrameDelay);
1896
1897         if (GameFrameDelay > ONE_SECOND_DELAY)
1898           Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1899         else if (GameFrameDelay != 0)
1900           Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1901                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1902                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1903         else
1904           Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1905
1906         return TRUE;
1907       }
1908     }
1909   }
1910
1911   if (game_status == GAME_MODE_PLAYING)
1912   {
1913     if (key == KSYM_d)
1914     {
1915       options.debug = !options.debug;
1916
1917       Error(ERR_INFO, "debug mode %s",
1918             (options.debug ? "enabled" : "disabled"));
1919
1920       return TRUE;
1921     }
1922     else if (key == KSYM_v)
1923     {
1924       Error(ERR_INFO, "currently using game engine version %d",
1925             game.engine_version);
1926
1927       return TRUE;
1928     }
1929   }
1930 #endif
1931
1932   return FALSE;
1933 }
1934
1935 void HandleKey(Key key, int key_status)
1936 {
1937   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1938   static boolean ignore_repeated_key = FALSE;
1939   static struct SetupKeyboardInfo ski;
1940   static struct SetupShortcutInfo ssi;
1941   static struct
1942   {
1943     Key *key_custom;
1944     Key *key_snap;
1945     Key key_default;
1946     byte action;
1947   } key_info[] =
1948   {
1949     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1950     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1951     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1952     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1953     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1954     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1955   };
1956   int joy = 0;
1957   int i;
1958
1959   if (HandleKeysDebug(key, key_status))
1960     return;             // do not handle already processed keys again
1961
1962   // map special keys (media keys / remote control buttons) to default keys
1963   if (key == KSYM_PlayPause)
1964     key = KSYM_space;
1965   else if (key == KSYM_Select)
1966     key = KSYM_Return;
1967
1968   HandleSpecialGameControllerKeys(key, key_status);
1969
1970   if (game_status == GAME_MODE_PLAYING)
1971   {
1972     // only needed for single-step tape recording mode
1973     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1974     int pnr;
1975
1976     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1977     {
1978       byte key_action = 0;
1979       byte key_snap_action = 0;
1980
1981       if (setup.input[pnr].use_joystick)
1982         continue;
1983
1984       ski = setup.input[pnr].key;
1985
1986       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1987         if (key == *key_info[i].key_custom)
1988           key_action |= key_info[i].action;
1989
1990       // use combined snap+direction keys for the first player only
1991       if (pnr == 0)
1992       {
1993         ssi = setup.shortcut;
1994
1995         // also remember normal snap key when handling snap+direction keys
1996         key_snap_action |= key_action & JOY_BUTTON_SNAP;
1997
1998         for (i = 0; i < NUM_DIRECTIONS; i++)
1999         {
2000           if (key == *key_info[i].key_snap)
2001           {
2002             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2003             key_snap_action |= key_info[i].action;
2004           }
2005         }
2006       }
2007
2008       if (key_status == KEY_PRESSED)
2009       {
2010         stored_player[pnr].action      |= key_action;
2011         stored_player[pnr].snap_action |= key_snap_action;
2012       }
2013       else
2014       {
2015         stored_player[pnr].action      &= ~key_action;
2016         stored_player[pnr].snap_action &= ~key_snap_action;
2017       }
2018
2019       // restore snap action if one of several pressed snap keys was released
2020       if (stored_player[pnr].snap_action)
2021         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2022
2023       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2024       {
2025         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2026         {
2027           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2028
2029           // if snap key already pressed, keep pause mode when releasing
2030           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2031             has_snapped[pnr] = TRUE;
2032         }
2033         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2034         {
2035           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2036
2037           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2038               getRedDiskReleaseFlag_SP() == 0)
2039           {
2040             // add a single inactive frame before dropping starts
2041             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2042             stored_player[pnr].force_dropping = TRUE;
2043           }
2044         }
2045         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2046         {
2047           // if snap key was pressed without direction, leave pause mode
2048           if (!has_snapped[pnr])
2049             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2050
2051           has_snapped[pnr] = FALSE;
2052         }
2053       }
2054       else if (tape.recording && tape.pausing && !tape.use_mouse)
2055       {
2056         // prevent key release events from un-pausing a paused game
2057         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2058           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2059       }
2060
2061       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2062       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2063         joy |= key_action;
2064     }
2065   }
2066   else
2067   {
2068     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2069       if (key == key_info[i].key_default)
2070         joy |= key_info[i].action;
2071   }
2072
2073   if (joy)
2074   {
2075     if (key_status == KEY_PRESSED)
2076       key_joystick_mapping |= joy;
2077     else
2078       key_joystick_mapping &= ~joy;
2079
2080     HandleJoystick();
2081   }
2082
2083   if (game_status != GAME_MODE_PLAYING)
2084     key_joystick_mapping = 0;
2085
2086   if (key_status == KEY_RELEASED)
2087   {
2088     // reset flag to ignore repeated "key pressed" events after key release
2089     ignore_repeated_key = FALSE;
2090
2091     return;
2092   }
2093
2094   if ((key == KSYM_F11 ||
2095        ((key == KSYM_Return ||
2096          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2097       video.fullscreen_available &&
2098       !ignore_repeated_key)
2099   {
2100     setup.fullscreen = !setup.fullscreen;
2101
2102     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2103
2104     if (game_status == GAME_MODE_SETUP)
2105       RedrawSetupScreenAfterFullscreenToggle();
2106
2107     // set flag to ignore repeated "key pressed" events
2108     ignore_repeated_key = TRUE;
2109
2110     return;
2111   }
2112
2113   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2114        key == KSYM_minus || key == KSYM_KP_Subtract ||
2115        key == KSYM_plus  || key == KSYM_KP_Add ||
2116        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2117       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2118       video.window_scaling_available &&
2119       !video.fullscreen_enabled)
2120   {
2121     if (key == KSYM_0 || key == KSYM_KP_0)
2122       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2123     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2124       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2125     else
2126       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2127
2128     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2129       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2130     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2131       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2132
2133     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2134
2135     if (game_status == GAME_MODE_SETUP)
2136       RedrawSetupScreenAfterFullscreenToggle();
2137
2138     return;
2139   }
2140
2141   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2142                                       key == KSYM_Return ||
2143                                       key == KSYM_Escape), TRUE))
2144   {
2145     // do not handle this key event anymore
2146     if (key != KSYM_Escape)     // always allow ESC key to be handled
2147       return;
2148   }
2149
2150   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2151       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2152   {
2153     GameEnd();
2154
2155     return;
2156   }
2157
2158   if (game_status == GAME_MODE_MAIN &&
2159       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2160   {
2161     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2162
2163     return;
2164   }
2165
2166   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2167   {
2168     if (key == setup.shortcut.save_game)
2169       TapeQuickSave();
2170     else if (key == setup.shortcut.load_game)
2171       TapeQuickLoad();
2172     else if (key == setup.shortcut.toggle_pause)
2173       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2174
2175     HandleTapeButtonKeys(key);
2176     HandleSoundButtonKeys(key);
2177   }
2178
2179   if (game_status == GAME_MODE_PLAYING && !network_playing)
2180   {
2181     int centered_player_nr_next = -999;
2182
2183     if (key == setup.shortcut.focus_player_all)
2184       centered_player_nr_next = -1;
2185     else
2186       for (i = 0; i < MAX_PLAYERS; i++)
2187         if (key == setup.shortcut.focus_player[i])
2188           centered_player_nr_next = i;
2189
2190     if (centered_player_nr_next != -999)
2191     {
2192       game.centered_player_nr_next = centered_player_nr_next;
2193       game.set_centered_player = TRUE;
2194
2195       if (tape.recording)
2196       {
2197         tape.centered_player_nr_next = game.centered_player_nr_next;
2198         tape.set_centered_player = TRUE;
2199       }
2200     }
2201   }
2202
2203   HandleKeysSpecial(key);
2204
2205   if (HandleGadgetsKeyInput(key))
2206     return;             // do not handle already processed keys again
2207
2208   switch (game_status)
2209   {
2210     case GAME_MODE_PSEUDO_TYPENAME:
2211       HandleTypeName(0, key);
2212       break;
2213
2214     case GAME_MODE_TITLE:
2215     case GAME_MODE_MAIN:
2216     case GAME_MODE_LEVELS:
2217     case GAME_MODE_LEVELNR:
2218     case GAME_MODE_SETUP:
2219     case GAME_MODE_INFO:
2220     case GAME_MODE_SCORES:
2221
2222       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2223         break;
2224
2225       switch (key)
2226       {
2227         case KSYM_space:
2228         case KSYM_Return:
2229           if (game_status == GAME_MODE_TITLE)
2230             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2231           else if (game_status == GAME_MODE_MAIN)
2232             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2233           else if (game_status == GAME_MODE_LEVELS)
2234             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2235           else if (game_status == GAME_MODE_LEVELNR)
2236             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2237           else if (game_status == GAME_MODE_SETUP)
2238             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2239           else if (game_status == GAME_MODE_INFO)
2240             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2241           else if (game_status == GAME_MODE_SCORES)
2242             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2243           break;
2244
2245         case KSYM_Escape:
2246           if (game_status != GAME_MODE_MAIN)
2247             FadeSkipNextFadeIn();
2248
2249           if (game_status == GAME_MODE_TITLE)
2250             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2251           else if (game_status == GAME_MODE_LEVELS)
2252             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2253           else if (game_status == GAME_MODE_LEVELNR)
2254             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2255           else if (game_status == GAME_MODE_SETUP)
2256             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2257           else if (game_status == GAME_MODE_INFO)
2258             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2259           else if (game_status == GAME_MODE_SCORES)
2260             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2261           break;
2262
2263         case KSYM_Page_Up:
2264           if (game_status == GAME_MODE_LEVELS)
2265             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2266           else if (game_status == GAME_MODE_LEVELNR)
2267             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2268           else if (game_status == GAME_MODE_SETUP)
2269             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2270           else if (game_status == GAME_MODE_INFO)
2271             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2272           else if (game_status == GAME_MODE_SCORES)
2273             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2274           break;
2275
2276         case KSYM_Page_Down:
2277           if (game_status == GAME_MODE_LEVELS)
2278             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2279           else if (game_status == GAME_MODE_LEVELNR)
2280             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2281           else if (game_status == GAME_MODE_SETUP)
2282             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2283           else if (game_status == GAME_MODE_INFO)
2284             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2285           else if (game_status == GAME_MODE_SCORES)
2286             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2287           break;
2288
2289         default:
2290           break;
2291       }
2292       break;
2293
2294     case GAME_MODE_EDITOR:
2295       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2296         HandleLevelEditorKeyInput(key);
2297       break;
2298
2299     case GAME_MODE_PLAYING:
2300     {
2301       switch (key)
2302       {
2303         case KSYM_Escape:
2304           RequestQuitGame(setup.ask_on_escape);
2305           break;
2306
2307         default:
2308           break;
2309       }
2310       break;
2311     }
2312
2313     default:
2314       if (key == KSYM_Escape)
2315       {
2316         SetGameStatus(GAME_MODE_MAIN);
2317
2318         DrawMainMenu();
2319
2320         return;
2321       }
2322   }
2323 }
2324
2325 void HandleNoEvent(void)
2326 {
2327   HandleMouseCursor();
2328
2329   switch (game_status)
2330   {
2331     case GAME_MODE_PLAYING:
2332       HandleButtonOrFinger(-1, -1, -1);
2333       break;
2334   }
2335 }
2336
2337 void HandleEventActions(void)
2338 {
2339   // if (button_status && game_status != GAME_MODE_PLAYING)
2340   if (button_status && (game_status != GAME_MODE_PLAYING ||
2341                         tape.pausing ||
2342                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2343   {
2344     HandleButton(0, 0, button_status, -button_status);
2345   }
2346   else
2347   {
2348     HandleJoystick();
2349   }
2350
2351   if (network.enabled)
2352     HandleNetworking();
2353
2354   switch (game_status)
2355   {
2356     case GAME_MODE_MAIN:
2357       DrawPreviewLevelAnimation();
2358       break;
2359
2360     case GAME_MODE_EDITOR:
2361       HandleLevelEditorIdle();
2362       break;
2363
2364     default:
2365       break;
2366   }
2367 }
2368
2369 static void HandleTileCursor(int dx, int dy, int button)
2370 {
2371   if (!dx || !button)
2372     ClearPlayerMouseAction();
2373
2374   if (!dx && !dy)
2375     return;
2376
2377   if (button)
2378   {
2379     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2380                          (dx < 0 ? MB_LEFTBUTTON :
2381                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2382   }
2383   else if (!tile_cursor.moving)
2384   {
2385     int old_xpos = tile_cursor.xpos;
2386     int old_ypos = tile_cursor.ypos;
2387     int new_xpos = old_xpos;
2388     int new_ypos = old_ypos;
2389
2390     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2391       new_xpos = old_xpos + dx;
2392
2393     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2394       new_ypos = old_ypos + dy;
2395
2396     SetTileCursorTargetXY(new_xpos, new_ypos);
2397   }
2398 }
2399
2400 static int HandleJoystickForAllPlayers(void)
2401 {
2402   int i;
2403   int result = 0;
2404   boolean no_joysticks_configured = TRUE;
2405   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2406   static byte joy_action_last[MAX_PLAYERS];
2407
2408   for (i = 0; i < MAX_PLAYERS; i++)
2409     if (setup.input[i].use_joystick)
2410       no_joysticks_configured = FALSE;
2411
2412   // if no joysticks configured, map connected joysticks to players
2413   if (no_joysticks_configured)
2414     use_as_joystick_nr = TRUE;
2415
2416   for (i = 0; i < MAX_PLAYERS; i++)
2417   {
2418     byte joy_action = 0;
2419
2420     joy_action = JoystickExt(i, use_as_joystick_nr);
2421     result |= joy_action;
2422
2423     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2424         joy_action != joy_action_last[i])
2425       stored_player[i].action = joy_action;
2426
2427     joy_action_last[i] = joy_action;
2428   }
2429
2430   return result;
2431 }
2432
2433 void HandleJoystick(void)
2434 {
2435   static unsigned int joytest_delay = 0;
2436   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2437   static int joytest_last = 0;
2438   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2439   int delay_value       = GADGET_FRAME_DELAY;
2440   int joystick  = HandleJoystickForAllPlayers();
2441   int keyboard  = key_joystick_mapping;
2442   int joy       = (joystick | keyboard);
2443   int joytest   = joystick;
2444   int left      = joy & JOY_LEFT;
2445   int right     = joy & JOY_RIGHT;
2446   int up        = joy & JOY_UP;
2447   int down      = joy & JOY_DOWN;
2448   int button    = joy & JOY_BUTTON;
2449   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2450   int dx        = (left ? -1    : right ? 1     : 0);
2451   int dy        = (up   ? -1    : down  ? 1     : 0);
2452   boolean use_delay_value_first = (joytest != joytest_last);
2453
2454   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2455   {
2456     // do not handle this button event anymore
2457     return;
2458   }
2459
2460   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2461                     anyTextGadgetActive()))
2462   {
2463     // leave name input in main menu or text input gadget
2464     HandleKey(KSYM_Escape, KEY_PRESSED);
2465     HandleKey(KSYM_Escape, KEY_RELEASED);
2466
2467     return;
2468   }
2469
2470   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2471   {
2472     if (game_status == GAME_MODE_PLAYING)
2473     {
2474       // when playing MM style levels, also use delay for keyboard events
2475       joytest |= keyboard;
2476
2477       // only use first delay value for new events, but not for changed events
2478       use_delay_value_first = (!joytest != !joytest_last);
2479
2480       // only use delay after the initial keyboard event
2481       delay_value = 0;
2482     }
2483
2484     // for any joystick or keyboard event, enable playfield tile cursor
2485     if (dx || dy || button)
2486       SetTileCursorEnabled(TRUE);
2487   }
2488
2489   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2490   {
2491     // delay joystick/keyboard actions if axes/keys continually pressed
2492     newbutton = dx = dy = 0;
2493   }
2494   else
2495   {
2496     // first start with longer delay, then continue with shorter delay
2497     joytest_delay_value =
2498       (use_delay_value_first ? delay_value_first : delay_value);
2499   }
2500
2501   joytest_last = joytest;
2502
2503   switch (game_status)
2504   {
2505     case GAME_MODE_TITLE:
2506     case GAME_MODE_MAIN:
2507     case GAME_MODE_LEVELS:
2508     case GAME_MODE_LEVELNR:
2509     case GAME_MODE_SETUP:
2510     case GAME_MODE_INFO:
2511     case GAME_MODE_SCORES:
2512     {
2513       if (anyTextGadgetActive())
2514         break;
2515
2516       if (game_status == GAME_MODE_TITLE)
2517         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2518       else if (game_status == GAME_MODE_MAIN)
2519         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2520       else if (game_status == GAME_MODE_LEVELS)
2521         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2522       else if (game_status == GAME_MODE_LEVELNR)
2523         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2524       else if (game_status == GAME_MODE_SETUP)
2525         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2526       else if (game_status == GAME_MODE_INFO)
2527         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2528       else if (game_status == GAME_MODE_SCORES)
2529         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2530
2531       break;
2532     }
2533
2534     case GAME_MODE_PLAYING:
2535 #if 0
2536       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2537       if (tape.playing || keyboard)
2538         newbutton = ((joy & JOY_BUTTON) != 0);
2539 #endif
2540
2541       if (newbutton && game.all_players_gone)
2542       {
2543         GameEnd();
2544
2545         return;
2546       }
2547
2548       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2549       {
2550         if (joystick & JOY_ACTION)
2551           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2552       }
2553       else if (tape.recording && tape.pausing && !tape.use_mouse)
2554       {
2555         if (joystick & JOY_ACTION)
2556           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2557       }
2558
2559       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2560         HandleTileCursor(dx, dy, button);
2561
2562       break;
2563
2564     default:
2565       break;
2566   }
2567 }
2568
2569 void HandleSpecialGameControllerButtons(Event *event)
2570 {
2571   int key_status;
2572   Key key;
2573
2574   switch (event->type)
2575   {
2576     case SDL_CONTROLLERBUTTONDOWN:
2577       key_status = KEY_PRESSED;
2578       break;
2579
2580     case SDL_CONTROLLERBUTTONUP:
2581       key_status = KEY_RELEASED;
2582       break;
2583
2584     default:
2585       return;
2586   }
2587
2588   switch (event->cbutton.button)
2589   {
2590     case SDL_CONTROLLER_BUTTON_START:
2591       key = KSYM_space;
2592       break;
2593
2594     case SDL_CONTROLLER_BUTTON_BACK:
2595       key = KSYM_Escape;
2596       break;
2597
2598     default:
2599       return;
2600   }
2601
2602   HandleKey(key, key_status);
2603 }
2604
2605 void HandleSpecialGameControllerKeys(Key key, int key_status)
2606 {
2607 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2608   int button = SDL_CONTROLLER_BUTTON_INVALID;
2609
2610   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2611   if (key == KSYM_Rewind)
2612     button = SDL_CONTROLLER_BUTTON_A;
2613   else if (key == KSYM_FastForward || key == KSYM_Menu)
2614     button = SDL_CONTROLLER_BUTTON_B;
2615
2616   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2617   {
2618     Event event;
2619
2620     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2621                   SDL_CONTROLLERBUTTONUP);
2622
2623     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2624     event.cbutton.button = button;
2625     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2626                            SDL_RELEASED);
2627
2628     HandleJoystickEvent(&event);
2629   }
2630 #endif
2631 }
2632
2633 boolean DoKeysymAction(int keysym)
2634 {
2635   if (keysym < 0)
2636   {
2637     Key key = (Key)(-keysym);
2638
2639     HandleKey(key, KEY_PRESSED);
2640     HandleKey(key, KEY_RELEASED);
2641
2642     return TRUE;
2643   }
2644
2645   return FALSE;
2646 }