added some performance tuning for "autotest" and warp mode
[rocksndiamonds.git] / src / libgame / system.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // system.c
10 // ============================================================================
11
12 #include <string.h>
13 #include <signal.h>
14
15 #include "platform.h"
16
17 #include "system.h"
18 #include "image.h"
19 #include "sound.h"
20 #include "setup.h"
21 #include "joystick.h"
22 #include "misc.h"
23
24 #define ENABLE_UNUSED_CODE      0       /* currently unused functions */
25
26
27 /* ========================================================================= */
28 /* exported variables                                                        */
29 /* ========================================================================= */
30
31 struct ProgramInfo      program;
32 struct OptionInfo       options;
33 struct VideoSystemInfo  video;
34 struct AudioSystemInfo  audio;
35 struct GfxInfo          gfx;
36 struct OverlayInfo      overlay;
37 struct ArtworkInfo      artwork;
38 struct JoystickInfo     joystick;
39 struct SetupInfo        setup;
40
41 LevelDirTree           *leveldir_first_all = NULL;
42 LevelDirTree           *leveldir_first = NULL;
43 LevelDirTree           *leveldir_current = NULL;
44 int                     level_nr;
45
46 struct LevelStats       level_stats[MAX_LEVELS];
47
48 DrawWindow             *window = NULL;
49 DrawBuffer             *backbuffer = NULL;
50 DrawBuffer             *drawto = NULL;
51
52 int                     button_status = MB_NOT_PRESSED;
53 boolean                 motion_status = FALSE;
54 int                     wheel_steps = DEFAULT_WHEEL_STEPS;
55 #if defined(TARGET_SDL2)
56 boolean                 keyrepeat_status = TRUE;
57 #endif
58
59 int                     redraw_mask = REDRAW_NONE;
60
61 int                     FrameCounter = 0;
62
63
64 /* ========================================================================= */
65 /* init/close functions                                                      */
66 /* ========================================================================= */
67
68 void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir,
69                      char *program_title, char *icon_title,
70                      char *icon_filename, char *cookie_prefix,
71                      char *program_version_string, int program_version)
72 {
73   program.command_basepath = getBasePath(argv0);
74   program.command_basename = getBaseName(argv0);
75
76   program.config_filename = config_filename;
77
78   program.userdata_subdir = userdata_subdir;
79   program.userdata_path = getUserGameDataDir();
80
81   program.program_title = program_title;
82   program.window_title = "(undefined)";
83   program.icon_title = icon_title;
84
85   program.icon_filename = icon_filename;
86
87   program.cookie_prefix = cookie_prefix;
88
89   program.version_major = VERSION_MAJOR(program_version);
90   program.version_minor = VERSION_MINOR(program_version);
91   program.version_patch = VERSION_PATCH(program_version);
92   program.version_build = VERSION_BUILD(program_version);
93   program.version_ident = program_version;
94
95   program.version_string = program_version_string;
96
97   program.log_filename[LOG_OUT_ID] = getLogFilename(LOG_OUT_BASENAME);
98   program.log_filename[LOG_ERR_ID] = getLogFilename(LOG_ERR_BASENAME);
99   program.log_file[LOG_OUT_ID] = program.log_file_default[LOG_OUT_ID] = stdout;
100   program.log_file[LOG_ERR_ID] = program.log_file_default[LOG_ERR_ID] = stderr;
101
102   program.headless = FALSE;
103 }
104
105 void InitScoresInfo()
106 {
107   char *global_scores_dir = getPath2(getCommonDataDir(), SCORES_DIRECTORY);
108
109   program.global_scores = directoryExists(global_scores_dir);
110   program.many_scores_per_name = !program.global_scores;
111
112   if (options.debug)
113   {
114     if (program.global_scores)
115     {
116       Error(ERR_DEBUG, "Using global, multi-user scores directory '%s'.",
117             global_scores_dir);
118       Error(ERR_DEBUG, "Remove to enable single-user scores directory.");
119       Error(ERR_DEBUG, "(This enables multipe score entries per user.)");
120     }
121     else
122     {
123       Error(ERR_DEBUG, "Using private, single-user scores directory.");
124     }
125   }
126
127   free(global_scores_dir);
128 }
129
130 void SetWindowTitle()
131 {
132   program.window_title = program.window_title_function();
133
134   SDLSetWindowTitle();
135 }
136
137 void InitWindowTitleFunction(char *(*window_title_function)(void))
138 {
139   program.window_title_function = window_title_function;
140 }
141
142 void InitExitMessageFunction(void (*exit_message_function)(char *, va_list))
143 {
144   program.exit_message_function = exit_message_function;
145 }
146
147 void InitExitFunction(void (*exit_function)(int))
148 {
149   program.exit_function = exit_function;
150
151   /* set signal handlers to custom exit function */
152   // signal(SIGINT, exit_function);
153   signal(SIGTERM, exit_function);
154
155   /* set exit function to automatically cleanup SDL stuff after exit() */
156   atexit(SDL_Quit);
157 }
158
159 void InitPlatformDependentStuff(void)
160 {
161   // this is initialized in GetOptions(), but may already be used before
162   options.verbose = TRUE;
163
164   OpenLogFiles();
165
166 #if defined(TARGET_SDL2)
167   int sdl_init_flags = SDL_INIT_EVENTS      | SDL_INIT_NOPARACHUTE;
168 #else
169   int sdl_init_flags = SDL_INIT_EVENTTHREAD | SDL_INIT_NOPARACHUTE;
170 #endif
171
172   if (SDL_Init(sdl_init_flags) < 0)
173     Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
174
175   SDLNet_Init();
176 }
177
178 void ClosePlatformDependentStuff(void)
179 {
180   CloseLogFiles();
181 }
182
183 void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize,
184                       int real_sx, int real_sy,
185                       int full_sxsize, int full_sysize,
186                       Bitmap *field_save_buffer)
187 {
188   gfx.sx = sx;
189   gfx.sy = sy;
190   gfx.sxsize = sxsize;
191   gfx.sysize = sysize;
192   gfx.real_sx = real_sx;
193   gfx.real_sy = real_sy;
194   gfx.full_sxsize = full_sxsize;
195   gfx.full_sysize = full_sysize;
196
197   gfx.field_save_buffer = field_save_buffer;
198
199   SetDrawDeactivationMask(REDRAW_NONE);         /* do not deactivate drawing */
200   SetDrawBackgroundMask(REDRAW_NONE);           /* deactivate masked drawing */
201 }
202
203 void InitGfxTileSizeInfo(int game_tile_size, int standard_tile_size)
204 {
205   gfx.game_tile_size = game_tile_size;
206   gfx.standard_tile_size = standard_tile_size;
207 }
208
209 void InitGfxDoor1Info(int dx, int dy, int dxsize, int dysize)
210 {
211   gfx.dx = dx;
212   gfx.dy = dy;
213   gfx.dxsize = dxsize;
214   gfx.dysize = dysize;
215 }
216
217 void InitGfxDoor2Info(int vx, int vy, int vxsize, int vysize)
218 {
219   gfx.vx = vx;
220   gfx.vy = vy;
221   gfx.vxsize = vxsize;
222   gfx.vysize = vysize;
223 }
224
225 void InitGfxDoor3Info(int ex, int ey, int exsize, int eysize)
226 {
227   gfx.ex = ex;
228   gfx.ey = ey;
229   gfx.exsize = exsize;
230   gfx.eysize = eysize;
231 }
232
233 void InitGfxWindowInfo(int win_xsize, int win_ysize)
234 {
235   if (win_xsize != gfx.win_xsize || win_ysize != gfx.win_ysize)
236   {
237     ReCreateBitmap(&gfx.background_bitmap, win_xsize, win_ysize);
238
239 #if defined(TARGET_SDL2)
240     ReCreateBitmap(&gfx.final_screen_bitmap, win_xsize, win_ysize);
241 #endif
242
243     ReCreateBitmap(&gfx.fade_bitmap_backup, win_xsize, win_ysize);
244     ReCreateBitmap(&gfx.fade_bitmap_source, win_xsize, win_ysize);
245     ReCreateBitmap(&gfx.fade_bitmap_target, win_xsize, win_ysize);
246     ReCreateBitmap(&gfx.fade_bitmap_black,  win_xsize, win_ysize);
247
248     ClearRectangle(gfx.fade_bitmap_black, 0, 0, win_xsize, win_ysize);
249   }
250
251   gfx.win_xsize = win_xsize;
252   gfx.win_ysize = win_ysize;
253
254   gfx.background_bitmap_mask = REDRAW_NONE;
255 }
256
257 void InitGfxScrollbufferInfo(int scrollbuffer_width, int scrollbuffer_height)
258 {
259   /* currently only used by MSDOS code to alloc VRAM buffer, if available */
260   /* 2009-03-24: also (temporarily?) used for overlapping blit workaround */
261   gfx.scrollbuffer_width = scrollbuffer_width;
262   gfx.scrollbuffer_height = scrollbuffer_height;
263 }
264
265 void InitGfxClipRegion(boolean enabled, int x, int y, int width, int height)
266 {
267   gfx.clipping_enabled = enabled;
268   gfx.clip_x = x;
269   gfx.clip_y = y;
270   gfx.clip_width = width;
271   gfx.clip_height = height;
272 }
273
274 void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(void))
275 {
276   gfx.draw_busy_anim_function = draw_busy_anim_function;
277 }
278
279 void InitGfxDrawGlobalAnimFunction(void (*draw_global_anim_function)(int, int))
280 {
281   gfx.draw_global_anim_function = draw_global_anim_function;
282 }
283
284 void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int))
285 {
286   gfx.draw_global_border_function = draw_global_border_function;
287 }
288
289 void InitGfxCustomArtworkInfo()
290 {
291   gfx.override_level_graphics = FALSE;
292   gfx.override_level_sounds = FALSE;
293   gfx.override_level_music = FALSE;
294
295   gfx.draw_init_text = TRUE;
296 }
297
298 void InitGfxOtherSettings()
299 {
300   gfx.cursor_mode = CURSOR_DEFAULT;
301 }
302
303 void InitOverlayInfo()
304 {
305   overlay.enabled = FALSE;
306   overlay.active = FALSE;
307
308 #if defined(PLATFORM_ANDROID)
309   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
310     overlay.enabled = TRUE;
311 #endif
312 }
313
314 void SetOverlayEnabled(boolean enabled)
315 {
316   overlay.enabled = enabled;
317 }
318
319 void SetOverlayActive(boolean active)
320 {
321   overlay.active = active;
322 }
323
324 boolean GetOverlayActive()
325 {
326   return overlay.active;
327 }
328
329 void SetDrawDeactivationMask(int draw_deactivation_mask)
330 {
331   gfx.draw_deactivation_mask = draw_deactivation_mask;
332 }
333
334 int GetDrawDeactivationMask()
335 {
336   return gfx.draw_deactivation_mask;
337 }
338
339 void SetDrawBackgroundMask(int draw_background_mask)
340 {
341   gfx.draw_background_mask = draw_background_mask;
342 }
343
344 void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask)
345 {
346   if (background_bitmap_tile != NULL)
347     gfx.background_bitmap_mask |= mask;
348   else
349     gfx.background_bitmap_mask &= ~mask;
350
351   if (background_bitmap_tile == NULL)   /* empty background requested */
352     return;
353
354   if (mask == REDRAW_ALL)
355     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
356                     0, 0, video.width, video.height);
357   else if (mask == REDRAW_FIELD)
358     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
359                     gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize);
360   else if (mask == REDRAW_DOOR_1)
361     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
362                     gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize);
363 }
364
365 void SetWindowBackgroundBitmap(Bitmap *background_bitmap_tile)
366 {
367   /* remove every mask before setting mask for window */
368   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
369   SetBackgroundBitmap(NULL, 0xffff);            /* !!! FIX THIS !!! */
370   SetBackgroundBitmap(background_bitmap_tile, REDRAW_ALL);
371 }
372
373 void SetMainBackgroundBitmap(Bitmap *background_bitmap_tile)
374 {
375   /* remove window area mask before setting mask for main area */
376   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
377   SetBackgroundBitmap(NULL, REDRAW_ALL);        /* !!! FIX THIS !!! */
378   SetBackgroundBitmap(background_bitmap_tile, REDRAW_FIELD);
379 }
380
381 void SetDoorBackgroundBitmap(Bitmap *background_bitmap_tile)
382 {
383   /* remove window area mask before setting mask for door area */
384   /* (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!) */
385   SetBackgroundBitmap(NULL, REDRAW_ALL);        /* !!! FIX THIS !!! */
386   SetBackgroundBitmap(background_bitmap_tile, REDRAW_DOOR_1);
387 }
388
389
390 /* ========================================================================= */
391 /* video functions                                                           */
392 /* ========================================================================= */
393
394 inline static int GetRealDepth(int depth)
395 {
396   return (depth == DEFAULT_DEPTH ? video.default_depth : depth);
397 }
398
399 inline static void sysFillRectangle(Bitmap *bitmap, int x, int y,
400                                     int width, int height, Pixel color)
401 {
402   SDLFillRectangle(bitmap, x, y, width, height, color);
403
404   if (bitmap == backbuffer)
405     SetRedrawMaskFromArea(x, y, width, height);
406 }
407
408 inline static void sysCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
409                                int src_x, int src_y, int width, int height,
410                                int dst_x, int dst_y, int mask_mode)
411 {
412   SDLCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
413               dst_x, dst_y, mask_mode);
414
415   if (dst_bitmap == backbuffer)
416     SetRedrawMaskFromArea(dst_x, dst_y, width, height);
417 }
418
419 void LimitScreenUpdates(boolean enable)
420 {
421   SDLLimitScreenUpdates(enable);
422 }
423
424 void InitVideoDisplay(void)
425 {
426   if (program.headless)
427     return;
428
429   SDLInitVideoDisplay();
430 #if defined(TARGET_SDL2)
431   SDLSetDisplaySize();
432 #endif
433 }
434
435 void CloseVideoDisplay(void)
436 {
437   KeyboardAutoRepeatOn();
438
439   SDL_QuitSubSystem(SDL_INIT_VIDEO);
440 }
441
442 void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
443 {
444   video.width = width;
445   video.height = height;
446   video.depth = GetRealDepth(depth);
447
448   video.screen_width = width;
449   video.screen_height = height;
450   video.screen_xoffset = 0;
451   video.screen_yoffset = 0;
452
453   video.fullscreen_available = FULLSCREEN_STATUS;
454   video.fullscreen_enabled = FALSE;
455
456   video.window_scaling_available = WINDOW_SCALING_STATUS;
457
458   video.frame_delay = 0;
459   video.frame_delay_value = GAME_FRAME_DELAY;
460
461   video.shifted_up = FALSE;
462   video.shifted_up_pos = 0;
463   video.shifted_up_pos_last = 0;
464   video.shifted_up_delay = 0;
465   video.shifted_up_delay_value = ONE_SECOND_DELAY / 4;
466
467   SDLInitVideoBuffer(fullscreen);
468
469   video.initialized = TRUE;
470
471   drawto = backbuffer;
472 }
473
474 inline static void FreeBitmapPointers(Bitmap *bitmap)
475 {
476   if (bitmap == NULL)
477     return;
478
479   SDLFreeBitmapPointers(bitmap);
480
481   checked_free(bitmap->source_filename);
482   bitmap->source_filename = NULL;
483 }
484
485 inline static void TransferBitmapPointers(Bitmap *src_bitmap,
486                                           Bitmap *dst_bitmap)
487 {
488   if (src_bitmap == NULL || dst_bitmap == NULL)
489     return;
490
491   FreeBitmapPointers(dst_bitmap);
492
493   *dst_bitmap = *src_bitmap;
494 }
495
496 void FreeBitmap(Bitmap *bitmap)
497 {
498   if (bitmap == NULL)
499     return;
500
501   FreeBitmapPointers(bitmap);
502
503   free(bitmap);
504 }
505
506 Bitmap *CreateBitmapStruct(void)
507 {
508   return checked_calloc(sizeof(Bitmap));
509 }
510
511 Bitmap *CreateBitmap(int width, int height, int depth)
512 {
513   Bitmap *new_bitmap = CreateBitmapStruct();
514   int real_width  = MAX(1, width);      /* prevent zero bitmap width */
515   int real_height = MAX(1, height);     /* prevent zero bitmap height */
516   int real_depth  = GetRealDepth(depth);
517
518   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
519
520   new_bitmap->width  = real_width;
521   new_bitmap->height = real_height;
522
523   return new_bitmap;
524 }
525
526 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
527 {
528   if (*bitmap != NULL)
529   {
530     /* if new bitmap size fits into old one, no need to re-create it */
531     if (width  <= (*bitmap)->width &&
532         height <= (*bitmap)->height)
533       return;
534
535     /* else adjust size so that old and new bitmap size fit into it */
536     width  = MAX(width,  (*bitmap)->width);
537     height = MAX(height, (*bitmap)->height);
538   }
539
540   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
541
542   if (*bitmap == NULL)
543   {
544     *bitmap = new_bitmap;
545   }
546   else
547   {
548     TransferBitmapPointers(new_bitmap, *bitmap);
549     free(new_bitmap);
550   }
551 }
552
553 void CloseWindow(DrawWindow *window)
554 {
555 }
556
557 void SetRedrawMaskFromArea(int x, int y, int width, int height)
558 {
559   int x1 = x;
560   int y1 = y;
561   int x2 = x + width - 1;
562   int y2 = y + height - 1;
563
564   if (width == 0 || height == 0)
565     return;
566
567   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
568     redraw_mask |= REDRAW_FIELD;
569   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
570     redraw_mask |= REDRAW_DOOR_1;
571   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
572     redraw_mask |= REDRAW_DOOR_2;
573   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
574     redraw_mask |= REDRAW_DOOR_3;
575   else
576     redraw_mask = REDRAW_ALL;
577 }
578
579 inline static boolean CheckDrawingArea(int x, int y, int width, int height,
580                                        int draw_mask)
581 {
582   if (draw_mask == REDRAW_NONE)
583     return FALSE;
584
585   if (draw_mask & REDRAW_ALL)
586     return TRUE;
587
588   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
589     return TRUE;
590
591   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
592     return TRUE;
593
594   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
595     return TRUE;
596
597   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
598     return TRUE;
599
600   return FALSE;
601 }
602
603 boolean DrawingDeactivatedField()
604 {
605   if (program.headless)
606     return TRUE;
607
608   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
609     return TRUE;
610
611   return FALSE;
612 }
613
614 boolean DrawingDeactivated(int x, int y, int width, int height)
615 {
616   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
617 }
618
619 boolean DrawingOnBackground(int x, int y)
620 {
621   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
622           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
623 }
624
625 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
626                                   int *width, int *height, boolean is_dest)
627 {
628   int clip_x, clip_y, clip_width, clip_height;
629
630   if (gfx.clipping_enabled && is_dest)  /* only clip destination bitmap */
631   {
632     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
633     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
634     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
635     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
636   }
637   else
638   {
639     clip_x = 0;
640     clip_y = 0;
641     clip_width = bitmap->width;
642     clip_height = bitmap->height;
643   }
644
645   /* skip if rectangle completely outside bitmap */
646
647   if (*x + *width  <= clip_x ||
648       *y + *height <= clip_y ||
649       *x >= clip_x + clip_width ||
650       *y >= clip_y + clip_height)
651     return FALSE;
652
653   /* clip if rectangle overlaps bitmap */
654
655   if (*x < clip_x)
656   {
657     *width -= clip_x - *x;
658     *x = clip_x;
659   }
660   else if (*x + *width > clip_x + clip_width)
661   {
662     *width = clip_x + clip_width - *x;
663   }
664
665   if (*y < clip_y)
666   {
667     *height -= clip_y - *y;
668     *y = clip_y;
669   }
670   else if (*y + *height > clip_y + clip_height)
671   {
672     *height = clip_y + clip_height - *y;
673   }
674
675   return TRUE;
676 }
677
678 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
679                 int src_x, int src_y, int width, int height,
680                 int dst_x, int dst_y)
681 {
682   int dst_x_unclipped = dst_x;
683   int dst_y_unclipped = dst_y;
684
685   if (program.headless)
686     return;
687
688   if (src_bitmap == NULL || dst_bitmap == NULL)
689     return;
690
691   if (DrawingDeactivated(dst_x, dst_y, width, height))
692     return;
693
694   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
695       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
696     return;
697
698   /* source x/y might need adjustment if destination x/y was clipped top/left */
699   src_x += dst_x - dst_x_unclipped;
700   src_y += dst_y - dst_y_unclipped;
701
702 #if defined(TARGET_SDL2)
703   /* !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!! */
704   /* !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!! */
705   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
706      but is already fixed in SVN and should therefore finally be fixed with
707      the next official SDL release, which is probably version 1.2.14.) */
708   /* !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!! */
709
710   if (src_bitmap == dst_bitmap)
711   {
712     /* needed when blitting directly to same bitmap -- should not be needed with
713        recent SDL libraries, but apparently does not work in 1.2.11 directly */
714
715     static Bitmap *tmp_bitmap = NULL;
716     static int tmp_bitmap_xsize = 0;
717     static int tmp_bitmap_ysize = 0;
718
719     /* start with largest static bitmaps for initial bitmap size ... */
720     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
721     {
722       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
723       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
724     }
725
726     /* ... and allow for later re-adjustments due to custom artwork bitmaps */
727     if (src_bitmap->width > tmp_bitmap_xsize ||
728         src_bitmap->height > tmp_bitmap_ysize)
729     {
730       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
731       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
732
733       FreeBitmap(tmp_bitmap);
734
735       tmp_bitmap = NULL;
736     }
737
738     if (tmp_bitmap == NULL)
739       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
740                                 DEFAULT_DEPTH);
741
742     sysCopyArea(src_bitmap, tmp_bitmap,
743                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
744     sysCopyArea(tmp_bitmap, dst_bitmap,
745                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
746
747     return;
748   }
749 #endif
750
751   sysCopyArea(src_bitmap, dst_bitmap,
752               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
753 }
754
755 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
756                      int src_x, int src_y, int src_width, int src_height,
757                      int dst_x, int dst_y, int dst_width, int dst_height)
758 {
759   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
760   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
761   int dst_xsize = dst_width;
762   int dst_ysize = dst_height;
763   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
764   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
765   int x, y;
766
767   for (y = 0; y < src_ysteps; y++)
768   {
769     for (x = 0; x < src_xsteps; x++)
770     {
771       int draw_x = dst_x + x * src_xsize;
772       int draw_y = dst_y + y * src_ysize;
773       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
774       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
775
776       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
777                  draw_x, draw_y);
778     }
779   }
780 }
781
782 void FadeRectangle(int x, int y, int width, int height,
783                    int fade_mode, int fade_delay, int post_delay,
784                    void (*draw_border_function)(void))
785 {
786   /* (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined) */
787   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
788     return;
789
790   SDLFadeRectangle(x, y, width, height,
791                    fade_mode, fade_delay, post_delay, draw_border_function);
792 }
793
794 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
795                    Pixel color)
796 {
797   if (DrawingDeactivated(x, y, width, height))
798     return;
799
800   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
801     return;
802
803   sysFillRectangle(bitmap, x, y, width, height, color);
804 }
805
806 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
807 {
808   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
809 }
810
811 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
812                                 int width, int height)
813 {
814   if (DrawingOnBackground(x, y))
815     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
816   else
817     ClearRectangle(bitmap, x, y, width, height);
818 }
819
820 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
821                       int src_x, int src_y, int width, int height,
822                       int dst_x, int dst_y)
823 {
824   if (DrawingDeactivated(dst_x, dst_y, width, height))
825     return;
826
827   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
828               dst_x, dst_y, BLIT_MASKED);
829 }
830
831 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
832                             int src_x, int src_y, int width, int height,
833                             int dst_x, int dst_y)
834 {
835   if (DrawingOnBackground(dst_x, dst_y))
836   {
837     /* draw background */
838     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
839                dst_x, dst_y);
840
841     /* draw foreground */
842     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
843                      dst_x, dst_y);
844   }
845   else
846     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
847                dst_x, dst_y);
848 }
849
850 void BlitTexture(Bitmap *bitmap,
851                 int src_x, int src_y, int width, int height,
852                 int dst_x, int dst_y)
853 {
854   if (bitmap == NULL)
855     return;
856
857   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
858                  BLIT_OPAQUE);
859 }
860
861 void BlitTextureMasked(Bitmap *bitmap,
862                        int src_x, int src_y, int width, int height,
863                        int dst_x, int dst_y)
864 {
865   if (bitmap == NULL)
866     return;
867
868   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
869                  BLIT_MASKED);
870 }
871
872 void BlitToScreen(Bitmap *bitmap,
873                   int src_x, int src_y, int width, int height,
874                   int dst_x, int dst_y)
875 {
876   if (bitmap == NULL)
877     return;
878
879   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
880     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
881                width, height, dst_x, dst_y);
882   else
883     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
884 }
885
886 void BlitToScreenMasked(Bitmap *bitmap,
887                         int src_x, int src_y, int width, int height,
888                         int dst_x, int dst_y)
889 {
890   if (bitmap == NULL)
891     return;
892
893   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
894     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
895                      width, height, dst_x, dst_y);
896   else
897     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
898 }
899
900 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
901                          int to_x, int to_y)
902 {
903   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
904 }
905
906 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
907                          int to_x, int to_y)
908 {
909   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
910 }
911
912 void DrawLine(Bitmap *bitmap, int from_x, int from_y,
913               int to_x, int to_y, Pixel pixel, int line_width)
914 {
915   int x, y;
916
917   for (x = 0; x < line_width; x++)
918   {
919     for (y = 0; y < line_width; y++)
920     {
921       int dx = x - line_width / 2;
922       int dy = y - line_width / 2;
923
924       if ((x == 0 && y == 0) ||
925           (x == 0 && y == line_width - 1) ||
926           (x == line_width - 1 && y == 0) ||
927           (x == line_width - 1 && y == line_width - 1))
928         continue;
929
930       SDLDrawLine(bitmap,
931                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
932     }
933   }
934 }
935
936 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
937 {
938   int line_width = 4;
939   int i;
940
941   for (i = 0; i < num_points - 1; i++)
942     DrawLine(bitmap, points[i].x, points[i].y,
943              points[i + 1].x, points[i + 1].y, pixel, line_width);
944
945   /*
946   SDLDrawLines(bitmap->surface, points, num_points, pixel);
947   */
948 }
949
950 Pixel GetPixel(Bitmap *bitmap, int x, int y)
951 {
952   if (x < 0 || x >= bitmap->width ||
953       y < 0 || y >= bitmap->height)
954     return BLACK_PIXEL;
955
956   return SDLGetPixel(bitmap, x, y);
957 }
958
959 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
960                       unsigned int color_g, unsigned int color_b)
961 {
962   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
963 }
964
965 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
966 {
967   unsigned int color_r = (color >> 16) & 0xff;
968   unsigned int color_g = (color >>  8) & 0xff;
969   unsigned int color_b = (color >>  0) & 0xff;
970
971   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
972 }
973
974 void KeyboardAutoRepeatOn(void)
975 {
976 #if defined(TARGET_SDL2)
977   keyrepeat_status = TRUE;
978 #else
979   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2,
980                       SDL_DEFAULT_REPEAT_INTERVAL / 2);
981   SDL_EnableUNICODE(1);
982 #endif
983 }
984
985 void KeyboardAutoRepeatOff(void)
986 {
987 #if defined(TARGET_SDL2)
988   keyrepeat_status = FALSE;
989 #else
990   SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
991   SDL_EnableUNICODE(0);
992 #endif
993 }
994
995 boolean SetVideoMode(boolean fullscreen)
996 {
997   return SDLSetVideoMode(fullscreen);
998 }
999
1000 void SetVideoFrameDelay(unsigned int frame_delay_value)
1001 {
1002   video.frame_delay_value = frame_delay_value;
1003 }
1004
1005 unsigned int GetVideoFrameDelay()
1006 {
1007   return video.frame_delay_value;
1008 }
1009
1010 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1011 {
1012   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1013       (!fullscreen && video.fullscreen_enabled))
1014     fullscreen = SetVideoMode(fullscreen);
1015
1016   return fullscreen;
1017 }
1018
1019 Bitmap *LoadImage(char *filename)
1020 {
1021   Bitmap *new_bitmap;
1022
1023   new_bitmap = SDLLoadImage(filename);
1024
1025   if (new_bitmap)
1026     new_bitmap->source_filename = getStringCopy(filename);
1027
1028   return new_bitmap;
1029 }
1030
1031 Bitmap *LoadCustomImage(char *basename)
1032 {
1033   char *filename = getCustomImageFilename(basename);
1034   Bitmap *new_bitmap;
1035
1036   if (filename == NULL)
1037     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1038
1039   if ((new_bitmap = LoadImage(filename)) == NULL)
1040     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1041
1042   return new_bitmap;
1043 }
1044
1045 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1046 {
1047   char *filename = getCustomImageFilename(basename);
1048   Bitmap *new_bitmap;
1049
1050   if (filename == NULL)         /* (should never happen) */
1051   {
1052     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1053     return;
1054   }
1055
1056   if (strEqual(filename, bitmap->source_filename))
1057   {
1058     /* The old and new image are the same (have the same filename and path).
1059        This usually means that this image does not exist in this graphic set
1060        and a fallback to the existing image is done. */
1061
1062     return;
1063   }
1064
1065   if ((new_bitmap = LoadImage(filename)) == NULL)
1066   {
1067     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1068     return;
1069   }
1070
1071   if (bitmap->width != new_bitmap->width ||
1072       bitmap->height != new_bitmap->height)
1073   {
1074     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1075           filename);
1076     FreeBitmap(new_bitmap);
1077     return;
1078   }
1079
1080   TransferBitmapPointers(new_bitmap, bitmap);
1081   free(new_bitmap);
1082 }
1083
1084 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1085 {
1086   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1087 }
1088
1089 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1090 {
1091   if (bitmaps[IMG_BITMAP_CUSTOM])
1092   {
1093     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1094
1095     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1096   }
1097
1098   if (gfx.game_tile_size == gfx.standard_tile_size)
1099   {
1100     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1101
1102     return;
1103   }
1104
1105   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1106   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1107   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1108
1109   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1110
1111   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1112   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1113 }
1114
1115 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1116                                 int tile_size, boolean create_small_bitmaps)
1117 {
1118   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1119   Bitmap *tmp_bitmap_final = NULL;
1120   Bitmap *tmp_bitmap_0 = NULL;
1121   Bitmap *tmp_bitmap_1 = NULL;
1122   Bitmap *tmp_bitmap_2 = NULL;
1123   Bitmap *tmp_bitmap_4 = NULL;
1124   Bitmap *tmp_bitmap_8 = NULL;
1125   Bitmap *tmp_bitmap_16 = NULL;
1126   Bitmap *tmp_bitmap_32 = NULL;
1127   int width_final, height_final;
1128   int width_0, height_0;
1129   int width_1, height_1;
1130   int width_2, height_2;
1131   int width_4, height_4;
1132   int width_8, height_8;
1133   int width_16, height_16;
1134   int width_32, height_32;
1135   int old_width, old_height;
1136   int i;
1137
1138   print_timestamp_init("CreateScaledBitmaps");
1139
1140   old_width  = old_bitmap->width;
1141   old_height = old_bitmap->height;
1142
1143   /* calculate new image dimensions for final image size */
1144   width_final  = old_width  * zoom_factor;
1145   height_final = old_height * zoom_factor;
1146
1147   /* get image with final size (this might require scaling up) */
1148   /* ("final" size may result in non-standard tile size image) */
1149   if (zoom_factor != 1)
1150     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1151   else
1152     tmp_bitmap_final = old_bitmap;
1153
1154   UPDATE_BUSY_STATE();
1155
1156   width_0  = width_1  = width_final;
1157   height_0 = height_1 = height_final;
1158
1159   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1160
1161   if (create_small_bitmaps)
1162   {
1163     /* check if we have a non-gameplay tile size image */
1164     if (tile_size != gfx.game_tile_size)
1165     {
1166       /* get image with gameplay tile size */
1167       width_0  = width_final  * gfx.game_tile_size / tile_size;
1168       height_0 = height_final * gfx.game_tile_size / tile_size;
1169
1170       if (width_0 == old_width)
1171         tmp_bitmap_0 = old_bitmap;
1172       else if (width_0 == width_final)
1173         tmp_bitmap_0 = tmp_bitmap_final;
1174       else
1175         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1176
1177       UPDATE_BUSY_STATE();
1178     }
1179
1180     /* check if we have a non-standard tile size image */
1181     if (tile_size != gfx.standard_tile_size)
1182     {
1183       /* get image with standard tile size */
1184       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1185       height_1 = height_final * gfx.standard_tile_size / tile_size;
1186
1187       if (width_1 == old_width)
1188         tmp_bitmap_1 = old_bitmap;
1189       else if (width_1 == width_final)
1190         tmp_bitmap_1 = tmp_bitmap_final;
1191       else if (width_1 == width_0)
1192         tmp_bitmap_1 = tmp_bitmap_0;
1193       else
1194         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1195
1196       UPDATE_BUSY_STATE();
1197     }
1198
1199     /* calculate new image dimensions for small images */
1200     width_2  = width_1  / 2;
1201     height_2 = height_1 / 2;
1202     width_4  = width_1  / 4;
1203     height_4 = height_1 / 4;
1204     width_8  = width_1  / 8;
1205     height_8 = height_1 / 8;
1206     width_16  = width_1  / 16;
1207     height_16 = height_1 / 16;
1208     width_32  = width_1  / 32;
1209     height_32 = height_1 / 32;
1210
1211     /* get image with 1/2 of normal size (for use in the level editor) */
1212     if (width_2 == old_width)
1213       tmp_bitmap_2 = old_bitmap;
1214     else
1215       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1216
1217     UPDATE_BUSY_STATE();
1218
1219     /* get image with 1/4 of normal size (for use in the level editor) */
1220     if (width_4 == old_width)
1221       tmp_bitmap_4 = old_bitmap;
1222     else
1223       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1224
1225     UPDATE_BUSY_STATE();
1226
1227     /* get image with 1/8 of normal size (for use on the preview screen) */
1228     if (width_8 == old_width)
1229       tmp_bitmap_8 = old_bitmap;
1230     else
1231       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1232
1233     UPDATE_BUSY_STATE();
1234
1235     /* get image with 1/16 of normal size (for use on the preview screen) */
1236     if (width_16 == old_width)
1237       tmp_bitmap_16 = old_bitmap;
1238     else
1239       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1240
1241     UPDATE_BUSY_STATE();
1242
1243     /* get image with 1/32 of normal size (for use on the preview screen) */
1244     if (width_32 == old_width)
1245       tmp_bitmap_32 = old_bitmap;
1246     else
1247       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1248
1249     UPDATE_BUSY_STATE();
1250
1251     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1252     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1253     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1254     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1255     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1256     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1257
1258     if (width_0 != width_1)
1259       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1260
1261     if (bitmaps[IMG_BITMAP_CUSTOM])
1262       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1263     else
1264       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1265
1266     boolean free_old_bitmap = TRUE;
1267
1268     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1269       if (bitmaps[i] == old_bitmap)
1270         free_old_bitmap = FALSE;
1271
1272     if (free_old_bitmap)
1273       FreeBitmap(old_bitmap);
1274   }
1275   else
1276   {
1277     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1278   }
1279
1280   UPDATE_BUSY_STATE();
1281
1282   print_timestamp_done("CreateScaledBitmaps");
1283 }
1284
1285 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1286                                   int tile_size)
1287 {
1288   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1289 }
1290
1291 void CreateBitmapTextures(Bitmap **bitmaps)
1292 {
1293   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1294 }
1295
1296 void FreeBitmapTextures(Bitmap **bitmaps)
1297 {
1298   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1299 }
1300
1301 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1302 {
1303   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1304 }
1305
1306
1307 /* ------------------------------------------------------------------------- */
1308 /* mouse pointer functions                                                   */
1309 /* ------------------------------------------------------------------------- */
1310
1311 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1312
1313 /* XPM image definitions */
1314 static const char *cursor_image_none[] =
1315 {
1316   /* width height num_colors chars_per_pixel */
1317   "    16    16        3            1",
1318
1319   /* colors */
1320   "X c #000000",
1321   ". c #ffffff",
1322   "  c None",
1323
1324   /* pixels */
1325   "                ",
1326   "                ",
1327   "                ",
1328   "                ",
1329   "                ",
1330   "                ",
1331   "                ",
1332   "                ",
1333   "                ",
1334   "                ",
1335   "                ",
1336   "                ",
1337   "                ",
1338   "                ",
1339   "                ",
1340   "                ",
1341
1342   /* hot spot */
1343   "0,0"
1344 };
1345
1346 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1347 static const char *cursor_image_dot[] =
1348 {
1349   /* width height num_colors chars_per_pixel */
1350   "    16    16        3            1",
1351
1352   /* colors */
1353   "X c #000000",
1354   ". c #ffffff",
1355   "  c None",
1356
1357   /* pixels */
1358   " X              ",
1359   "X.X             ",
1360   " X              ",
1361   "                ",
1362   "                ",
1363   "                ",
1364   "                ",
1365   "                ",
1366   "                ",
1367   "                ",
1368   "                ",
1369   "                ",
1370   "                ",
1371   "                ",
1372   "                ",
1373   "                ",
1374
1375   /* hot spot */
1376   "1,1"
1377 };
1378 static const char **cursor_image_playfield = cursor_image_dot;
1379 #else
1380 /* some people complained about a "white dot" on the screen and thought it
1381    was a graphical error... OK, let's just remove the whole pointer :-) */
1382 static const char **cursor_image_playfield = cursor_image_none;
1383 #endif
1384
1385 static const int cursor_bit_order = BIT_ORDER_MSB;
1386
1387 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1388 {
1389   struct MouseCursorInfo *cursor;
1390   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1391   int header_lines = 4;
1392   int x, y, i;
1393
1394   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1395
1396   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1397
1398   i = -1;
1399   for (y = 0; y < cursor->width; y++)
1400   {
1401     for (x = 0; x < cursor->height; x++)
1402     {
1403       int bit_nr = x % 8;
1404       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1405
1406       if (bit_nr == 0)
1407       {
1408         i++;
1409         cursor->data[i] = cursor->mask[i] = 0;
1410       }
1411
1412       switch (image[header_lines + y][x])
1413       {
1414         case 'X':
1415           cursor->data[i] |= bit_mask;
1416           cursor->mask[i] |= bit_mask;
1417           break;
1418
1419         case '.':
1420           cursor->mask[i] |= bit_mask;
1421           break;
1422
1423         case ' ':
1424           break;
1425       }
1426     }
1427   }
1428
1429   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1430
1431   return cursor;
1432 }
1433
1434 void SetMouseCursor(int mode)
1435 {
1436   static struct MouseCursorInfo *cursor_none = NULL;
1437   static struct MouseCursorInfo *cursor_playfield = NULL;
1438   struct MouseCursorInfo *cursor_new;
1439
1440   if (cursor_none == NULL)
1441     cursor_none = get_cursor_from_image(cursor_image_none);
1442
1443   if (cursor_playfield == NULL)
1444     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1445
1446   cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
1447                 mode == CURSOR_NONE      ? cursor_none :
1448                 mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1449
1450   SDLSetMouseCursor(cursor_new);
1451
1452   gfx.cursor_mode = mode;
1453 }
1454
1455
1456 /* ========================================================================= */
1457 /* audio functions                                                           */
1458 /* ========================================================================= */
1459
1460 void OpenAudio(void)
1461 {
1462   /* always start with reliable default values */
1463   audio.sound_available = FALSE;
1464   audio.music_available = FALSE;
1465   audio.loops_available = FALSE;
1466
1467   audio.sound_enabled = FALSE;
1468   audio.sound_deactivated = FALSE;
1469
1470   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1471   audio.mixer_pid = 0;
1472   audio.device_name = NULL;
1473   audio.device_fd = -1;
1474
1475   audio.num_channels = 0;
1476   audio.music_channel = 0;
1477   audio.first_sound_channel = 0;
1478
1479   SDLOpenAudio();
1480 }
1481
1482 void CloseAudio(void)
1483 {
1484   SDLCloseAudio();
1485
1486   audio.sound_enabled = FALSE;
1487 }
1488
1489 void SetAudioMode(boolean enabled)
1490 {
1491   if (!audio.sound_available)
1492     return;
1493
1494   audio.sound_enabled = enabled;
1495 }
1496
1497
1498 /* ========================================================================= */
1499 /* event functions                                                           */
1500 /* ========================================================================= */
1501
1502 boolean PendingEvent(void)
1503 {
1504   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1505 }
1506
1507 void WaitEvent(Event *event)
1508 {
1509   SDLWaitEvent(event);
1510 }
1511
1512 void PeekEvent(Event *event)
1513 {
1514 #if defined(TARGET_SDL2)
1515   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1516 #else
1517   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
1518 #endif
1519 }
1520
1521 void CheckQuitEvent(void)
1522 {
1523   if (SDL_QuitRequested())
1524     program.exit_function(0);
1525 }
1526
1527 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1528 {
1529 #if defined(TARGET_SDL2)
1530   /* key up/down events in SDL2 do not return text characters anymore */
1531   return event->keysym.sym;
1532 #else
1533
1534 #if ENABLE_UNUSED_CODE
1535   printf("unicode == '%d', sym == '%d', mod == '0x%04x'\n",
1536          (int)event->keysym.unicode,
1537          (int)event->keysym.sym,
1538          (int)SDL_GetModState());
1539 #endif
1540
1541   if (with_modifiers &&
1542       event->keysym.unicode > 0x0000 &&
1543       event->keysym.unicode < 0x2000)
1544     return event->keysym.unicode;
1545   else
1546     return event->keysym.sym;
1547
1548 #endif
1549 }
1550
1551 KeyMod HandleKeyModState(Key key, int key_status)
1552 {
1553   static KeyMod current_modifiers = KMOD_None;
1554
1555   if (key != KSYM_UNDEFINED)    /* new key => check for modifier key change */
1556   {
1557     KeyMod new_modifier = KMOD_None;
1558
1559     switch(key)
1560     {
1561       case KSYM_Shift_L:
1562         new_modifier = KMOD_Shift_L;
1563         break;
1564       case KSYM_Shift_R:
1565         new_modifier = KMOD_Shift_R;
1566         break;
1567       case KSYM_Control_L:
1568         new_modifier = KMOD_Control_L;
1569         break;
1570       case KSYM_Control_R:
1571         new_modifier = KMOD_Control_R;
1572         break;
1573       case KSYM_Meta_L:
1574         new_modifier = KMOD_Meta_L;
1575         break;
1576       case KSYM_Meta_R:
1577         new_modifier = KMOD_Meta_R;
1578         break;
1579       case KSYM_Alt_L:
1580         new_modifier = KMOD_Alt_L;
1581         break;
1582       case KSYM_Alt_R:
1583         new_modifier = KMOD_Alt_R;
1584         break;
1585       default:
1586         break;
1587     }
1588
1589     if (key_status == KEY_PRESSED)
1590       current_modifiers |= new_modifier;
1591     else
1592       current_modifiers &= ~new_modifier;
1593   }
1594
1595   return current_modifiers;
1596 }
1597
1598 KeyMod GetKeyModState()
1599 {
1600   return (KeyMod)SDL_GetModState();
1601 }
1602
1603 KeyMod GetKeyModStateFromEvents()
1604 {
1605   /* always use key modifier state as tracked from key events (this is needed
1606      if the modifier key event was injected into the event queue, but the key
1607      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1608      query the keys as held pressed on the keyboard) -- this case is currently
1609      only used to filter out clipboard insert events from "True X-Mouse" tool */
1610
1611   return HandleKeyModState(KSYM_UNDEFINED, 0);
1612 }
1613
1614 void StartTextInput(int x, int y, int width, int height)
1615 {
1616 #if defined(TARGET_SDL2)
1617 #if defined(HAS_SCREEN_KEYBOARD)
1618   SDL_StartTextInput();
1619
1620   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1621   {
1622     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1623     video.shifted_up_delay = SDL_GetTicks();
1624     video.shifted_up = TRUE;
1625   }
1626 #endif
1627 #endif
1628 }
1629
1630 void StopTextInput()
1631 {
1632 #if defined(TARGET_SDL2)
1633 #if defined(HAS_SCREEN_KEYBOARD)
1634   SDL_StopTextInput();
1635
1636   if (video.shifted_up)
1637   {
1638     video.shifted_up_pos = 0;
1639     video.shifted_up_delay = SDL_GetTicks();
1640     video.shifted_up = FALSE;
1641   }
1642 #endif
1643 #endif
1644 }
1645
1646 boolean CheckCloseWindowEvent(ClientMessageEvent *event)
1647 {
1648   if (event->type != EVENT_CLIENTMESSAGE)
1649     return FALSE;
1650
1651   return TRUE;          /* the only possible message here is SDL_QUIT */
1652 }
1653
1654
1655 /* ========================================================================= */
1656 /* joystick functions                                                        */
1657 /* ========================================================================= */
1658
1659 void InitJoysticks()
1660 {
1661   int i;
1662
1663 #if defined(NO_JOYSTICK)
1664   return;       /* joysticks generally deactivated by compile-time directive */
1665 #endif
1666
1667   /* always start with reliable default values */
1668   joystick.status = JOYSTICK_NOT_AVAILABLE;
1669   for (i = 0; i < MAX_PLAYERS; i++)
1670     joystick.nr[i] = -1;                /* no joystick configured */
1671
1672   SDLInitJoysticks();
1673 }
1674
1675 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1676 {
1677   return SDLReadJoystick(nr, x, y, b1, b2);
1678 }
1679
1680 boolean CheckJoystickOpened(int nr)
1681 {
1682   return SDLCheckJoystickOpened(nr);
1683 }
1684
1685 void ClearJoystickState()
1686 {
1687   SDLClearJoystickState();
1688 }