added custom version string to default program configuration settings
[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   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
529
530   if (*bitmap == NULL)
531   {
532     *bitmap = new_bitmap;
533   }
534   else
535   {
536     TransferBitmapPointers(new_bitmap, *bitmap);
537     free(new_bitmap);
538   }
539 }
540
541 void CloseWindow(DrawWindow *window)
542 {
543 }
544
545 void SetRedrawMaskFromArea(int x, int y, int width, int height)
546 {
547   int x1 = x;
548   int y1 = y;
549   int x2 = x + width - 1;
550   int y2 = y + height - 1;
551
552   if (width == 0 || height == 0)
553     return;
554
555   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
556     redraw_mask |= REDRAW_FIELD;
557   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
558     redraw_mask |= REDRAW_DOOR_1;
559   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
560     redraw_mask |= REDRAW_DOOR_2;
561   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
562     redraw_mask |= REDRAW_DOOR_3;
563   else
564     redraw_mask = REDRAW_ALL;
565 }
566
567 inline static boolean CheckDrawingArea(int x, int y, int width, int height,
568                                        int draw_mask)
569 {
570   if (draw_mask == REDRAW_NONE)
571     return FALSE;
572
573   if (draw_mask & REDRAW_ALL)
574     return TRUE;
575
576   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
577     return TRUE;
578
579   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
580     return TRUE;
581
582   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
583     return TRUE;
584
585   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
586     return TRUE;
587
588   return FALSE;
589 }
590
591 boolean DrawingDeactivated(int x, int y, int width, int height)
592 {
593   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
594 }
595
596 boolean DrawingOnBackground(int x, int y)
597 {
598   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
599           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
600 }
601
602 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
603                                   int *width, int *height, boolean is_dest)
604 {
605   int clip_x, clip_y, clip_width, clip_height;
606
607   if (gfx.clipping_enabled && is_dest)  /* only clip destination bitmap */
608   {
609     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
610     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
611     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
612     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
613   }
614   else
615   {
616     clip_x = 0;
617     clip_y = 0;
618     clip_width = bitmap->width;
619     clip_height = bitmap->height;
620   }
621
622   /* skip if rectangle completely outside bitmap */
623
624   if (*x + *width  <= clip_x ||
625       *y + *height <= clip_y ||
626       *x >= clip_x + clip_width ||
627       *y >= clip_y + clip_height)
628     return FALSE;
629
630   /* clip if rectangle overlaps bitmap */
631
632   if (*x < clip_x)
633   {
634     *width -= clip_x - *x;
635     *x = clip_x;
636   }
637   else if (*x + *width > clip_x + clip_width)
638   {
639     *width = clip_x + clip_width - *x;
640   }
641
642   if (*y < clip_y)
643   {
644     *height -= clip_y - *y;
645     *y = clip_y;
646   }
647   else if (*y + *height > clip_y + clip_height)
648   {
649     *height = clip_y + clip_height - *y;
650   }
651
652   return TRUE;
653 }
654
655 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
656                 int src_x, int src_y, int width, int height,
657                 int dst_x, int dst_y)
658 {
659   int dst_x_unclipped = dst_x;
660   int dst_y_unclipped = dst_y;
661
662   if (program.headless)
663     return;
664
665   if (src_bitmap == NULL || dst_bitmap == NULL)
666     return;
667
668   if (DrawingDeactivated(dst_x, dst_y, width, height))
669     return;
670
671   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
672       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
673     return;
674
675   /* source x/y might need adjustment if destination x/y was clipped top/left */
676   src_x += dst_x - dst_x_unclipped;
677   src_y += dst_y - dst_y_unclipped;
678
679 #if defined(TARGET_SDL2)
680   /* !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!! */
681   /* !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!! */
682   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
683      but is already fixed in SVN and should therefore finally be fixed with
684      the next official SDL release, which is probably version 1.2.14.) */
685   /* !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!! */
686
687   if (src_bitmap == dst_bitmap)
688   {
689     /* needed when blitting directly to same bitmap -- should not be needed with
690        recent SDL libraries, but apparently does not work in 1.2.11 directly */
691
692     static Bitmap *tmp_bitmap = NULL;
693     static int tmp_bitmap_xsize = 0;
694     static int tmp_bitmap_ysize = 0;
695
696     /* start with largest static bitmaps for initial bitmap size ... */
697     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
698     {
699       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
700       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
701     }
702
703     /* ... and allow for later re-adjustments due to custom artwork bitmaps */
704     if (src_bitmap->width > tmp_bitmap_xsize ||
705         src_bitmap->height > tmp_bitmap_ysize)
706     {
707       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
708       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
709
710       FreeBitmap(tmp_bitmap);
711
712       tmp_bitmap = NULL;
713     }
714
715     if (tmp_bitmap == NULL)
716       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
717                                 DEFAULT_DEPTH);
718
719     sysCopyArea(src_bitmap, tmp_bitmap,
720                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
721     sysCopyArea(tmp_bitmap, dst_bitmap,
722                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
723
724     return;
725   }
726 #endif
727
728   sysCopyArea(src_bitmap, dst_bitmap,
729               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
730 }
731
732 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
733                      int src_x, int src_y, int src_width, int src_height,
734                      int dst_x, int dst_y, int dst_width, int dst_height)
735 {
736   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
737   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
738   int dst_xsize = dst_width;
739   int dst_ysize = dst_height;
740   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
741   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
742   int x, y;
743
744   for (y = 0; y < src_ysteps; y++)
745   {
746     for (x = 0; x < src_xsteps; x++)
747     {
748       int draw_x = dst_x + x * src_xsize;
749       int draw_y = dst_y + y * src_ysize;
750       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
751       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
752
753       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
754                  draw_x, draw_y);
755     }
756   }
757 }
758
759 void FadeRectangle(int x, int y, int width, int height,
760                    int fade_mode, int fade_delay, int post_delay,
761                    void (*draw_border_function)(void))
762 {
763   /* (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined) */
764   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
765     return;
766
767   SDLFadeRectangle(x, y, width, height,
768                    fade_mode, fade_delay, post_delay, draw_border_function);
769 }
770
771 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
772                    Pixel color)
773 {
774   if (DrawingDeactivated(x, y, width, height))
775     return;
776
777   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
778     return;
779
780   sysFillRectangle(bitmap, x, y, width, height, color);
781 }
782
783 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
784 {
785   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
786 }
787
788 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
789                                 int width, int height)
790 {
791   if (DrawingOnBackground(x, y))
792     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
793   else
794     ClearRectangle(bitmap, x, y, width, height);
795 }
796
797 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
798                       int src_x, int src_y, int width, int height,
799                       int dst_x, int dst_y)
800 {
801   if (DrawingDeactivated(dst_x, dst_y, width, height))
802     return;
803
804   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
805               dst_x, dst_y, BLIT_MASKED);
806 }
807
808 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
809                             int src_x, int src_y, int width, int height,
810                             int dst_x, int dst_y)
811 {
812   if (DrawingOnBackground(dst_x, dst_y))
813   {
814     /* draw background */
815     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
816                dst_x, dst_y);
817
818     /* draw foreground */
819     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
820                      dst_x, dst_y);
821   }
822   else
823     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
824                dst_x, dst_y);
825 }
826
827 void BlitTexture(Bitmap *bitmap,
828                 int src_x, int src_y, int width, int height,
829                 int dst_x, int dst_y)
830 {
831   if (bitmap == NULL)
832     return;
833
834   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
835                  BLIT_OPAQUE);
836 }
837
838 void BlitTextureMasked(Bitmap *bitmap,
839                        int src_x, int src_y, int width, int height,
840                        int dst_x, int dst_y)
841 {
842   if (bitmap == NULL)
843     return;
844
845   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
846                  BLIT_MASKED);
847 }
848
849 void BlitToScreen(Bitmap *bitmap,
850                   int src_x, int src_y, int width, int height,
851                   int dst_x, int dst_y)
852 {
853   if (bitmap == NULL)
854     return;
855
856   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
857     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
858                width, height, dst_x, dst_y);
859   else
860     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
861 }
862
863 void BlitToScreenMasked(Bitmap *bitmap,
864                         int src_x, int src_y, int width, int height,
865                         int dst_x, int dst_y)
866 {
867   if (bitmap == NULL)
868     return;
869
870   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
871     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
872                      width, height, dst_x, dst_y);
873   else
874     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
875 }
876
877 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
878                          int to_x, int to_y)
879 {
880   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
881 }
882
883 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
884                          int to_x, int to_y)
885 {
886   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
887 }
888
889 void DrawLine(Bitmap *bitmap, int from_x, int from_y,
890               int to_x, int to_y, Pixel pixel, int line_width)
891 {
892   int x, y;
893
894   for (x = 0; x < line_width; x++)
895   {
896     for (y = 0; y < line_width; y++)
897     {
898       int dx = x - line_width / 2;
899       int dy = y - line_width / 2;
900
901       if ((x == 0 && y == 0) ||
902           (x == 0 && y == line_width - 1) ||
903           (x == line_width - 1 && y == 0) ||
904           (x == line_width - 1 && y == line_width - 1))
905         continue;
906
907       SDLDrawLine(bitmap,
908                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
909     }
910   }
911 }
912
913 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
914 {
915   int line_width = 4;
916   int i;
917
918   for (i = 0; i < num_points - 1; i++)
919     DrawLine(bitmap, points[i].x, points[i].y,
920              points[i + 1].x, points[i + 1].y, pixel, line_width);
921
922   /*
923   SDLDrawLines(bitmap->surface, points, num_points, pixel);
924   */
925 }
926
927 Pixel GetPixel(Bitmap *bitmap, int x, int y)
928 {
929   if (x < 0 || x >= bitmap->width ||
930       y < 0 || y >= bitmap->height)
931     return BLACK_PIXEL;
932
933   return SDLGetPixel(bitmap, x, y);
934 }
935
936 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
937                       unsigned int color_g, unsigned int color_b)
938 {
939   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
940 }
941
942 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
943 {
944   unsigned int color_r = (color >> 16) & 0xff;
945   unsigned int color_g = (color >>  8) & 0xff;
946   unsigned int color_b = (color >>  0) & 0xff;
947
948   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
949 }
950
951 void KeyboardAutoRepeatOn(void)
952 {
953 #if defined(TARGET_SDL2)
954   keyrepeat_status = TRUE;
955 #else
956   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY / 2,
957                       SDL_DEFAULT_REPEAT_INTERVAL / 2);
958   SDL_EnableUNICODE(1);
959 #endif
960 }
961
962 void KeyboardAutoRepeatOff(void)
963 {
964 #if defined(TARGET_SDL2)
965   keyrepeat_status = FALSE;
966 #else
967   SDL_EnableKeyRepeat(0, SDL_DEFAULT_REPEAT_INTERVAL);
968   SDL_EnableUNICODE(0);
969 #endif
970 }
971
972 boolean SetVideoMode(boolean fullscreen)
973 {
974   return SDLSetVideoMode(fullscreen);
975 }
976
977 void SetVideoFrameDelay(unsigned int frame_delay_value)
978 {
979   video.frame_delay_value = frame_delay_value;
980 }
981
982 unsigned int GetVideoFrameDelay()
983 {
984   return video.frame_delay_value;
985 }
986
987 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
988 {
989   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
990       (!fullscreen && video.fullscreen_enabled))
991     fullscreen = SetVideoMode(fullscreen);
992
993   return fullscreen;
994 }
995
996 Bitmap *LoadImage(char *filename)
997 {
998   Bitmap *new_bitmap;
999
1000   new_bitmap = SDLLoadImage(filename);
1001
1002   if (new_bitmap)
1003     new_bitmap->source_filename = getStringCopy(filename);
1004
1005   return new_bitmap;
1006 }
1007
1008 Bitmap *LoadCustomImage(char *basename)
1009 {
1010   char *filename = getCustomImageFilename(basename);
1011   Bitmap *new_bitmap;
1012
1013   if (filename == NULL)
1014     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1015
1016   if ((new_bitmap = LoadImage(filename)) == NULL)
1017     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1018
1019   return new_bitmap;
1020 }
1021
1022 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1023 {
1024   char *filename = getCustomImageFilename(basename);
1025   Bitmap *new_bitmap;
1026
1027   if (filename == NULL)         /* (should never happen) */
1028   {
1029     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1030     return;
1031   }
1032
1033   if (strEqual(filename, bitmap->source_filename))
1034   {
1035     /* The old and new image are the same (have the same filename and path).
1036        This usually means that this image does not exist in this graphic set
1037        and a fallback to the existing image is done. */
1038
1039     return;
1040   }
1041
1042   if ((new_bitmap = LoadImage(filename)) == NULL)
1043   {
1044     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1045     return;
1046   }
1047
1048   if (bitmap->width != new_bitmap->width ||
1049       bitmap->height != new_bitmap->height)
1050   {
1051     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1052           filename);
1053     FreeBitmap(new_bitmap);
1054     return;
1055   }
1056
1057   TransferBitmapPointers(new_bitmap, bitmap);
1058   free(new_bitmap);
1059 }
1060
1061 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1062 {
1063   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1064 }
1065
1066 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1067 {
1068   if (bitmaps[IMG_BITMAP_CUSTOM])
1069   {
1070     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1071
1072     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1073   }
1074
1075   if (gfx.game_tile_size == gfx.standard_tile_size)
1076   {
1077     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1078
1079     return;
1080   }
1081
1082   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1083   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1084   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1085
1086   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1087
1088   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1089   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1090 }
1091
1092 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1093                                 int tile_size, boolean create_small_bitmaps)
1094 {
1095   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1096   Bitmap *tmp_bitmap_final = NULL;
1097   Bitmap *tmp_bitmap_0 = NULL;
1098   Bitmap *tmp_bitmap_1 = NULL;
1099   Bitmap *tmp_bitmap_2 = NULL;
1100   Bitmap *tmp_bitmap_4 = NULL;
1101   Bitmap *tmp_bitmap_8 = NULL;
1102   Bitmap *tmp_bitmap_16 = NULL;
1103   Bitmap *tmp_bitmap_32 = NULL;
1104   int width_final, height_final;
1105   int width_0, height_0;
1106   int width_1, height_1;
1107   int width_2, height_2;
1108   int width_4, height_4;
1109   int width_8, height_8;
1110   int width_16, height_16;
1111   int width_32, height_32;
1112   int old_width, old_height;
1113   int i;
1114
1115   print_timestamp_init("CreateScaledBitmaps");
1116
1117   old_width  = old_bitmap->width;
1118   old_height = old_bitmap->height;
1119
1120   /* calculate new image dimensions for final image size */
1121   width_final  = old_width  * zoom_factor;
1122   height_final = old_height * zoom_factor;
1123
1124   /* get image with final size (this might require scaling up) */
1125   /* ("final" size may result in non-standard tile size image) */
1126   if (zoom_factor != 1)
1127     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1128   else
1129     tmp_bitmap_final = old_bitmap;
1130
1131   UPDATE_BUSY_STATE();
1132
1133   width_0  = width_1  = width_final;
1134   height_0 = height_1 = height_final;
1135
1136   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1137
1138   if (create_small_bitmaps)
1139   {
1140     /* check if we have a non-gameplay tile size image */
1141     if (tile_size != gfx.game_tile_size)
1142     {
1143       /* get image with gameplay tile size */
1144       width_0  = width_final  * gfx.game_tile_size / tile_size;
1145       height_0 = height_final * gfx.game_tile_size / tile_size;
1146
1147       if (width_0 == old_width)
1148         tmp_bitmap_0 = old_bitmap;
1149       else if (width_0 == width_final)
1150         tmp_bitmap_0 = tmp_bitmap_final;
1151       else
1152         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1153
1154       UPDATE_BUSY_STATE();
1155     }
1156
1157     /* check if we have a non-standard tile size image */
1158     if (tile_size != gfx.standard_tile_size)
1159     {
1160       /* get image with standard tile size */
1161       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1162       height_1 = height_final * gfx.standard_tile_size / tile_size;
1163
1164       if (width_1 == old_width)
1165         tmp_bitmap_1 = old_bitmap;
1166       else if (width_1 == width_final)
1167         tmp_bitmap_1 = tmp_bitmap_final;
1168       else if (width_1 == width_0)
1169         tmp_bitmap_1 = tmp_bitmap_0;
1170       else
1171         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1172
1173       UPDATE_BUSY_STATE();
1174     }
1175
1176     /* calculate new image dimensions for small images */
1177     width_2  = width_1  / 2;
1178     height_2 = height_1 / 2;
1179     width_4  = width_1  / 4;
1180     height_4 = height_1 / 4;
1181     width_8  = width_1  / 8;
1182     height_8 = height_1 / 8;
1183     width_16  = width_1  / 16;
1184     height_16 = height_1 / 16;
1185     width_32  = width_1  / 32;
1186     height_32 = height_1 / 32;
1187
1188     /* get image with 1/2 of normal size (for use in the level editor) */
1189     if (width_2 == old_width)
1190       tmp_bitmap_2 = old_bitmap;
1191     else
1192       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1193
1194     UPDATE_BUSY_STATE();
1195
1196     /* get image with 1/4 of normal size (for use in the level editor) */
1197     if (width_4 == old_width)
1198       tmp_bitmap_4 = old_bitmap;
1199     else
1200       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1201
1202     UPDATE_BUSY_STATE();
1203
1204     /* get image with 1/8 of normal size (for use on the preview screen) */
1205     if (width_8 == old_width)
1206       tmp_bitmap_8 = old_bitmap;
1207     else
1208       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1209
1210     UPDATE_BUSY_STATE();
1211
1212     /* get image with 1/16 of normal size (for use on the preview screen) */
1213     if (width_16 == old_width)
1214       tmp_bitmap_16 = old_bitmap;
1215     else
1216       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1217
1218     UPDATE_BUSY_STATE();
1219
1220     /* get image with 1/32 of normal size (for use on the preview screen) */
1221     if (width_32 == old_width)
1222       tmp_bitmap_32 = old_bitmap;
1223     else
1224       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1225
1226     UPDATE_BUSY_STATE();
1227
1228     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1229     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1230     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1231     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1232     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1233     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1234
1235     if (width_0 != width_1)
1236       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1237
1238     if (bitmaps[IMG_BITMAP_CUSTOM])
1239       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1240     else
1241       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1242
1243     boolean free_old_bitmap = TRUE;
1244
1245     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1246       if (bitmaps[i] == old_bitmap)
1247         free_old_bitmap = FALSE;
1248
1249     if (free_old_bitmap)
1250       FreeBitmap(old_bitmap);
1251   }
1252   else
1253   {
1254     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1255   }
1256
1257   UPDATE_BUSY_STATE();
1258
1259   print_timestamp_done("CreateScaledBitmaps");
1260 }
1261
1262 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1263                                   int tile_size)
1264 {
1265   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1266 }
1267
1268 void CreateBitmapTextures(Bitmap **bitmaps)
1269 {
1270   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1271 }
1272
1273 void FreeBitmapTextures(Bitmap **bitmaps)
1274 {
1275   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1276 }
1277
1278 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1279 {
1280   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1281 }
1282
1283
1284 /* ------------------------------------------------------------------------- */
1285 /* mouse pointer functions                                                   */
1286 /* ------------------------------------------------------------------------- */
1287
1288 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1289
1290 /* XPM image definitions */
1291 static const char *cursor_image_none[] =
1292 {
1293   /* width height num_colors chars_per_pixel */
1294   "    16    16        3            1",
1295
1296   /* colors */
1297   "X c #000000",
1298   ". c #ffffff",
1299   "  c None",
1300
1301   /* pixels */
1302   "                ",
1303   "                ",
1304   "                ",
1305   "                ",
1306   "                ",
1307   "                ",
1308   "                ",
1309   "                ",
1310   "                ",
1311   "                ",
1312   "                ",
1313   "                ",
1314   "                ",
1315   "                ",
1316   "                ",
1317   "                ",
1318
1319   /* hot spot */
1320   "0,0"
1321 };
1322
1323 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1324 static const char *cursor_image_dot[] =
1325 {
1326   /* width height num_colors chars_per_pixel */
1327   "    16    16        3            1",
1328
1329   /* colors */
1330   "X c #000000",
1331   ". c #ffffff",
1332   "  c None",
1333
1334   /* pixels */
1335   " X              ",
1336   "X.X             ",
1337   " X              ",
1338   "                ",
1339   "                ",
1340   "                ",
1341   "                ",
1342   "                ",
1343   "                ",
1344   "                ",
1345   "                ",
1346   "                ",
1347   "                ",
1348   "                ",
1349   "                ",
1350   "                ",
1351
1352   /* hot spot */
1353   "1,1"
1354 };
1355 static const char **cursor_image_playfield = cursor_image_dot;
1356 #else
1357 /* some people complained about a "white dot" on the screen and thought it
1358    was a graphical error... OK, let's just remove the whole pointer :-) */
1359 static const char **cursor_image_playfield = cursor_image_none;
1360 #endif
1361
1362 static const int cursor_bit_order = BIT_ORDER_MSB;
1363
1364 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1365 {
1366   struct MouseCursorInfo *cursor;
1367   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1368   int header_lines = 4;
1369   int x, y, i;
1370
1371   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1372
1373   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1374
1375   i = -1;
1376   for (y = 0; y < cursor->width; y++)
1377   {
1378     for (x = 0; x < cursor->height; x++)
1379     {
1380       int bit_nr = x % 8;
1381       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1382
1383       if (bit_nr == 0)
1384       {
1385         i++;
1386         cursor->data[i] = cursor->mask[i] = 0;
1387       }
1388
1389       switch (image[header_lines + y][x])
1390       {
1391         case 'X':
1392           cursor->data[i] |= bit_mask;
1393           cursor->mask[i] |= bit_mask;
1394           break;
1395
1396         case '.':
1397           cursor->mask[i] |= bit_mask;
1398           break;
1399
1400         case ' ':
1401           break;
1402       }
1403     }
1404   }
1405
1406   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1407
1408   return cursor;
1409 }
1410
1411 void SetMouseCursor(int mode)
1412 {
1413   static struct MouseCursorInfo *cursor_none = NULL;
1414   static struct MouseCursorInfo *cursor_playfield = NULL;
1415   struct MouseCursorInfo *cursor_new;
1416
1417   if (cursor_none == NULL)
1418     cursor_none = get_cursor_from_image(cursor_image_none);
1419
1420   if (cursor_playfield == NULL)
1421     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1422
1423   cursor_new = (mode == CURSOR_DEFAULT   ? NULL :
1424                 mode == CURSOR_NONE      ? cursor_none :
1425                 mode == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1426
1427   SDLSetMouseCursor(cursor_new);
1428
1429   gfx.cursor_mode = mode;
1430 }
1431
1432
1433 /* ========================================================================= */
1434 /* audio functions                                                           */
1435 /* ========================================================================= */
1436
1437 void OpenAudio(void)
1438 {
1439   /* always start with reliable default values */
1440   audio.sound_available = FALSE;
1441   audio.music_available = FALSE;
1442   audio.loops_available = FALSE;
1443
1444   audio.sound_enabled = FALSE;
1445   audio.sound_deactivated = FALSE;
1446
1447   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1448   audio.mixer_pid = 0;
1449   audio.device_name = NULL;
1450   audio.device_fd = -1;
1451
1452   audio.num_channels = 0;
1453   audio.music_channel = 0;
1454   audio.first_sound_channel = 0;
1455
1456   SDLOpenAudio();
1457 }
1458
1459 void CloseAudio(void)
1460 {
1461   SDLCloseAudio();
1462
1463   audio.sound_enabled = FALSE;
1464 }
1465
1466 void SetAudioMode(boolean enabled)
1467 {
1468   if (!audio.sound_available)
1469     return;
1470
1471   audio.sound_enabled = enabled;
1472 }
1473
1474
1475 /* ========================================================================= */
1476 /* event functions                                                           */
1477 /* ========================================================================= */
1478
1479 boolean PendingEvent(void)
1480 {
1481   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1482 }
1483
1484 void NextEvent(Event *event)
1485 {
1486   SDLNextEvent(event);
1487 }
1488
1489 void PeekEvent(Event *event)
1490 {
1491 #if defined(TARGET_SDL2)
1492   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1493 #else
1494   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_ALLEVENTS);
1495 #endif
1496 }
1497
1498 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1499 {
1500 #if defined(TARGET_SDL2)
1501   /* key up/down events in SDL2 do not return text characters anymore */
1502   return event->keysym.sym;
1503 #else
1504
1505 #if ENABLE_UNUSED_CODE
1506   printf("unicode == '%d', sym == '%d', mod == '0x%04x'\n",
1507          (int)event->keysym.unicode,
1508          (int)event->keysym.sym,
1509          (int)SDL_GetModState());
1510 #endif
1511
1512   if (with_modifiers &&
1513       event->keysym.unicode > 0x0000 &&
1514       event->keysym.unicode < 0x2000)
1515     return event->keysym.unicode;
1516   else
1517     return event->keysym.sym;
1518
1519 #endif
1520 }
1521
1522 KeyMod HandleKeyModState(Key key, int key_status)
1523 {
1524   static KeyMod current_modifiers = KMOD_None;
1525
1526   if (key != KSYM_UNDEFINED)    /* new key => check for modifier key change */
1527   {
1528     KeyMod new_modifier = KMOD_None;
1529
1530     switch(key)
1531     {
1532       case KSYM_Shift_L:
1533         new_modifier = KMOD_Shift_L;
1534         break;
1535       case KSYM_Shift_R:
1536         new_modifier = KMOD_Shift_R;
1537         break;
1538       case KSYM_Control_L:
1539         new_modifier = KMOD_Control_L;
1540         break;
1541       case KSYM_Control_R:
1542         new_modifier = KMOD_Control_R;
1543         break;
1544       case KSYM_Meta_L:
1545         new_modifier = KMOD_Meta_L;
1546         break;
1547       case KSYM_Meta_R:
1548         new_modifier = KMOD_Meta_R;
1549         break;
1550       case KSYM_Alt_L:
1551         new_modifier = KMOD_Alt_L;
1552         break;
1553       case KSYM_Alt_R:
1554         new_modifier = KMOD_Alt_R;
1555         break;
1556       default:
1557         break;
1558     }
1559
1560     if (key_status == KEY_PRESSED)
1561       current_modifiers |= new_modifier;
1562     else
1563       current_modifiers &= ~new_modifier;
1564   }
1565
1566   return current_modifiers;
1567 }
1568
1569 KeyMod GetKeyModState()
1570 {
1571   return (KeyMod)SDL_GetModState();
1572 }
1573
1574 KeyMod GetKeyModStateFromEvents()
1575 {
1576   /* always use key modifier state as tracked from key events (this is needed
1577      if the modifier key event was injected into the event queue, but the key
1578      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1579      query the keys as held pressed on the keyboard) -- this case is currently
1580      only used to filter out clipboard insert events from "True X-Mouse" tool */
1581
1582   return HandleKeyModState(KSYM_UNDEFINED, 0);
1583 }
1584
1585 void StartTextInput(int x, int y, int width, int height)
1586 {
1587 #if defined(TARGET_SDL2)
1588 #if defined(HAS_SCREEN_KEYBOARD)
1589   SDL_StartTextInput();
1590
1591   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1592   {
1593     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1594     video.shifted_up_delay = SDL_GetTicks();
1595     video.shifted_up = TRUE;
1596   }
1597 #endif
1598 #endif
1599 }
1600
1601 void StopTextInput()
1602 {
1603 #if defined(TARGET_SDL2)
1604 #if defined(HAS_SCREEN_KEYBOARD)
1605   SDL_StopTextInput();
1606
1607   if (video.shifted_up)
1608   {
1609     video.shifted_up_pos = 0;
1610     video.shifted_up_delay = SDL_GetTicks();
1611     video.shifted_up = FALSE;
1612   }
1613 #endif
1614 #endif
1615 }
1616
1617 boolean CheckCloseWindowEvent(ClientMessageEvent *event)
1618 {
1619   if (event->type != EVENT_CLIENTMESSAGE)
1620     return FALSE;
1621
1622   return TRUE;          /* the only possible message here is SDL_QUIT */
1623 }
1624
1625
1626 /* ========================================================================= */
1627 /* joystick functions                                                        */
1628 /* ========================================================================= */
1629
1630 void InitJoysticks()
1631 {
1632   int i;
1633
1634 #if defined(NO_JOYSTICK)
1635   return;       /* joysticks generally deactivated by compile-time directive */
1636 #endif
1637
1638   /* always start with reliable default values */
1639   joystick.status = JOYSTICK_NOT_AVAILABLE;
1640   for (i = 0; i < MAX_PLAYERS; i++)
1641     joystick.nr[i] = -1;                /* no joystick configured */
1642
1643   SDLInitJoysticks();
1644 }
1645
1646 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1647 {
1648   return SDLReadJoystick(nr, x, y, b1, b2);
1649 }
1650
1651 boolean CheckJoystickOpened(int nr)
1652 {
1653   return SDLCheckJoystickOpened(nr);
1654 }
1655
1656 void ClearJoystickState()
1657 {
1658   SDLClearJoystickState();
1659 }