fixed crash bug when playing MM style tapes in headless 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   if (program.headless)
918     return;
919
920   for (x = 0; x < line_width; x++)
921   {
922     for (y = 0; y < line_width; y++)
923     {
924       int dx = x - line_width / 2;
925       int dy = y - line_width / 2;
926
927       if ((x == 0 && y == 0) ||
928           (x == 0 && y == line_width - 1) ||
929           (x == line_width - 1 && y == 0) ||
930           (x == line_width - 1 && y == line_width - 1))
931         continue;
932
933       SDLDrawLine(bitmap,
934                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
935     }
936   }
937 }
938
939 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
940 {
941   int line_width = 4;
942   int i;
943
944   for (i = 0; i < num_points - 1; i++)
945     DrawLine(bitmap, points[i].x, points[i].y,
946              points[i + 1].x, points[i + 1].y, pixel, line_width);
947
948   /*
949   SDLDrawLines(bitmap->surface, points, num_points, pixel);
950   */
951 }
952
953 Pixel GetPixel(Bitmap *bitmap, int x, int y)
954 {
955   if (program.headless)
956     return BLACK_PIXEL;
957
958   if (x < 0 || x >= bitmap->width ||
959       y < 0 || y >= bitmap->height)
960     return BLACK_PIXEL;
961
962   return SDLGetPixel(bitmap, x, y);
963 }
964
965 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
966                       unsigned int color_g, unsigned int color_b)
967 {
968   if (program.headless)
969     return BLACK_PIXEL;
970
971   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
972 }
973
974 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
975 {
976   unsigned int color_r = (color >> 16) & 0xff;
977   unsigned int color_g = (color >>  8) & 0xff;
978   unsigned int color_b = (color >>  0) & 0xff;
979
980   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
981 }
982
983 void KeyboardAutoRepeatOn(void)
984 {
985 #if defined(TARGET_SDL2)
986   keyrepeat_status = TRUE;
987 #else
988   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2,
989                       SDL_DEFAULT_REPEAT_INTERVAL / 2);
990   SDL_EnableUNICODE(1);
991 #endif
992 }
993
994 void KeyboardAutoRepeatOff(void)
995 {
996 #if defined(TARGET_SDL2)
997   keyrepeat_status = FALSE;
998 #else
999   SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
1000   SDL_EnableUNICODE(0);
1001 #endif
1002 }
1003
1004 boolean SetVideoMode(boolean fullscreen)
1005 {
1006   return SDLSetVideoMode(fullscreen);
1007 }
1008
1009 void SetVideoFrameDelay(unsigned int frame_delay_value)
1010 {
1011   video.frame_delay_value = frame_delay_value;
1012 }
1013
1014 unsigned int GetVideoFrameDelay()
1015 {
1016   return video.frame_delay_value;
1017 }
1018
1019 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1020 {
1021   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1022       (!fullscreen && video.fullscreen_enabled))
1023     fullscreen = SetVideoMode(fullscreen);
1024
1025   return fullscreen;
1026 }
1027
1028 Bitmap *LoadImage(char *filename)
1029 {
1030   Bitmap *new_bitmap;
1031
1032   new_bitmap = SDLLoadImage(filename);
1033
1034   if (new_bitmap)
1035     new_bitmap->source_filename = getStringCopy(filename);
1036
1037   return new_bitmap;
1038 }
1039
1040 Bitmap *LoadCustomImage(char *basename)
1041 {
1042   char *filename = getCustomImageFilename(basename);
1043   Bitmap *new_bitmap;
1044
1045   if (filename == NULL)
1046     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1047
1048   if ((new_bitmap = LoadImage(filename)) == NULL)
1049     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1050
1051   return new_bitmap;
1052 }
1053
1054 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1055 {
1056   char *filename = getCustomImageFilename(basename);
1057   Bitmap *new_bitmap;
1058
1059   if (filename == NULL)         /* (should never happen) */
1060   {
1061     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1062     return;
1063   }
1064
1065   if (strEqual(filename, bitmap->source_filename))
1066   {
1067     /* The old and new image are the same (have the same filename and path).
1068        This usually means that this image does not exist in this graphic set
1069        and a fallback to the existing image is done. */
1070
1071     return;
1072   }
1073
1074   if ((new_bitmap = LoadImage(filename)) == NULL)
1075   {
1076     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1077     return;
1078   }
1079
1080   if (bitmap->width != new_bitmap->width ||
1081       bitmap->height != new_bitmap->height)
1082   {
1083     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1084           filename);
1085     FreeBitmap(new_bitmap);
1086     return;
1087   }
1088
1089   TransferBitmapPointers(new_bitmap, bitmap);
1090   free(new_bitmap);
1091 }
1092
1093 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1094 {
1095   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1096 }
1097
1098 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1099 {
1100   if (bitmaps[IMG_BITMAP_CUSTOM])
1101   {
1102     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1103
1104     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1105   }
1106
1107   if (gfx.game_tile_size == gfx.standard_tile_size)
1108   {
1109     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1110
1111     return;
1112   }
1113
1114   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1115   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1116   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1117
1118   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1119
1120   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1121   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1122 }
1123
1124 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1125                                 int tile_size, boolean create_small_bitmaps)
1126 {
1127   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1128   Bitmap *tmp_bitmap_final = NULL;
1129   Bitmap *tmp_bitmap_0 = NULL;
1130   Bitmap *tmp_bitmap_1 = NULL;
1131   Bitmap *tmp_bitmap_2 = NULL;
1132   Bitmap *tmp_bitmap_4 = NULL;
1133   Bitmap *tmp_bitmap_8 = NULL;
1134   Bitmap *tmp_bitmap_16 = NULL;
1135   Bitmap *tmp_bitmap_32 = NULL;
1136   int width_final, height_final;
1137   int width_0, height_0;
1138   int width_1, height_1;
1139   int width_2, height_2;
1140   int width_4, height_4;
1141   int width_8, height_8;
1142   int width_16, height_16;
1143   int width_32, height_32;
1144   int old_width, old_height;
1145   int i;
1146
1147   print_timestamp_init("CreateScaledBitmaps");
1148
1149   old_width  = old_bitmap->width;
1150   old_height = old_bitmap->height;
1151
1152   /* calculate new image dimensions for final image size */
1153   width_final  = old_width  * zoom_factor;
1154   height_final = old_height * zoom_factor;
1155
1156   /* get image with final size (this might require scaling up) */
1157   /* ("final" size may result in non-standard tile size image) */
1158   if (zoom_factor != 1)
1159     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1160   else
1161     tmp_bitmap_final = old_bitmap;
1162
1163   UPDATE_BUSY_STATE();
1164
1165   width_0  = width_1  = width_final;
1166   height_0 = height_1 = height_final;
1167
1168   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1169
1170   if (create_small_bitmaps)
1171   {
1172     /* check if we have a non-gameplay tile size image */
1173     if (tile_size != gfx.game_tile_size)
1174     {
1175       /* get image with gameplay tile size */
1176       width_0  = width_final  * gfx.game_tile_size / tile_size;
1177       height_0 = height_final * gfx.game_tile_size / tile_size;
1178
1179       if (width_0 == old_width)
1180         tmp_bitmap_0 = old_bitmap;
1181       else if (width_0 == width_final)
1182         tmp_bitmap_0 = tmp_bitmap_final;
1183       else
1184         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1185
1186       UPDATE_BUSY_STATE();
1187     }
1188
1189     /* check if we have a non-standard tile size image */
1190     if (tile_size != gfx.standard_tile_size)
1191     {
1192       /* get image with standard tile size */
1193       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1194       height_1 = height_final * gfx.standard_tile_size / tile_size;
1195
1196       if (width_1 == old_width)
1197         tmp_bitmap_1 = old_bitmap;
1198       else if (width_1 == width_final)
1199         tmp_bitmap_1 = tmp_bitmap_final;
1200       else if (width_1 == width_0)
1201         tmp_bitmap_1 = tmp_bitmap_0;
1202       else
1203         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1204
1205       UPDATE_BUSY_STATE();
1206     }
1207
1208     /* calculate new image dimensions for small images */
1209     width_2  = width_1  / 2;
1210     height_2 = height_1 / 2;
1211     width_4  = width_1  / 4;
1212     height_4 = height_1 / 4;
1213     width_8  = width_1  / 8;
1214     height_8 = height_1 / 8;
1215     width_16  = width_1  / 16;
1216     height_16 = height_1 / 16;
1217     width_32  = width_1  / 32;
1218     height_32 = height_1 / 32;
1219
1220     /* get image with 1/2 of normal size (for use in the level editor) */
1221     if (width_2 == old_width)
1222       tmp_bitmap_2 = old_bitmap;
1223     else
1224       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1225
1226     UPDATE_BUSY_STATE();
1227
1228     /* get image with 1/4 of normal size (for use in the level editor) */
1229     if (width_4 == old_width)
1230       tmp_bitmap_4 = old_bitmap;
1231     else
1232       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1233
1234     UPDATE_BUSY_STATE();
1235
1236     /* get image with 1/8 of normal size (for use on the preview screen) */
1237     if (width_8 == old_width)
1238       tmp_bitmap_8 = old_bitmap;
1239     else
1240       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1241
1242     UPDATE_BUSY_STATE();
1243
1244     /* get image with 1/16 of normal size (for use on the preview screen) */
1245     if (width_16 == old_width)
1246       tmp_bitmap_16 = old_bitmap;
1247     else
1248       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1249
1250     UPDATE_BUSY_STATE();
1251
1252     /* get image with 1/32 of normal size (for use on the preview screen) */
1253     if (width_32 == old_width)
1254       tmp_bitmap_32 = old_bitmap;
1255     else
1256       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1257
1258     UPDATE_BUSY_STATE();
1259
1260     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1261     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1262     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1263     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1264     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1265     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1266
1267     if (width_0 != width_1)
1268       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1269
1270     if (bitmaps[IMG_BITMAP_CUSTOM])
1271       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1272     else
1273       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1274
1275     boolean free_old_bitmap = TRUE;
1276
1277     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1278       if (bitmaps[i] == old_bitmap)
1279         free_old_bitmap = FALSE;
1280
1281     if (free_old_bitmap)
1282       FreeBitmap(old_bitmap);
1283   }
1284   else
1285   {
1286     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1287   }
1288
1289   UPDATE_BUSY_STATE();
1290
1291   print_timestamp_done("CreateScaledBitmaps");
1292 }
1293
1294 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1295                                   int tile_size)
1296 {
1297   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1298 }
1299
1300 void CreateBitmapTextures(Bitmap **bitmaps)
1301 {
1302   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1303 }
1304
1305 void FreeBitmapTextures(Bitmap **bitmaps)
1306 {
1307   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1308 }
1309
1310 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1311 {
1312   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1313 }
1314
1315
1316 /* ------------------------------------------------------------------------- */
1317 /* mouse pointer functions                                                   */
1318 /* ------------------------------------------------------------------------- */
1319
1320 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1321
1322 /* XPM image definitions */
1323 static const char *cursor_image_none[] =
1324 {
1325   /* width height num_colors chars_per_pixel */
1326   "    16    16        3            1",
1327
1328   /* colors */
1329   "X c #000000",
1330   ". c #ffffff",
1331   "  c None",
1332
1333   /* pixels */
1334   "                ",
1335   "                ",
1336   "                ",
1337   "                ",
1338   "                ",
1339   "                ",
1340   "                ",
1341   "                ",
1342   "                ",
1343   "                ",
1344   "                ",
1345   "                ",
1346   "                ",
1347   "                ",
1348   "                ",
1349   "                ",
1350
1351   /* hot spot */
1352   "0,0"
1353 };
1354
1355 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1356 static const char *cursor_image_dot[] =
1357 {
1358   /* width height num_colors chars_per_pixel */
1359   "    16    16        3            1",
1360
1361   /* colors */
1362   "X c #000000",
1363   ". c #ffffff",
1364   "  c None",
1365
1366   /* pixels */
1367   " X              ",
1368   "X.X             ",
1369   " X              ",
1370   "                ",
1371   "                ",
1372   "                ",
1373   "                ",
1374   "                ",
1375   "                ",
1376   "                ",
1377   "                ",
1378   "                ",
1379   "                ",
1380   "                ",
1381   "                ",
1382   "                ",
1383
1384   /* hot spot */
1385   "1,1"
1386 };
1387 static const char **cursor_image_playfield = cursor_image_dot;
1388 #else
1389 /* some people complained about a "white dot" on the screen and thought it
1390    was a graphical error... OK, let's just remove the whole pointer :-) */
1391 static const char **cursor_image_playfield = cursor_image_none;
1392 #endif
1393
1394 static const int cursor_bit_order = BIT_ORDER_MSB;
1395
1396 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1397 {
1398   struct MouseCursorInfo *cursor;
1399   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1400   int header_lines = 4;
1401   int x, y, i;
1402
1403   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1404
1405   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1406
1407   i = -1;
1408   for (y = 0; y < cursor->width; y++)
1409   {
1410     for (x = 0; x < cursor->height; x++)
1411     {
1412       int bit_nr = x % 8;
1413       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1414
1415       if (bit_nr == 0)
1416       {
1417         i++;
1418         cursor->data[i] = cursor->mask[i] = 0;
1419       }
1420
1421       switch (image[header_lines + y][x])
1422       {
1423         case 'X':
1424           cursor->data[i] |= bit_mask;
1425           cursor->mask[i] |= bit_mask;
1426           break;
1427
1428         case '.':
1429           cursor->mask[i] |= bit_mask;
1430           break;
1431
1432         case ' ':
1433           break;
1434       }
1435     }
1436   }
1437
1438   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1439
1440   return cursor;
1441 }
1442
1443 void SetMouseCursor(int mode)
1444 {
1445   static struct MouseCursorInfo *cursor_none = NULL;
1446   static struct MouseCursorInfo *cursor_playfield = NULL;
1447   struct MouseCursorInfo *cursor_new;
1448
1449   if (cursor_none == NULL)
1450     cursor_none = get_cursor_from_image(cursor_image_none);
1451
1452   if (cursor_playfield == NULL)
1453     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1454
1455   cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
1456                 mode == CURSOR_NONE      ? cursor_none :
1457                 mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1458
1459   SDLSetMouseCursor(cursor_new);
1460
1461   gfx.cursor_mode = mode;
1462 }
1463
1464
1465 /* ========================================================================= */
1466 /* audio functions                                                           */
1467 /* ========================================================================= */
1468
1469 void OpenAudio(void)
1470 {
1471   /* always start with reliable default values */
1472   audio.sound_available = FALSE;
1473   audio.music_available = FALSE;
1474   audio.loops_available = FALSE;
1475
1476   audio.sound_enabled = FALSE;
1477   audio.sound_deactivated = FALSE;
1478
1479   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1480   audio.mixer_pid = 0;
1481   audio.device_name = NULL;
1482   audio.device_fd = -1;
1483
1484   audio.num_channels = 0;
1485   audio.music_channel = 0;
1486   audio.first_sound_channel = 0;
1487
1488   SDLOpenAudio();
1489 }
1490
1491 void CloseAudio(void)
1492 {
1493   SDLCloseAudio();
1494
1495   audio.sound_enabled = FALSE;
1496 }
1497
1498 void SetAudioMode(boolean enabled)
1499 {
1500   if (!audio.sound_available)
1501     return;
1502
1503   audio.sound_enabled = enabled;
1504 }
1505
1506
1507 /* ========================================================================= */
1508 /* event functions                                                           */
1509 /* ========================================================================= */
1510
1511 boolean PendingEvent(void)
1512 {
1513   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1514 }
1515
1516 void WaitEvent(Event *event)
1517 {
1518   SDLWaitEvent(event);
1519 }
1520
1521 void PeekEvent(Event *event)
1522 {
1523 #if defined(TARGET_SDL2)
1524   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1525 #else
1526   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
1527 #endif
1528 }
1529
1530 void CheckQuitEvent(void)
1531 {
1532   if (SDL_QuitRequested())
1533     program.exit_function(0);
1534 }
1535
1536 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1537 {
1538 #if defined(TARGET_SDL2)
1539   /* key up/down events in SDL2 do not return text characters anymore */
1540   return event->keysym.sym;
1541 #else
1542
1543 #if ENABLE_UNUSED_CODE
1544   printf("unicode == '%d', sym == '%d', mod == '0x%04x'\n",
1545          (int)event->keysym.unicode,
1546          (int)event->keysym.sym,
1547          (int)SDL_GetModState());
1548 #endif
1549
1550   if (with_modifiers &&
1551       event->keysym.unicode > 0x0000 &&
1552       event->keysym.unicode < 0x2000)
1553     return event->keysym.unicode;
1554   else
1555     return event->keysym.sym;
1556
1557 #endif
1558 }
1559
1560 KeyMod HandleKeyModState(Key key, int key_status)
1561 {
1562   static KeyMod current_modifiers = KMOD_None;
1563
1564   if (key != KSYM_UNDEFINED)    /* new key => check for modifier key change */
1565   {
1566     KeyMod new_modifier = KMOD_None;
1567
1568     switch(key)
1569     {
1570       case KSYM_Shift_L:
1571         new_modifier = KMOD_Shift_L;
1572         break;
1573       case KSYM_Shift_R:
1574         new_modifier = KMOD_Shift_R;
1575         break;
1576       case KSYM_Control_L:
1577         new_modifier = KMOD_Control_L;
1578         break;
1579       case KSYM_Control_R:
1580         new_modifier = KMOD_Control_R;
1581         break;
1582       case KSYM_Meta_L:
1583         new_modifier = KMOD_Meta_L;
1584         break;
1585       case KSYM_Meta_R:
1586         new_modifier = KMOD_Meta_R;
1587         break;
1588       case KSYM_Alt_L:
1589         new_modifier = KMOD_Alt_L;
1590         break;
1591       case KSYM_Alt_R:
1592         new_modifier = KMOD_Alt_R;
1593         break;
1594       default:
1595         break;
1596     }
1597
1598     if (key_status == KEY_PRESSED)
1599       current_modifiers |= new_modifier;
1600     else
1601       current_modifiers &= ~new_modifier;
1602   }
1603
1604   return current_modifiers;
1605 }
1606
1607 KeyMod GetKeyModState()
1608 {
1609   return (KeyMod)SDL_GetModState();
1610 }
1611
1612 KeyMod GetKeyModStateFromEvents()
1613 {
1614   /* always use key modifier state as tracked from key events (this is needed
1615      if the modifier key event was injected into the event queue, but the key
1616      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1617      query the keys as held pressed on the keyboard) -- this case is currently
1618      only used to filter out clipboard insert events from "True X-Mouse" tool */
1619
1620   return HandleKeyModState(KSYM_UNDEFINED, 0);
1621 }
1622
1623 void StartTextInput(int x, int y, int width, int height)
1624 {
1625 #if defined(TARGET_SDL2)
1626 #if defined(HAS_SCREEN_KEYBOARD)
1627   SDL_StartTextInput();
1628
1629   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1630   {
1631     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1632     video.shifted_up_delay = SDL_GetTicks();
1633     video.shifted_up = TRUE;
1634   }
1635 #endif
1636 #endif
1637 }
1638
1639 void StopTextInput()
1640 {
1641 #if defined(TARGET_SDL2)
1642 #if defined(HAS_SCREEN_KEYBOARD)
1643   SDL_StopTextInput();
1644
1645   if (video.shifted_up)
1646   {
1647     video.shifted_up_pos = 0;
1648     video.shifted_up_delay = SDL_GetTicks();
1649     video.shifted_up = FALSE;
1650   }
1651 #endif
1652 #endif
1653 }
1654
1655 boolean CheckCloseWindowEvent(ClientMessageEvent *event)
1656 {
1657   if (event->type != EVENT_CLIENTMESSAGE)
1658     return FALSE;
1659
1660   return TRUE;          /* the only possible message here is SDL_QUIT */
1661 }
1662
1663
1664 /* ========================================================================= */
1665 /* joystick functions                                                        */
1666 /* ========================================================================= */
1667
1668 void InitJoysticks()
1669 {
1670   int i;
1671
1672 #if defined(NO_JOYSTICK)
1673   return;       /* joysticks generally deactivated by compile-time directive */
1674 #endif
1675
1676   /* always start with reliable default values */
1677   joystick.status = JOYSTICK_NOT_AVAILABLE;
1678   for (i = 0; i < MAX_PLAYERS; i++)
1679     joystick.nr[i] = -1;                /* no joystick configured */
1680
1681   SDLInitJoysticks();
1682 }
1683
1684 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1685 {
1686   return SDLReadJoystick(nr, x, y, b1, b2);
1687 }
1688
1689 boolean CheckJoystickOpened(int nr)
1690 {
1691   return SDLCheckJoystickOpened(nr);
1692 }
1693
1694 void ClearJoystickState()
1695 {
1696   SDLClearJoystickState();
1697 }