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