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