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