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