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