rnd-20140515-2-src
[rocksndiamonds.git] / src / events.c
1 /***********************************************************
2 * Rocks'n'Diamonds -- McDuffin Strikes Back!               *
3 *----------------------------------------------------------*
4 * (c) 1995-2006 Artsoft Entertainment                      *
5 *               Holger Schemel                             *
6 *               Detmolder Strasse 189                      *
7 *               33604 Bielefeld                            *
8 *               Germany                                    *
9 *               e-mail: info@artsoft.org                   *
10 *----------------------------------------------------------*
11 * events.c                                                 *
12 ***********************************************************/
13
14 #include "libgame/libgame.h"
15
16 #include "events.h"
17 #include "init.h"
18 #include "screens.h"
19 #include "tools.h"
20 #include "game.h"
21 #include "editor.h"
22 #include "files.h"
23 #include "tape.h"
24 #include "network.h"
25
26
27 #define DEBUG_EVENTS            0
28
29
30 static boolean cursor_inside_playfield = FALSE;
31 static boolean playfield_cursor_set = FALSE;
32 static unsigned int playfield_cursor_delay = 0;
33
34
35 /* event filter especially needed for SDL event filtering due to
36    delay problems with lots of mouse motion events when mouse button
37    not pressed (X11 can handle this with 'PointerMotionHintMask') */
38
39 /* event filter addition for SDL2: as SDL2 does not have a function to enable
40    or disable keyboard auto-repeat, filter repeated keyboard events instead */
41
42 static int FilterEventsExt(const Event *event)
43 {
44   MotionEvent *motion;
45
46 #if defined(TARGET_SDL2)
47   /* skip repeated key press events if keyboard auto-repeat is disabled */
48   if (event->type == EVENT_KEYPRESS &&
49       event->key.repeat &&
50       !keyrepeat_status)
51     return 0;
52 #endif
53
54   /* non-motion events are directly passed to event handler functions */
55   if (event->type != EVENT_MOTIONNOTIFY)
56     return 1;
57
58   motion = (MotionEvent *)event;
59   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
60                              motion->y >= SY && motion->y < SY + SYSIZE);
61
62   if (game_status == GAME_MODE_PLAYING && playfield_cursor_set)
63   {
64     SetMouseCursor(CURSOR_DEFAULT);
65     playfield_cursor_set = FALSE;
66     DelayReached(&playfield_cursor_delay, 0);
67   }
68
69   /* skip mouse motion events without pressed button outside level editor */
70   if (button_status == MB_RELEASED &&
71       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
72     return 0;
73
74   return 1;
75 }
76
77 #if defined(TARGET_SDL2)
78 int FilterEvents(void *userdata, Event *event)
79 {
80   return FilterEventsExt(event);
81 }
82 #else
83 int FilterEvents(const Event *event)
84 {
85   return FilterEventsExt(event);
86 }
87 #endif
88
89 /* to prevent delay problems, skip mouse motion events if the very next
90    event is also a mouse motion event (and therefore effectively only
91    handling the last of a row of mouse motion events in the event queue) */
92
93 boolean SkipPressedMouseMotionEvent(const Event *event)
94 {
95   /* nothing to do if the current event is not a mouse motion event */
96   if (event->type != EVENT_MOTIONNOTIFY)
97     return FALSE;
98
99   /* only skip motion events with pressed button outside level editor */
100   if (button_status == MB_RELEASED ||
101       game_status == GAME_MODE_EDITOR || game_status == GAME_MODE_PLAYING)
102     return FALSE;
103
104   if (PendingEvent())
105   {
106     Event next_event;
107
108     PeekEvent(&next_event);
109
110     /* if next event is also a mouse motion event, skip the current one */
111     if (next_event.type == EVENT_MOTIONNOTIFY)
112       return TRUE;
113   }
114
115   return FALSE;
116 }
117
118 /* this is only really needed for non-SDL targets to filter unwanted events;
119    when using SDL with properly installed event filter, this function can be
120    replaced with a simple "NextEvent()" call, but it doesn't hurt either */
121
122 static boolean NextValidEvent(Event *event)
123 {
124   while (PendingEvent())
125   {
126     boolean handle_this_event = FALSE;
127
128     NextEvent(event);
129
130     if (FilterEventsExt(event))
131       handle_this_event = TRUE;
132
133     if (SkipPressedMouseMotionEvent(event))
134       handle_this_event = FALSE;
135
136     if (handle_this_event)
137       return TRUE;
138   }
139
140   return FALSE;
141 }
142
143 void EventLoop(void)
144 {
145   while (1)
146   {
147     if (PendingEvent())         /* got event */
148     {
149       Event event;
150
151       while (NextValidEvent(&event))
152       {
153         switch (event.type)
154         {
155           case EVENT_BUTTONPRESS:
156           case EVENT_BUTTONRELEASE:
157             HandleButtonEvent((ButtonEvent *) &event);
158             break;
159   
160           case EVENT_MOTIONNOTIFY:
161             HandleMotionEvent((MotionEvent *) &event);
162             break;
163
164 #if defined(TARGET_SDL2)
165           case SDL_WINDOWEVENT:
166             HandleWindowEvent((WindowEvent *) &event);
167             break;
168
169           case EVENT_FINGERPRESS:
170           case EVENT_FINGERRELEASE:
171           case EVENT_FINGERMOTION:
172             HandleFingerEvent((FingerEvent *) &event);
173             break;
174
175           case EVENT_TEXTINPUT:
176             HandleTextEvent((TextEvent *) &event);
177             break;
178 #endif
179
180           case EVENT_KEYPRESS:
181           case EVENT_KEYRELEASE:
182             HandleKeyEvent((KeyEvent *) &event);
183             break;
184
185           default:
186             HandleOtherEvents(&event);
187             break;
188         }
189       }
190     }
191     else
192     {
193       /* when playing, display a special mouse pointer inside the playfield */
194       if (game_status == GAME_MODE_PLAYING && !tape.pausing)
195       {
196         if (!playfield_cursor_set && cursor_inside_playfield &&
197             DelayReached(&playfield_cursor_delay, 1000))
198         {
199           SetMouseCursor(CURSOR_PLAYFIELD);
200           playfield_cursor_set = TRUE;
201         }
202       }
203       else if (playfield_cursor_set)
204       {
205         SetMouseCursor(CURSOR_DEFAULT);
206         playfield_cursor_set = FALSE;
207       }
208
209 #if 0
210       HandleNoEvent();
211 #endif
212     }
213
214 #if 1
215     HandleNoEvent();
216 #endif
217
218     /* don't use all CPU time when idle; the main loop while playing
219        has its own synchronization and is CPU friendly, too */
220
221     if (game_status == GAME_MODE_PLAYING)
222     {
223       HandleGameActions();
224     }
225     else
226     {
227       SyncDisplay();
228       if (!PendingEvent())      /* delay only if no pending events */
229         Delay(10);
230     }
231
232     /* refresh window contents from drawing buffer, if needed */
233     BackToFront();
234
235     if (game_status == GAME_MODE_QUIT)
236       return;
237   }
238 }
239
240 void HandleOtherEvents(Event *event)
241 {
242   switch (event->type)
243   {
244     case EVENT_EXPOSE:
245       HandleExposeEvent((ExposeEvent *) event);
246       break;
247
248     case EVENT_UNMAPNOTIFY:
249 #if 0
250       /* This causes the game to stop not only when iconified, but also
251          when on another virtual desktop, which might be not desired. */
252       SleepWhileUnmapped();
253 #endif
254       break;
255
256     case EVENT_FOCUSIN:
257     case EVENT_FOCUSOUT:
258       HandleFocusEvent((FocusChangeEvent *) event);
259       break;
260
261     case EVENT_CLIENTMESSAGE:
262       HandleClientMessageEvent((ClientMessageEvent *) event);
263       break;
264
265 #if defined(TARGET_SDL)
266     case SDL_JOYAXISMOTION:
267     case SDL_JOYBUTTONDOWN:
268     case SDL_JOYBUTTONUP:
269       HandleJoystickEvent(event);
270       break;
271
272     case SDL_SYSWMEVENT:
273       HandleWindowManagerEvent(event);
274       break;
275 #endif
276
277     default:
278       break;
279   }
280 }
281
282 void ClearEventQueue()
283 {
284   while (PendingEvent())
285   {
286     Event event;
287
288     NextEvent(&event);
289
290     switch (event.type)
291     {
292       case EVENT_BUTTONRELEASE:
293         button_status = MB_RELEASED;
294         break;
295
296       case EVENT_KEYRELEASE:
297 #if 1
298         ClearPlayerAction();
299 #else
300         key_joystick_mapping = 0;
301 #endif
302         break;
303
304       default:
305         HandleOtherEvents(&event);
306         break;
307     }
308   }
309 }
310
311 void ClearPlayerAction()
312 {
313   int i;
314
315   /* simulate key release events for still pressed keys */
316   key_joystick_mapping = 0;
317   for (i = 0; i < MAX_PLAYERS; i++)
318     stored_player[i].action = 0;
319 }
320
321 void SleepWhileUnmapped()
322 {
323   boolean window_unmapped = TRUE;
324
325   KeyboardAutoRepeatOn();
326
327   while (window_unmapped)
328   {
329     Event event;
330
331     NextEvent(&event);
332
333     switch (event.type)
334     {
335       case EVENT_BUTTONRELEASE:
336         button_status = MB_RELEASED;
337         break;
338
339       case EVENT_KEYRELEASE:
340         key_joystick_mapping = 0;
341         break;
342
343       case EVENT_MAPNOTIFY:
344         window_unmapped = FALSE;
345         break;
346
347       case EVENT_UNMAPNOTIFY:
348         /* this is only to surely prevent the 'should not happen' case
349          * of recursively looping between 'SleepWhileUnmapped()' and
350          * 'HandleOtherEvents()' which usually calls this funtion.
351          */
352         break;
353
354       default:
355         HandleOtherEvents(&event);
356         break;
357     }
358   }
359
360   if (game_status == GAME_MODE_PLAYING)
361     KeyboardAutoRepeatOffUnlessAutoplay();
362 }
363
364 void HandleExposeEvent(ExposeEvent *event)
365 {
366 }
367
368 void HandleButtonEvent(ButtonEvent *event)
369 {
370 #if DEBUG_EVENTS
371   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
372         event->button,
373         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
374         event->x, event->y);
375 #endif
376
377   motion_status = FALSE;
378
379   if (event->type == EVENT_BUTTONPRESS)
380     button_status = event->button;
381   else
382     button_status = MB_RELEASED;
383
384   HandleButton(event->x, event->y, button_status, event->button);
385 }
386
387 void HandleMotionEvent(MotionEvent *event)
388 {
389   if (!PointerInWindow(window))
390     return;     /* window and pointer are on different screens */
391
392   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
393     return;
394
395   motion_status = TRUE;
396
397 #if DEBUG_EVENTS
398   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
399         button_status, event->x, event->y);
400 #endif
401
402   HandleButton(event->x, event->y, button_status, button_status);
403 }
404
405 #if defined(TARGET_SDL2)
406 void HandleWindowEvent(WindowEvent *event)
407 {
408 #if DEBUG_EVENTS
409   int subtype = event->event;
410
411   char *event_name =
412     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
413      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
414      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
415      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
416      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
417      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
418      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
419      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
420      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
421      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
422      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
423      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
424      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
425      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
426      "(UNKNOWN)");
427
428   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
429         event_name, event->data1, event->data2);
430 #endif
431
432   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
433       event->event == SDL_WINDOWEVENT_RESIZED ||
434       event->event == SDL_WINDOWEVENT_EXPOSED)
435     SDLRedrawWindow();
436
437 #if 0
438   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED)
439   {
440     // if game started in fullscreen mode, window will also get fullscreen size
441     if (!video.fullscreen_enabled && video.fullscreen_initial)
442     {
443       SDLSetWindowScaling(setup.window_scaling_percent);
444
445       // only do this correction once
446       video.fullscreen_initial = FALSE;
447     }
448   }
449 #endif
450
451   if (event->event == SDL_WINDOWEVENT_RESIZED && !video.fullscreen_enabled)
452   {
453 #if 1
454     int new_window_width  = event->data1;
455     int new_window_height = event->data2;
456
457     // if window size has changed after resizing, calculate new scaling factor
458     if (new_window_width  != video.window_width ||
459         new_window_height != video.window_height)
460     {
461       int new_xpercent = (100 * new_window_width  / video.width);
462       int new_ypercent = (100 * new_window_height / video.height);
463
464 #if 0
465       printf("::: RESIZED from %d, %d to %d, %d\n",
466              video.window_width, video.window_height,
467              new_window_width, new_window_height);
468 #endif
469
470       setup.window_scaling_percent = video.window_scaling_percent =
471         MIN(MAX(MIN_WINDOW_SCALING_PERCENT, MIN(new_xpercent, new_ypercent)),
472             MAX_WINDOW_SCALING_PERCENT);
473
474       video.window_width  = new_window_width;
475       video.window_height = new_window_height;
476
477 #if 0
478       printf("::: setup.window_scaling_percent set to %d\n",
479              setup.window_scaling_percent);
480 #endif
481
482       if (game_status == GAME_MODE_SETUP)
483         RedrawSetupScreenAfterFullscreenToggle();
484
485       SetWindowTitle();
486     }
487 #else
488     // prevent slightly wrong scaling factor due to rounding differences
489     float scaling_factor = (float)setup.window_scaling_percent / 100;
490     int old_xsize = (int)(scaling_factor * video.width);
491     int old_ysize = (int)(scaling_factor * video.height);
492     int new_xsize = event->data1;
493     int new_ysize = event->data2;
494
495     // window size is unchanged when going from fullscreen to window mode,
496     // but reverse calculation of scaling factor might result in a scaling
497     // factor that is slightly different due to rounding differences;
498     // therefore compare old/new window size and not old/new scaling factor
499     if (old_xsize != new_xsize ||
500         old_ysize != new_ysize)
501     {
502       int new_xpercent = (100 * new_xsize / video.width);
503       int new_ypercent = (100 * new_ysize / video.height);
504
505       setup.window_scaling_percent = MIN(new_xpercent, new_ypercent);
506
507       if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
508         setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
509       else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
510         setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
511
512       printf("::: setup.window_scaling_percent set to %d\n",
513              setup.window_scaling_percent);
514     }
515 #endif
516   }
517 }
518
519 #if 1
520
521 #define NUM_TOUCH_FINGERS               3
522
523 static struct
524 {
525   boolean touched;
526   SDL_FingerID finger_id;
527   int counter;
528   Key key;
529 } touch_info[NUM_TOUCH_FINGERS];
530
531 void HandleFingerEvent(FingerEvent *event)
532 {
533   static Key motion_key_x = KSYM_UNDEFINED;
534   static Key motion_key_y = KSYM_UNDEFINED;
535   static Key button_key = KSYM_UNDEFINED;
536   static float motion_x1, motion_y1;
537   static float button_x1, button_y1;
538   static SDL_FingerID motion_id = -1;
539   static SDL_FingerID button_id = -1;
540   int move_trigger_distance_percent = 2;   // percent of touchpad width/height
541   int drop_trigger_distance_percent = 5;   // percent of touchpad width/height
542   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
543   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
544   float event_x = event->x;
545   float event_y = event->y;
546
547 #if 1
548 #if DEBUG_EVENTS
549   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
550         event->type == EVENT_FINGERPRESS ? "pressed" :
551         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
552         event->touchId,
553         event->fingerId,
554         event->x, event->y,
555         event->dx, event->dy,
556         event->pressure);
557 #endif
558 #endif
559
560   if (game_status != GAME_MODE_PLAYING)
561     return;
562
563 #if 1
564   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
565 #else
566   boolean use_virtual_button_control = FALSE;
567
568   if (use_virtual_button_control)
569 #endif
570   {
571     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
572                       KEY_PRESSED);
573 #if 1
574     Key key = (event->x < 1.0 / 3.0 ?
575                (event->y < 1.0 / 2.0 ? setup.input[0].key.snap :
576                 setup.input[0].key.drop) :
577                event->x > 2.0 / 3.0 ?
578                (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
579                 event->y > 2.0 / 3.0 ? setup.input[0].key.down :
580                 event->x < 5.0 / 6.0 ? setup.input[0].key.left :
581                 setup.input[0].key.right) :
582                KSYM_UNDEFINED);
583 #if 0
584     char *key_name = (key == setup.input[0].key.snap  ? "SNAP" :
585                       key == setup.input[0].key.drop  ? "DROP" :
586                       key == setup.input[0].key.up    ? "UP" :
587                       key == setup.input[0].key.down  ? "DOWN" :
588                       key == setup.input[0].key.left  ? "LEFT" :
589                       key == setup.input[0].key.right ? "RIGHT" : "(unknown)");
590 #endif
591     char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
592                              "KEY_PRESSED");
593 #else
594     Key key = (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
595                event->y > 2.0 / 3.0 ? setup.input[0].key.down :
596                event->x < 1.0 / 3.0 ? setup.input[0].key.left :
597                event->x > 2.0 / 3.0 ? setup.input[0].key.right :
598                setup.input[0].key.snap);
599 #endif
600     int i;
601
602     Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
603           getKeyNameFromKey(key), key_status_name, event->fingerId);
604
605     // check if we already know this touch event's finger id
606     for (i = 0; i < NUM_TOUCH_FINGERS; i++)
607     {
608       if (touch_info[i].touched &&
609           touch_info[i].finger_id == event->fingerId)
610       {
611         // Error(ERR_DEBUG, "MARK 1: %d", i);
612
613         break;
614       }
615     }
616
617     if (i >= NUM_TOUCH_FINGERS)
618     {
619       if (key_status == KEY_PRESSED)
620       {
621         int oldest_pos = 0, oldest_counter = touch_info[0].counter;
622
623         // unknown finger id -- get new, empty slot, if available
624         for (i = 0; i < NUM_TOUCH_FINGERS; i++)
625         {
626           if (touch_info[i].counter < oldest_counter)
627           {
628             oldest_pos = i;
629             oldest_counter = touch_info[i].counter;
630
631             // Error(ERR_DEBUG, "MARK 2: %d", i);
632           }
633
634           if (!touch_info[i].touched)
635           {
636             // Error(ERR_DEBUG, "MARK 3: %d", i);
637
638             break;
639           }
640         }
641
642         if (i >= NUM_TOUCH_FINGERS)
643         {
644           // all slots allocated -- use oldest slot
645           i = oldest_pos;
646
647           // Error(ERR_DEBUG, "MARK 4: %d", i);
648         }
649       }
650       else
651       {
652         // release of previously unknown key (should not happen)
653
654         if (key != KSYM_UNDEFINED)
655         {
656           HandleKey(key, KEY_RELEASED);
657
658           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
659                 getKeyNameFromKey(key), "KEY_RELEASED", i);
660         }
661       }
662     }
663
664     if (i < NUM_TOUCH_FINGERS)
665     {
666       if (key_status == KEY_PRESSED)
667       {
668         if (touch_info[i].key != key)
669         {
670           if (touch_info[i].key != KSYM_UNDEFINED)
671           {
672             HandleKey(touch_info[i].key, KEY_RELEASED);
673
674             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
675                   getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
676           }
677
678           if (key != KSYM_UNDEFINED)
679           {
680             HandleKey(key, KEY_PRESSED);
681
682             Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
683                   getKeyNameFromKey(key), "KEY_PRESSED", i);
684           }
685         }
686
687         touch_info[i].touched = TRUE;
688         touch_info[i].finger_id = event->fingerId;
689         touch_info[i].counter = Counter();
690         touch_info[i].key = key;
691       }
692       else
693       {
694         if (touch_info[i].key != KSYM_UNDEFINED)
695         {
696           HandleKey(touch_info[i].key, KEY_RELEASED);
697
698           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
699                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
700         }
701
702         touch_info[i].touched = FALSE;
703         touch_info[i].finger_id = 0;
704         touch_info[i].counter = 0;
705         touch_info[i].key = 0;
706       }
707     }
708
709 #if 0
710 #if 1
711     Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d]",
712           key_name, key_status_name, i);
713 #else
714     Error(ERR_DEBUG, "=> key == %d, key_status == %d [%d]", key, key_status, i);
715 #endif
716 #endif
717
718     return;
719   }
720
721   // use touch direction control
722
723   if (event->type == EVENT_FINGERPRESS)
724   {
725     if (event_x > 1.0 / 3.0)
726     {
727       // motion area
728
729       motion_id = event->fingerId;
730
731       motion_x1 = event_x;
732       motion_y1 = event_y;
733
734       motion_key_x = KSYM_UNDEFINED;
735       motion_key_y = KSYM_UNDEFINED;
736
737       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
738     }
739     else
740     {
741       // button area
742
743       button_id = event->fingerId;
744
745       button_x1 = event_x;
746       button_y1 = event_y;
747
748       button_key = setup.input[0].key.snap;
749
750       HandleKey(button_key, KEY_PRESSED);
751
752       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
753     }
754   }
755   else if (event->type == EVENT_FINGERRELEASE)
756   {
757     if (event->fingerId == motion_id)
758     {
759       motion_id = -1;
760
761       if (motion_key_x != KSYM_UNDEFINED)
762         HandleKey(motion_key_x, KEY_RELEASED);
763       if (motion_key_y != KSYM_UNDEFINED)
764         HandleKey(motion_key_y, KEY_RELEASED);
765
766       motion_key_x = KSYM_UNDEFINED;
767       motion_key_y = KSYM_UNDEFINED;
768
769       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
770     }
771     else if (event->fingerId == button_id)
772     {
773       button_id = -1;
774
775       if (button_key != KSYM_UNDEFINED)
776         HandleKey(button_key, KEY_RELEASED);
777
778       button_key = KSYM_UNDEFINED;
779
780       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
781     }
782   }
783   else if (event->type == EVENT_FINGERMOTION)
784   {
785     if (event->fingerId == motion_id)
786     {
787       float distance_x = ABS(event_x - motion_x1);
788       float distance_y = ABS(event_y - motion_y1);
789       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
790                               event_x > motion_x1 ? setup.input[0].key.right :
791                               KSYM_UNDEFINED);
792       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
793                               event_y > motion_y1 ? setup.input[0].key.down :
794                               KSYM_UNDEFINED);
795
796       if (distance_x < move_trigger_distance / 2 ||
797           distance_x < distance_y)
798         new_motion_key_x = KSYM_UNDEFINED;
799
800       if (distance_y < move_trigger_distance / 2 ||
801           distance_y < distance_x)
802         new_motion_key_y = KSYM_UNDEFINED;
803
804       if (distance_x > move_trigger_distance ||
805           distance_y > move_trigger_distance)
806       {
807         if (new_motion_key_x != motion_key_x)
808         {
809           if (motion_key_x != KSYM_UNDEFINED)
810             HandleKey(motion_key_x, KEY_RELEASED);
811           if (new_motion_key_x != KSYM_UNDEFINED)
812             HandleKey(new_motion_key_x, KEY_PRESSED);
813         }
814
815         if (new_motion_key_y != motion_key_y)
816         {
817           if (motion_key_y != KSYM_UNDEFINED)
818             HandleKey(motion_key_y, KEY_RELEASED);
819           if (new_motion_key_y != KSYM_UNDEFINED)
820             HandleKey(new_motion_key_y, KEY_PRESSED);
821         }
822
823         motion_x1 = event_x;
824         motion_y1 = event_y;
825
826         motion_key_x = new_motion_key_x;
827         motion_key_y = new_motion_key_y;
828
829         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
830       }
831     }
832     else if (event->fingerId == button_id)
833     {
834       float distance_x = ABS(event_x - button_x1);
835       float distance_y = ABS(event_y - button_y1);
836
837       if (distance_x < drop_trigger_distance / 2 &&
838           distance_y > drop_trigger_distance)
839       {
840         if (button_key == setup.input[0].key.snap)
841           HandleKey(button_key, KEY_RELEASED);
842
843         button_x1 = event_x;
844         button_y1 = event_y;
845
846         button_key = setup.input[0].key.drop;
847
848         HandleKey(button_key, KEY_PRESSED);
849
850         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
851       }
852     }
853   }
854 }
855
856 #else
857
858 void HandleFingerEvent(FingerEvent *event)
859 {
860 #if 0
861   static int num_events = 0;
862   int max_events = 10;
863 #endif
864
865 #if 0
866 #if DEBUG_EVENTS
867   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
868         event->type == EVENT_FINGERPRESS ? "pressed" :
869         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
870         event->touchId,
871         event->fingerId,
872         event->x, event->y,
873         event->dx, event->dy,
874         event->pressure);
875 #endif
876 #endif
877
878 #if 0
879   int x = (int)(event->x * video.width);
880   int y = (int)(event->y * video.height);
881   int button = MB_LEFTBUTTON;
882
883   Error(ERR_DEBUG, "=> screen x/y %d/%d", x, y);
884 #endif
885
886 #if 0
887   if (++num_events >= max_events)
888     CloseAllAndExit(0);
889 #endif
890
891 #if 1
892 #if 0
893   if (event->type == EVENT_FINGERPRESS ||
894       event->type == EVENT_FINGERMOTION)
895     button_status = button;
896   else
897     button_status = MB_RELEASED;
898
899   int max_x = SX + SXSIZE;
900   int max_y = SY + SYSIZE;
901 #endif
902
903 #if 1
904   if (game_status == GAME_MODE_PLAYING)
905 #else
906   if (game_status == GAME_MODE_PLAYING &&
907       x < max_x)
908 #endif
909   {
910     int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
911                       KEY_PRESSED);
912 #if 1
913     Key key = (event->y < 1.0 / 3.0 ? setup.input[0].key.up :
914                event->y > 2.0 / 3.0 ? setup.input[0].key.down :
915                event->x < 1.0 / 3.0 ? setup.input[0].key.left :
916                event->x > 2.0 / 3.0 ? setup.input[0].key.right :
917                setup.input[0].key.drop);
918 #else
919     Key key = (y <     max_y / 3 ? setup.input[0].key.up :
920                y > 2 * max_y / 3 ? setup.input[0].key.down :
921                x <     max_x / 3 ? setup.input[0].key.left :
922                x > 2 * max_x / 3 ? setup.input[0].key.right :
923                setup.input[0].key.drop);
924 #endif
925
926     Error(ERR_DEBUG, "=> key == %d, key_status == %d", key, key_status);
927
928     HandleKey(key, key_status);
929   }
930   else
931   {
932 #if 0
933     Error(ERR_DEBUG, "::: button_status == %d, button == %d\n",
934           button_status, button);
935
936     HandleButton(x, y, button_status, button);
937 #endif
938   }
939 #endif
940 }
941
942 #endif
943
944 static boolean checkTextInputKeyModState()
945 {
946   // when playing, only handle raw key events and ignore text input
947   if (game_status == GAME_MODE_PLAYING)
948     return FALSE;
949
950   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
951 }
952
953 void HandleTextEvent(TextEvent *event)
954 {
955   char *text = event->text;
956   Key key = getKeyFromKeyName(text);
957
958 #if DEBUG_EVENTS
959   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
960         text,
961         strlen(text),
962         text[0], (int)(text[0]),
963         key,
964         getKeyNameFromKey(key),
965         GetKeyModState());
966 #endif
967
968   // if (game_status != GAME_MODE_PLAYING && GetKeyModState() != KMOD_None)
969   /*
970   if (game_status != GAME_MODE_PLAYING &&
971       (GetKeyModState() & KMOD_TextInput) != KMOD_None)
972   */
973   if (checkTextInputKeyModState())
974   {
975     HandleKey(key, KEY_PRESSED);
976     HandleKey(key, KEY_RELEASED);
977   }
978 }
979 #endif
980
981 void HandleKeyEvent(KeyEvent *event)
982 {
983   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
984   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
985   Key key = GetEventKey(event, with_modifiers);
986   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
987
988 #if DEBUG_EVENTS
989   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
990         event->type == EVENT_KEYPRESS ? "pressed" : "released",
991         event->keysym.scancode,
992         event->keysym.sym,
993         keymod,
994         GetKeyModState(),
995         key,
996         getKeyNameFromKey(key));
997 #endif
998
999 #if 0
1000   if (key == KSYM_Menu)
1001     Error(ERR_DEBUG, "menu key pressed");
1002   else if (key == KSYM_Back)
1003     Error(ERR_DEBUG, "back key pressed");
1004 #endif
1005
1006 #if defined(PLATFORM_ANDROID)
1007   // always map the "back" button to the "escape" key on Android devices
1008   if (key == KSYM_Back)
1009     key = KSYM_Escape;
1010 #endif
1011
1012   HandleKeyModState(keymod, key_status);
1013
1014 #if defined(TARGET_SDL2)
1015
1016   // if (game_status == GAME_MODE_PLAYING || GetKeyModState() == KMOD_None)
1017   /*
1018   if (game_status == GAME_MODE_PLAYING ||
1019       (GetKeyModState() & KMOD_TextInput) == KMOD_None)
1020   */
1021   if (!checkTextInputKeyModState())
1022     HandleKey(key, key_status);
1023 #else
1024   HandleKey(key, key_status);
1025 #endif
1026 }
1027
1028 void HandleFocusEvent(FocusChangeEvent *event)
1029 {
1030   static int old_joystick_status = -1;
1031
1032   if (event->type == EVENT_FOCUSOUT)
1033   {
1034     KeyboardAutoRepeatOn();
1035     old_joystick_status = joystick.status;
1036     joystick.status = JOYSTICK_NOT_AVAILABLE;
1037
1038     ClearPlayerAction();
1039   }
1040   else if (event->type == EVENT_FOCUSIN)
1041   {
1042     /* When there are two Rocks'n'Diamonds windows which overlap and
1043        the player moves the pointer from one game window to the other,
1044        a 'FocusOut' event is generated for the window the pointer is
1045        leaving and a 'FocusIn' event is generated for the window the
1046        pointer is entering. In some cases, it can happen that the
1047        'FocusIn' event is handled by the one game process before the
1048        'FocusOut' event by the other game process. In this case the
1049        X11 environment would end up with activated keyboard auto repeat,
1050        because unfortunately this is a global setting and not (which
1051        would be far better) set for each X11 window individually.
1052        The effect would be keyboard auto repeat while playing the game
1053        (game_status == GAME_MODE_PLAYING), which is not desired.
1054        To avoid this special case, we just wait 1/10 second before
1055        processing the 'FocusIn' event.
1056     */
1057
1058     if (game_status == GAME_MODE_PLAYING)
1059     {
1060       Delay(100);
1061       KeyboardAutoRepeatOffUnlessAutoplay();
1062     }
1063
1064     if (old_joystick_status != -1)
1065       joystick.status = old_joystick_status;
1066   }
1067 }
1068
1069 void HandleClientMessageEvent(ClientMessageEvent *event)
1070 {
1071   if (CheckCloseWindowEvent(event))
1072     CloseAllAndExit(0);
1073 }
1074
1075 void HandleWindowManagerEvent(Event *event)
1076 {
1077 #if defined(TARGET_SDL)
1078   SDLHandleWindowManagerEvent(event);
1079 #endif
1080 }
1081
1082 void HandleButton(int mx, int my, int button, int button_nr)
1083 {
1084   static int old_mx = 0, old_my = 0;
1085
1086   if (button < 0)
1087   {
1088     mx = old_mx;
1089     my = old_my;
1090     button = -button;
1091   }
1092   else
1093   {
1094     old_mx = mx;
1095     old_my = my;
1096   }
1097
1098 #if defined(PLATFORM_ANDROID)
1099   if (game_status != GAME_MODE_PLAYING &&
1100       HandleGadgets(mx, my, button))
1101   {
1102     /* do not handle this button event anymore */
1103     mx = my = -32;      /* force mouse event to be outside screen tiles */
1104   }
1105 #else
1106   if (HandleGadgets(mx, my, button))
1107   {
1108     /* do not handle this button event anymore */
1109     mx = my = -32;      /* force mouse event to be outside screen tiles */
1110   }
1111 #endif
1112
1113   /* do not use scroll wheel button events for anything other than gadgets */
1114   if (IS_WHEEL_BUTTON(button_nr))
1115     return;
1116
1117 #if 0
1118   Error(ERR_DEBUG, "::: game_status == %d", game_status);
1119 #endif
1120
1121   switch (game_status)
1122   {
1123     case GAME_MODE_TITLE:
1124       HandleTitleScreen(mx, my, 0, 0, button);
1125       break;
1126
1127     case GAME_MODE_MAIN:
1128       HandleMainMenu(mx, my, 0, 0, button);
1129       break;
1130
1131     case GAME_MODE_PSEUDO_TYPENAME:
1132       HandleTypeName(0, KSYM_Return);
1133       break;
1134
1135     case GAME_MODE_LEVELS:
1136       HandleChooseLevelSet(mx, my, 0, 0, button);
1137       break;
1138
1139     case GAME_MODE_LEVELNR:
1140       HandleChooseLevelNr(mx, my, 0, 0, button);
1141       break;
1142
1143     case GAME_MODE_SCORES:
1144       HandleHallOfFame(0, 0, 0, 0, button);
1145       break;
1146
1147     case GAME_MODE_EDITOR:
1148       HandleLevelEditorIdle();
1149       break;
1150
1151     case GAME_MODE_INFO:
1152       HandleInfoScreen(mx, my, 0, 0, button);
1153       break;
1154
1155     case GAME_MODE_SETUP:
1156       HandleSetupScreen(mx, my, 0, 0, button);
1157       break;
1158
1159     case GAME_MODE_PLAYING:
1160 #ifdef DEBUG
1161       if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my))
1162         DumpTile(LEVELX((mx - SX) / TILESIZE_VAR),
1163                  LEVELY((my - SY) / TILESIZE_VAR));
1164         // DumpTile(LEVELX((mx - SX) / TILEX), LEVELY((my - SY) / TILEY));
1165 #endif
1166       break;
1167
1168     default:
1169       break;
1170   }
1171 }
1172
1173 static boolean is_string_suffix(char *string, char *suffix)
1174 {
1175   int string_len = strlen(string);
1176   int suffix_len = strlen(suffix);
1177
1178   if (suffix_len > string_len)
1179     return FALSE;
1180
1181   return (strEqual(&string[string_len - suffix_len], suffix));
1182 }
1183
1184 #define MAX_CHEAT_INPUT_LEN     32
1185
1186 static void HandleKeysSpecial(Key key)
1187 {
1188   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1189   char letter = getCharFromKey(key);
1190   int cheat_input_len = strlen(cheat_input);
1191   int i;
1192
1193   if (letter == 0)
1194     return;
1195
1196   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1197   {
1198     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1199       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1200
1201     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1202   }
1203
1204   cheat_input[cheat_input_len++] = letter;
1205   cheat_input[cheat_input_len] = '\0';
1206
1207 #if DEBUG_EVENTS
1208   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1209 #endif
1210
1211   if (game_status == GAME_MODE_MAIN)
1212   {
1213     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1214         is_string_suffix(cheat_input, ":ist"))
1215     {
1216       InsertSolutionTape();
1217     }
1218     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1219              is_string_suffix(cheat_input, ":rg"))
1220     {
1221       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1222       DrawMainMenu();
1223     }
1224     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1225              is_string_suffix(cheat_input, ":rs"))
1226     {
1227       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1228       DrawMainMenu();
1229     }
1230     else if (is_string_suffix(cheat_input, ":reload-music") ||
1231              is_string_suffix(cheat_input, ":rm"))
1232     {
1233       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1234       DrawMainMenu();
1235     }
1236     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1237              is_string_suffix(cheat_input, ":ra"))
1238     {
1239       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1240                           1 << ARTWORK_TYPE_SOUNDS |
1241                           1 << ARTWORK_TYPE_MUSIC);
1242       DrawMainMenu();
1243     }
1244     else if (is_string_suffix(cheat_input, ":dump-level") ||
1245              is_string_suffix(cheat_input, ":dl"))
1246     {
1247       DumpLevel(&level);
1248     }
1249     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1250              is_string_suffix(cheat_input, ":dt"))
1251     {
1252       DumpTape(&tape);
1253     }
1254     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1255              is_string_suffix(cheat_input, ":ft"))
1256     {
1257       /* fix single-player tapes that contain player input for more than one
1258          player (due to a bug in 3.3.1.2 and earlier versions), which results
1259          in playing levels with more than one player in multi-player mode,
1260          even though the tape was originally recorded in single-player mode */
1261
1262       /* remove player input actions for all players but the first one */
1263       for (i = 1; i < MAX_PLAYERS; i++)
1264         tape.player_participates[i] = FALSE;
1265
1266       tape.changed = TRUE;
1267     }
1268     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1269              is_string_suffix(cheat_input, ":snl"))
1270     {
1271       SaveNativeLevel(&level);
1272     }
1273   }
1274   else if (game_status == GAME_MODE_PLAYING)
1275   {
1276 #ifdef DEBUG
1277     if (is_string_suffix(cheat_input, ".q"))
1278       DEBUG_SetMaximumDynamite();
1279 #endif
1280   }
1281   else if (game_status == GAME_MODE_EDITOR)
1282   {
1283     if (is_string_suffix(cheat_input, ":dump-brush") ||
1284         is_string_suffix(cheat_input, ":DB"))
1285     {
1286       DumpBrush();
1287     }
1288     else if (is_string_suffix(cheat_input, ":DDB"))
1289     {
1290       DumpBrush_Small();
1291     }
1292   }
1293 }
1294
1295 void HandleKey(Key key, int key_status)
1296 {
1297   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1298   static struct SetupKeyboardInfo ski;
1299   static struct SetupShortcutInfo ssi;
1300   static struct
1301   {
1302     Key *key_custom;
1303     Key *key_snap;
1304     Key key_default;
1305     byte action;
1306   } key_info[] =
1307   {
1308     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1309     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1310     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1311     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1312     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1313     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1314   };
1315   int joy = 0;
1316   int i;
1317
1318   if (game_status == GAME_MODE_PLAYING)
1319   {
1320     /* only needed for single-step tape recording mode */
1321     static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1322     static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1323     static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1324     static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1325     int pnr;
1326
1327     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1328     {
1329       byte key_action = 0;
1330
1331       if (setup.input[pnr].use_joystick)
1332         continue;
1333
1334       ski = setup.input[pnr].key;
1335
1336       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1337         if (key == *key_info[i].key_custom)
1338           key_action |= key_info[i].action;
1339
1340       /* use combined snap+direction keys for the first player only */
1341       if (pnr == 0)
1342       {
1343         ssi = setup.shortcut;
1344
1345         for (i = 0; i < NUM_DIRECTIONS; i++)
1346           if (key == *key_info[i].key_snap)
1347             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1348       }
1349
1350       /* clear delayed snap and drop actions in single step mode (see below) */
1351       if (tape.single_step)
1352       {
1353         if (clear_snap_button[pnr])
1354         {
1355           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1356           clear_snap_button[pnr] = FALSE;
1357         }
1358
1359         if (clear_drop_button[pnr])
1360         {
1361           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1362           clear_drop_button[pnr] = FALSE;
1363         }
1364       }
1365
1366       if (key_status == KEY_PRESSED)
1367         stored_player[pnr].action |= key_action;
1368       else
1369         stored_player[pnr].action &= ~key_action;
1370
1371       if (tape.single_step && tape.recording && tape.pausing)
1372       {
1373         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1374         {
1375           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1376
1377           /* if snap key already pressed, don't snap when releasing (below) */
1378           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1379             element_snapped[pnr] = TRUE;
1380
1381           /* if drop key already pressed, don't drop when releasing (below) */
1382           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1383             element_dropped[pnr] = TRUE;
1384         }
1385 #if 1
1386         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1387         {
1388           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1389               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1390           {
1391 #if 0
1392             printf("::: drop key pressed\n");
1393 #endif
1394
1395             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1396                 getRedDiskReleaseFlag_SP() == 0)
1397               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1398
1399             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1400           }
1401         }
1402 #endif
1403         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1404         {
1405           if (key_action & KEY_BUTTON_SNAP)
1406           {
1407             /* if snap key was released without moving (see above), snap now */
1408             if (!element_snapped[pnr])
1409             {
1410               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1411
1412               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1413
1414               /* clear delayed snap button on next event */
1415               clear_snap_button[pnr] = TRUE;
1416             }
1417
1418             element_snapped[pnr] = FALSE;
1419           }
1420
1421 #if 1
1422           if (key_action & KEY_BUTTON_DROP &&
1423               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1424           {
1425             /* if drop key was released without moving (see above), drop now */
1426             if (!element_dropped[pnr])
1427             {
1428               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1429
1430               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1431                   getRedDiskReleaseFlag_SP() != 0)
1432                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1433
1434               /* clear delayed drop button on next event */
1435               clear_drop_button[pnr] = TRUE;
1436             }
1437
1438             element_dropped[pnr] = FALSE;
1439           }
1440 #endif
1441         }
1442       }
1443       else if (tape.recording && tape.pausing)
1444       {
1445         /* prevent key release events from un-pausing a paused game */
1446         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1447           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1448       }
1449     }
1450   }
1451   else
1452   {
1453     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1454       if (key == key_info[i].key_default)
1455         joy |= key_info[i].action;
1456   }
1457
1458   if (joy)
1459   {
1460     if (key_status == KEY_PRESSED)
1461       key_joystick_mapping |= joy;
1462     else
1463       key_joystick_mapping &= ~joy;
1464
1465     HandleJoystick();
1466   }
1467
1468   if (game_status != GAME_MODE_PLAYING)
1469     key_joystick_mapping = 0;
1470
1471   if (key_status == KEY_RELEASED)
1472     return;
1473
1474   if ((key == KSYM_Return || key == KSYM_KP_Enter) &&
1475       (GetKeyModState() & KMOD_Alt) && video.fullscreen_available)
1476   {
1477     setup.fullscreen = !setup.fullscreen;
1478
1479 #if 0
1480     printf("::: %d\n", setup.window_scaling_percent);
1481 #endif
1482
1483     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1484
1485     if (game_status == GAME_MODE_SETUP)
1486       RedrawSetupScreenAfterFullscreenToggle();
1487
1488     return;
1489   }
1490
1491   if ((key == KSYM_minus || key == KSYM_plus || key == KSYM_0) &&
1492       (GetKeyModState() & KMOD_Alt) && video.window_scaling_available &&
1493       !video.fullscreen_enabled)
1494   {
1495     if (key == KSYM_0)
1496       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1497     else
1498       setup.window_scaling_percent +=
1499         (key == KSYM_minus ? -1 : +1) * STEP_WINDOW_SCALING_PERCENT;
1500
1501     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1502       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1503     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1504       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1505
1506     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1507
1508     if (game_status == GAME_MODE_SETUP)
1509       RedrawSetupScreenAfterFullscreenToggle();
1510
1511     return;
1512   }
1513
1514 #if 0
1515   if (game_status == GAME_MODE_PLAYING && local_player->LevelSolved_GameEnd &&
1516       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1517 #else
1518   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1519       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1520 #endif
1521   {
1522     GameEnd();
1523
1524     return;
1525   }
1526
1527   if (game_status == GAME_MODE_MAIN &&
1528       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1529   {
1530     StartGameActions(options.network, setup.autorecord, level.random_seed);
1531
1532     return;
1533   }
1534
1535   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1536   {
1537     if (key == setup.shortcut.save_game)
1538       TapeQuickSave();
1539     else if (key == setup.shortcut.load_game)
1540       TapeQuickLoad();
1541     else if (key == setup.shortcut.toggle_pause)
1542       TapeTogglePause(TAPE_TOGGLE_MANUAL);
1543
1544     HandleTapeButtonKeys(key);
1545     HandleSoundButtonKeys(key);
1546   }
1547
1548   if (game_status == GAME_MODE_PLAYING && !network_playing)
1549   {
1550     int centered_player_nr_next = -999;
1551
1552     if (key == setup.shortcut.focus_player_all)
1553       centered_player_nr_next = -1;
1554     else
1555       for (i = 0; i < MAX_PLAYERS; i++)
1556         if (key == setup.shortcut.focus_player[i])
1557           centered_player_nr_next = i;
1558
1559     if (centered_player_nr_next != -999)
1560     {
1561       game.centered_player_nr_next = centered_player_nr_next;
1562       game.set_centered_player = TRUE;
1563
1564       if (tape.recording)
1565       {
1566         tape.centered_player_nr_next = game.centered_player_nr_next;
1567         tape.set_centered_player = TRUE;
1568       }
1569     }
1570   }
1571
1572   HandleKeysSpecial(key);
1573
1574   if (HandleGadgetsKeyInput(key))
1575   {
1576     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1577       key = KSYM_UNDEFINED;
1578   }
1579
1580   switch (game_status)
1581   {
1582     case GAME_MODE_PSEUDO_TYPENAME:
1583       HandleTypeName(0, key);
1584       break;
1585
1586     case GAME_MODE_TITLE:
1587     case GAME_MODE_MAIN:
1588     case GAME_MODE_LEVELS:
1589     case GAME_MODE_LEVELNR:
1590     case GAME_MODE_SETUP:
1591     case GAME_MODE_INFO:
1592     case GAME_MODE_SCORES:
1593       switch (key)
1594       {
1595         case KSYM_space:
1596         case KSYM_Return:
1597           if (game_status == GAME_MODE_TITLE)
1598             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1599           else if (game_status == GAME_MODE_MAIN)
1600             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1601           else if (game_status == GAME_MODE_LEVELS)
1602             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1603           else if (game_status == GAME_MODE_LEVELNR)
1604             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1605           else if (game_status == GAME_MODE_SETUP)
1606             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1607           else if (game_status == GAME_MODE_INFO)
1608             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1609           else if (game_status == GAME_MODE_SCORES)
1610             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1611           break;
1612
1613         case KSYM_Escape:
1614           if (game_status != GAME_MODE_MAIN)
1615             FadeSkipNextFadeIn();
1616
1617           if (game_status == GAME_MODE_TITLE)
1618             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1619           else if (game_status == GAME_MODE_LEVELS)
1620             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1621           else if (game_status == GAME_MODE_LEVELNR)
1622             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1623           else if (game_status == GAME_MODE_SETUP)
1624             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1625           else if (game_status == GAME_MODE_INFO)
1626             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1627           else if (game_status == GAME_MODE_SCORES)
1628             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1629           break;
1630
1631         case KSYM_Page_Up:
1632           if (game_status == GAME_MODE_LEVELS)
1633             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1634           else if (game_status == GAME_MODE_LEVELNR)
1635             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1636           else if (game_status == GAME_MODE_SETUP)
1637             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1638           else if (game_status == GAME_MODE_INFO)
1639             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1640           else if (game_status == GAME_MODE_SCORES)
1641             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1642           break;
1643
1644         case KSYM_Page_Down:
1645           if (game_status == GAME_MODE_LEVELS)
1646             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1647           else if (game_status == GAME_MODE_LEVELNR)
1648             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1649           else if (game_status == GAME_MODE_SETUP)
1650             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1651           else if (game_status == GAME_MODE_INFO)
1652             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1653           else if (game_status == GAME_MODE_SCORES)
1654             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1655           break;
1656
1657 #ifdef DEBUG
1658         case KSYM_0:
1659           GameFrameDelay = (GameFrameDelay == 500 ? GAME_FRAME_DELAY : 500);
1660           break;
1661
1662         case KSYM_b:
1663           setup.sp_show_border_elements = !setup.sp_show_border_elements;
1664           printf("Supaplex border elements %s\n",
1665                  setup.sp_show_border_elements ? "enabled" : "disabled");
1666           break;
1667 #endif
1668
1669         default:
1670           break;
1671       }
1672       break;
1673
1674     case GAME_MODE_EDITOR:
1675       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1676         HandleLevelEditorKeyInput(key);
1677       break;
1678
1679     case GAME_MODE_PLAYING:
1680     {
1681       switch (key)
1682       {
1683         case KSYM_Escape:
1684           RequestQuitGame(setup.ask_on_escape);
1685           break;
1686
1687 #ifdef DEBUG
1688         case KSYM_0:
1689 #if 0
1690         case KSYM_1:
1691         case KSYM_2:
1692         case KSYM_3:
1693         case KSYM_4:
1694         case KSYM_5:
1695         case KSYM_6:
1696         case KSYM_7:
1697         case KSYM_8:
1698         case KSYM_9:
1699 #endif
1700           if (key == KSYM_0)
1701           {
1702             if (GameFrameDelay == 500)
1703               GameFrameDelay = GAME_FRAME_DELAY;
1704             else
1705               GameFrameDelay = 500;
1706           }
1707           else
1708             GameFrameDelay = (key - KSYM_0) * 10;
1709           printf("Game speed == %d%% (%d ms delay between two frames)\n",
1710                  GAME_FRAME_DELAY * 100 / GameFrameDelay, GameFrameDelay);
1711           break;
1712
1713         case KSYM_d:
1714           if (options.debug)
1715           {
1716             options.debug = FALSE;
1717             printf("debug mode disabled\n");
1718           }
1719           else
1720           {
1721             options.debug = TRUE;
1722             printf("debug mode enabled\n");
1723           }
1724           break;
1725
1726 #if 0
1727         case KSYM_s:
1728           if (!global.fps_slowdown)
1729           {
1730             global.fps_slowdown = TRUE;
1731             global.fps_slowdown_factor = 2;
1732             printf("fps slowdown enabled -- display only every 2nd frame\n");
1733           }
1734           else if (global.fps_slowdown_factor == 2)
1735           {
1736             global.fps_slowdown_factor = 4;
1737             printf("fps slowdown enabled -- display only every 4th frame\n");
1738           }
1739           else
1740           {
1741             global.fps_slowdown = FALSE;
1742             global.fps_slowdown_factor = 1;
1743             printf("fps slowdown disabled\n");
1744           }
1745           break;
1746 #endif
1747
1748 #if 0
1749         case KSYM_f:
1750           ScrollStepSize = TILEX / 8;
1751           printf("ScrollStepSize == %d (1/8)\n", ScrollStepSize);
1752           break;
1753
1754         case KSYM_g:
1755           ScrollStepSize = TILEX / 4;
1756           printf("ScrollStepSize == %d (1/4)\n", ScrollStepSize);
1757           break;
1758
1759         case KSYM_h:
1760           ScrollStepSize = TILEX / 2;
1761           printf("ScrollStepSize == %d (1/2)\n", ScrollStepSize);
1762           break;
1763
1764         case KSYM_l:
1765           ScrollStepSize = TILEX;
1766           printf("ScrollStepSize == %d (1/1)\n", ScrollStepSize);
1767           break;
1768 #endif
1769
1770         case KSYM_v:
1771           printf("::: currently using game engine version %d\n",
1772                  game.engine_version);
1773           break;
1774 #endif
1775
1776         default:
1777           break;
1778       }
1779       break;
1780     }
1781
1782     default:
1783       if (key == KSYM_Escape)
1784       {
1785         game_status = GAME_MODE_MAIN;
1786         DrawMainMenu();
1787
1788         return;
1789       }
1790   }
1791 }
1792
1793 void HandleNoEvent()
1794 {
1795   if (button_status && game_status != GAME_MODE_PLAYING)
1796   {
1797     HandleButton(0, 0, -button_status, button_status);
1798
1799 #if 0
1800     return;
1801 #endif
1802   }
1803   else
1804   {
1805     HandleJoystick();
1806   }
1807
1808 #if defined(NETWORK_AVALIABLE)
1809   if (options.network)
1810     HandleNetworking();
1811 #endif
1812
1813   switch (game_status)
1814   {
1815     case GAME_MODE_MAIN:
1816       DrawPreviewLevelAnimation();
1817       DoAnimation();
1818       break;
1819
1820     case GAME_MODE_LEVELS:
1821     case GAME_MODE_LEVELNR:
1822     case GAME_MODE_SETUP:
1823     case GAME_MODE_INFO:
1824     case GAME_MODE_SCORES:
1825       DoAnimation();
1826       break;
1827
1828     case GAME_MODE_EDITOR:
1829       HandleLevelEditorIdle();
1830       break;
1831
1832     default:
1833       break;
1834   }
1835 }
1836
1837 static int HandleJoystickForAllPlayers()
1838 {
1839   int i;
1840   int result = 0;
1841
1842   for (i = 0; i < MAX_PLAYERS; i++)
1843   {
1844     byte joy_action = 0;
1845
1846     /*
1847     if (!setup.input[i].use_joystick)
1848       continue;
1849       */
1850
1851     joy_action = Joystick(i);
1852     result |= joy_action;
1853
1854     if (!setup.input[i].use_joystick)
1855       continue;
1856
1857     stored_player[i].action = joy_action;
1858   }
1859
1860   return result;
1861 }
1862
1863 void HandleJoystick()
1864 {
1865   int joystick  = HandleJoystickForAllPlayers();
1866   int keyboard  = key_joystick_mapping;
1867   int joy       = (joystick | keyboard);
1868   int left      = joy & JOY_LEFT;
1869   int right     = joy & JOY_RIGHT;
1870   int up        = joy & JOY_UP;
1871   int down      = joy & JOY_DOWN;
1872   int button    = joy & JOY_BUTTON;
1873   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1874   int dx        = (left ? -1    : right ? 1     : 0);
1875   int dy        = (up   ? -1    : down  ? 1     : 0);
1876
1877   switch (game_status)
1878   {
1879     case GAME_MODE_TITLE:
1880     case GAME_MODE_MAIN:
1881     case GAME_MODE_LEVELS:
1882     case GAME_MODE_LEVELNR:
1883     case GAME_MODE_SETUP:
1884     case GAME_MODE_INFO:
1885     {
1886       static unsigned int joystickmove_delay = 0;
1887
1888       if (joystick && !button &&
1889           !DelayReached(&joystickmove_delay, GADGET_FRAME_DELAY))
1890         newbutton = dx = dy = 0;
1891
1892       if (game_status == GAME_MODE_TITLE)
1893         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1894       else if (game_status == GAME_MODE_MAIN)
1895         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1896       else if (game_status == GAME_MODE_LEVELS)
1897         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
1898       else if (game_status == GAME_MODE_LEVELNR)
1899         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
1900       else if (game_status == GAME_MODE_SETUP)
1901         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1902       else if (game_status == GAME_MODE_INFO)
1903         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
1904       break;
1905     }
1906
1907     case GAME_MODE_SCORES:
1908       HandleHallOfFame(0, 0, dx, dy, !newbutton);
1909       break;
1910
1911 #if 0
1912     case GAME_MODE_EDITOR:
1913       HandleLevelEditorIdle();
1914       break;
1915 #endif
1916
1917     case GAME_MODE_PLAYING:
1918       if (tape.playing || keyboard)
1919         newbutton = ((joy & JOY_BUTTON) != 0);
1920
1921 #if 0
1922       if (newbutton && local_player->LevelSolved_GameEnd)
1923 #else
1924       if (newbutton && AllPlayersGone)
1925 #endif
1926       {
1927         GameEnd();
1928
1929         return;
1930       }
1931
1932       break;
1933
1934     default:
1935       break;
1936   }
1937 }