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