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