1 //
2 // Copyright(C) 2005-2014 Simon Howard
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 //
15 // Text mode emulation in SDL
16 //
17 
18 #include "SDL.h"
19 
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "doomkeys.h"
26 
27 #include "txt_main.h"
28 #include "txt_sdl.h"
29 #include "txt_utf8.h"
30 
31 #if defined(_MSC_VER) && !defined(__cplusplus)
32 #define inline __inline
33 #endif
34 
35 typedef struct
36 {
37     char *name;
38     const uint8_t *data;
39     unsigned int w;
40     unsigned int h;
41 } txt_font_t;
42 
43 // Fonts:
44 
45 #include "fonts/small.h"
46 #include "fonts/normal.h"
47 #include "fonts/large.h"
48 #include "fonts/codepage.h"
49 
50 // Time between character blinks in ms
51 
52 #define BLINK_PERIOD 250
53 
54 SDL_Window *TXT_SDLWindow;
55 static SDL_Surface *screenbuffer;
56 static unsigned char *screendata;
57 static SDL_Renderer *renderer;
58 
59 // Current input mode.
60 static txt_input_mode_t input_mode = TXT_INPUT_NORMAL;
61 
62 // Dimensions of the screen image in screen coordinates (not pixels); this
63 // is the value that was passed to SDL_CreateWindow().
64 static int screen_image_w, screen_image_h;
65 
66 static TxtSDLEventCallbackFunc event_callback;
67 static void *event_callback_data;
68 
69 // Font we are using:
70 static const txt_font_t *font;
71 
72 // Dummy "font" that means to try highdpi rendering, or fallback to
73 // normal_font otherwise.
74 static const txt_font_t highdpi_font = { "normal-highdpi", NULL, 8, 16 };
75 
76 // Mapping from SDL keyboard scancode to internal key code.
77 static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY;
78 
79 // String names of keys. This is a fallback; we usually use the SDL API.
80 static const struct {
81     int key;
82     const char *name;
83 } key_names[] = KEY_NAMES_ARRAY;
84 
85 // Unicode key mapping; see codepage.h.
86 static const short code_page_to_unicode[] = CODE_PAGE_TO_UNICODE;
87 
88 static SDL_Color ega_colors[] =
89 {
90     {0x00, 0x00, 0x00, 0xff},          // 0: Black
91     {0x00, 0x00, 0xa8, 0xff},          // 1: Blue
92     {0x00, 0xa8, 0x00, 0xff},          // 2: Green
93     {0x00, 0xa8, 0xa8, 0xff},          // 3: Cyan
94     {0xa8, 0x00, 0x00, 0xff},          // 4: Red
95     {0xa8, 0x00, 0xa8, 0xff},          // 5: Magenta
96     {0xa8, 0x54, 0x00, 0xff},          // 6: Brown
97     {0xa8, 0xa8, 0xa8, 0xff},          // 7: Grey
98     {0x54, 0x54, 0x54, 0xff},          // 8: Dark grey
99     {0x54, 0x54, 0xfe, 0xff},          // 9: Bright blue
100     {0x54, 0xfe, 0x54, 0xff},          // 10: Bright green
101     {0x54, 0xfe, 0xfe, 0xff},          // 11: Bright cyan
102     {0xfe, 0x54, 0x54, 0xff},          // 12: Bright red
103     {0xfe, 0x54, 0xfe, 0xff},          // 13: Bright magenta
104     {0xfe, 0xfe, 0x54, 0xff},          // 14: Yellow
105     {0xfe, 0xfe, 0xfe, 0xff},          // 15: Bright white
106 };
107 
108 #ifdef _WIN32
109 
110 #define WIN32_LEAN_AND_MEAN
111 #include <windows.h>
112 
113 // Examine system DPI settings to determine whether to use the large font.
114 
Win32_UseLargeFont(void)115 static int Win32_UseLargeFont(void)
116 {
117     HDC hdc = GetDC(NULL);
118     int dpix;
119 
120     if (!hdc)
121     {
122         return 0;
123     }
124 
125     dpix = GetDeviceCaps(hdc, LOGPIXELSX);
126     ReleaseDC(NULL, hdc);
127 
128     // 144 is the DPI when using "150%" scaling. If the user has this set
129     // then consider this an appropriate threshold for using the large font.
130 
131     return dpix >= 144;
132 }
133 
134 #endif
135 
FontForName(char * name)136 static const txt_font_t *FontForName(char *name)
137 {
138     int i;
139     const txt_font_t *fonts[] =
140     {
141         &small_font,
142         &normal_font,
143         &large_font,
144         &highdpi_font,
145         NULL,
146     };
147 
148     for (i = 0; fonts[i]->name != NULL; ++i)
149     {
150         if (!strcmp(fonts[i]->name, name))
151         {
152             return fonts[i];
153         }
154     }
155     return NULL;
156 }
157 
158 //
159 // Select the font to use, based on screen resolution
160 //
161 // If the highest screen resolution available is less than
162 // 640x480, use the small font.
163 //
164 
ChooseFont(void)165 static void ChooseFont(void)
166 {
167     SDL_DisplayMode desktop_info;
168     char *env;
169 
170     // Allow normal selection to be overridden from an environment variable:
171     env = getenv("TEXTSCREEN_FONT");
172     if (env != NULL)
173     {
174         font = FontForName(env);
175 
176         if (font != NULL)
177         {
178             return;
179         }
180     }
181 
182     // Get desktop resolution.
183     // If in doubt and we can't get a list, always prefer to
184     // fall back to the normal font:
185     if (SDL_GetCurrentDisplayMode(0, &desktop_info))
186     {
187         font = &highdpi_font;
188         return;
189     }
190 
191     // On tiny low-res screens (eg. palmtops) use the small font.
192     // If the screen resolution is at least 1920x1080, this is
193     // a modern high-resolution display, and we can use the
194     // large font.
195 
196     if (desktop_info.w < 640 || desktop_info.h < 480)
197     {
198         font = &small_font;
199     }
200 #ifdef _WIN32
201     // On Windows we can use the system DPI settings to make a
202     // more educated guess about whether to use the large font.
203 
204     else if (Win32_UseLargeFont())
205     {
206         font = &large_font;
207     }
208 #endif
209     // TODO: Detect high DPI on Linux by inquiring about Gtk+ scale
210     // settings. This looks like it should just be a case of shelling
211     // out to invoke the 'gsettings' command, eg.
212     //   gsettings get org.gnome.desktop.interface text-scaling-factor
213     // and using large_font if the result is >= 2.
214     else
215     {
216         // highdpi_font usually means normal_font (the normal resolution
217         // version), but actually means "set the HIGHDPI flag and try
218         // to use large_font if we initialize successfully".
219         font = &highdpi_font;
220     }
221 }
222 
223 //
224 // Initialize text mode screen
225 //
226 // Returns 1 if successful, 0 if an error occurred
227 //
228 
TXT_Init(void)229 int TXT_Init(void)
230 {
231     int flags = 0;
232 
233     if (SDL_Init(SDL_INIT_VIDEO) < 0)
234     {
235         return 0;
236     }
237 
238     ChooseFont();
239 
240     screen_image_w = TXT_SCREEN_W * font->w;
241     screen_image_h = TXT_SCREEN_H * font->h;
242 
243     // If highdpi_font is selected, try to initialize high dpi rendering.
244     if (font == &highdpi_font)
245     {
246         flags |= SDL_WINDOW_ALLOW_HIGHDPI;
247     }
248 
249     TXT_SDLWindow =
250         SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
251                          screen_image_w, screen_image_h, flags);
252 
253     if (TXT_SDLWindow == NULL)
254         return 0;
255 
256     renderer = SDL_CreateRenderer(TXT_SDLWindow, -1, 0);
257 
258     // Special handling for OS X retina display. If we successfully set the
259     // highdpi flag, check the output size for the screen renderer. If we get
260     // the 2x doubled size we expect from a retina display, use the large font
261     // for drawing the screen.
262     if ((SDL_GetWindowFlags(TXT_SDLWindow) & SDL_WINDOW_ALLOW_HIGHDPI) != 0)
263     {
264         int render_w, render_h;
265 
266         if (SDL_GetRendererOutputSize(renderer, &render_w, &render_h) == 0
267          && render_w >= TXT_SCREEN_W * large_font.w
268          && render_h >= TXT_SCREEN_H * large_font.h)
269         {
270             font = &large_font;
271             // Note that we deliberately do not update screen_image_{w,h}
272             // since these are the dimensions of textscreen image in screen
273             // coordinates, not pixels.
274         }
275     }
276 
277     // Failed to initialize for high dpi (retina display) rendering? If so
278     // then use the normal resolution font instead.
279     if (font == &highdpi_font)
280     {
281         font = &normal_font;
282     }
283 
284     // Instead, we draw everything into an intermediate 8-bit surface
285     // the same dimensions as the screen. SDL then takes care of all the
286     // 8->32 bit (or whatever depth) color conversions for us.
287     screenbuffer = SDL_CreateRGBSurface(0,
288                                         TXT_SCREEN_W * font->w,
289                                         TXT_SCREEN_H * font->h,
290                                         8, 0, 0, 0, 0);
291 
292     SDL_LockSurface(screenbuffer);
293     SDL_SetPaletteColors(screenbuffer->format->palette, ega_colors, 0, 16);
294     SDL_UnlockSurface(screenbuffer);
295 
296     screendata = malloc(TXT_SCREEN_W * TXT_SCREEN_H * 2);
297     memset(screendata, 0, TXT_SCREEN_W * TXT_SCREEN_H * 2);
298 
299     return 1;
300 }
301 
TXT_Shutdown(void)302 void TXT_Shutdown(void)
303 {
304     free(screendata);
305     screendata = NULL;
306     SDL_FreeSurface(screenbuffer);
307     screenbuffer = NULL;
308     SDL_QuitSubSystem(SDL_INIT_VIDEO);
309 }
310 
TXT_GetScreenData(void)311 unsigned char *TXT_GetScreenData(void)
312 {
313     return screendata;
314 }
315 
UpdateCharacter(int x,int y)316 static inline void UpdateCharacter(int x, int y)
317 {
318     unsigned char character;
319     const uint8_t *p;
320     unsigned char *s, *s1;
321     unsigned int bit;
322     int bg, fg;
323     unsigned int x1, y1;
324 
325     p = &screendata[(y * TXT_SCREEN_W + x) * 2];
326     character = p[0];
327 
328     fg = p[1] & 0xf;
329     bg = (p[1] >> 4) & 0xf;
330 
331     if (bg & 0x8)
332     {
333         // blinking
334 
335         bg &= ~0x8;
336 
337         if (((SDL_GetTicks() / BLINK_PERIOD) % 2) == 0)
338         {
339             fg = bg;
340         }
341     }
342 
343     // How many bytes per line?
344     p = &font->data[(character * font->w * font->h) / 8];
345     bit = 0;
346 
347     s = ((unsigned char *) screenbuffer->pixels)
348       + (y * font->h * screenbuffer->pitch)
349       + (x * font->w);
350 
351     for (y1=0; y1<font->h; ++y1)
352     {
353         s1 = s;
354 
355         for (x1=0; x1<font->w; ++x1)
356         {
357             if (*p & (1 << bit))
358             {
359                 *s1++ = fg;
360             }
361             else
362             {
363                 *s1++ = bg;
364             }
365 
366             ++bit;
367             if (bit == 8)
368             {
369                 ++p;
370                 bit = 0;
371             }
372         }
373 
374         s += screenbuffer->pitch;
375     }
376 }
377 
LimitToRange(int val,int min,int max)378 static int LimitToRange(int val, int min, int max)
379 {
380     if (val < min)
381     {
382         return min;
383     }
384     else if (val > max)
385     {
386         return max;
387     }
388     else
389     {
390         return val;
391     }
392 }
393 
GetDestRect(SDL_Rect * rect)394 static void GetDestRect(SDL_Rect *rect)
395 {
396     int w, h;
397 
398     SDL_GetRendererOutputSize(renderer, &w, &h);
399     rect->x = (w - screenbuffer->w) / 2;
400     rect->y = (h - screenbuffer->h) / 2;
401     rect->w = screenbuffer->w;
402     rect->h = screenbuffer->h;
403 }
404 
TXT_UpdateScreenArea(int x,int y,int w,int h)405 void TXT_UpdateScreenArea(int x, int y, int w, int h)
406 {
407     SDL_Texture *screentx;
408     SDL_Rect rect;
409     int x1, y1;
410     int x_end;
411     int y_end;
412 
413     SDL_LockSurface(screenbuffer);
414 
415     x_end = LimitToRange(x + w, 0, TXT_SCREEN_W);
416     y_end = LimitToRange(y + h, 0, TXT_SCREEN_H);
417     x = LimitToRange(x, 0, TXT_SCREEN_W);
418     y = LimitToRange(y, 0, TXT_SCREEN_H);
419 
420     for (y1=y; y1<y_end; ++y1)
421     {
422         for (x1=x; x1<x_end; ++x1)
423         {
424             UpdateCharacter(x1, y1);
425         }
426     }
427 
428     SDL_UnlockSurface(screenbuffer);
429 
430     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
431 
432     // TODO: This is currently creating a new texture every time we render
433     // the screen; find a more efficient way to do it.
434     screentx = SDL_CreateTextureFromSurface(renderer, screenbuffer);
435 
436     SDL_RenderClear(renderer);
437     GetDestRect(&rect);
438     SDL_RenderCopy(renderer, screentx, NULL, &rect);
439     SDL_RenderPresent(renderer);
440 
441     SDL_DestroyTexture(screentx);
442 }
443 
TXT_UpdateScreen(void)444 void TXT_UpdateScreen(void)
445 {
446     TXT_UpdateScreenArea(0, 0, TXT_SCREEN_W, TXT_SCREEN_H);
447 }
448 
TXT_GetMousePosition(int * x,int * y)449 void TXT_GetMousePosition(int *x, int *y)
450 {
451     int window_w, window_h;
452     int origin_x, origin_y;
453 
454     SDL_GetMouseState(x, y);
455 
456     // Translate mouse position from 'pixel' position into character position.
457     // We are working here in screen coordinates and not pixels, since this is
458     // what SDL_GetWindowSize() returns; we must calculate and subtract the
459     // origin position since we center the image within the window.
460     SDL_GetWindowSize(TXT_SDLWindow, &window_w, &window_h);
461     origin_x = (window_w - screen_image_w) / 2;
462     origin_y = (window_h - screen_image_h) / 2;
463     *x = ((*x - origin_x) * TXT_SCREEN_W) / screen_image_w;
464     *y = ((*y - origin_y) * TXT_SCREEN_H) / screen_image_h;
465 
466     if (*x < 0)
467     {
468         *x = 0;
469     }
470     else if (*x >= TXT_SCREEN_W)
471     {
472         *x = TXT_SCREEN_W - 1;
473     }
474     if (*y < 0)
475     {
476         *y = 0;
477     }
478     else if (*y >= TXT_SCREEN_H)
479     {
480         *y = TXT_SCREEN_H - 1;
481     }
482 }
483 
484 //
485 // Translates the SDL key
486 //
487 
488 // XXX: duplicate from doomtype.h
489 #define arrlen(array) (sizeof(array) / sizeof(*array))
490 
TranslateScancode(SDL_Scancode scancode)491 static int TranslateScancode(SDL_Scancode scancode)
492 {
493     switch (scancode)
494     {
495         case SDL_SCANCODE_LCTRL:
496         case SDL_SCANCODE_RCTRL:
497             return KEY_RCTRL;
498 
499         case SDL_SCANCODE_LSHIFT:
500         case SDL_SCANCODE_RSHIFT:
501             return KEY_RSHIFT;
502 
503         case SDL_SCANCODE_LALT:
504             return KEY_LALT;
505 
506         case SDL_SCANCODE_RALT:
507             return KEY_RALT;
508 
509         default:
510             if (scancode < arrlen(scancode_translate_table))
511             {
512                 return scancode_translate_table[scancode];
513             }
514             else
515             {
516                 return 0;
517             }
518     }
519 }
520 
TranslateKeysym(SDL_Keysym * sym)521 static int TranslateKeysym(SDL_Keysym *sym)
522 {
523     int translated;
524 
525     // We cheat here and make use of TranslateScancode. The range of keys
526     // associated with printable characters is pretty contiguous, so if it's
527     // inside that range we want the localized version of the key instead.
528     translated = TranslateScancode(sym->scancode);
529 
530     if (translated >= 0x20 && translated < 0x7f)
531     {
532         return sym->sym;
533     }
534     else
535     {
536         return translated;
537     }
538 }
539 
540 // Convert an SDL button index to textscreen button index.
541 //
542 // Note special cases because 2 == mid in SDL, 3 == mid in textscreen/setup
543 
SDLButtonToTXTButton(int button)544 static int SDLButtonToTXTButton(int button)
545 {
546     switch (button)
547     {
548         case SDL_BUTTON_LEFT:
549             return TXT_MOUSE_LEFT;
550         case SDL_BUTTON_RIGHT:
551             return TXT_MOUSE_RIGHT;
552         case SDL_BUTTON_MIDDLE:
553             return TXT_MOUSE_MIDDLE;
554         default:
555             return TXT_MOUSE_BASE + button - 1;
556     }
557 }
558 
559 // Convert an SDL wheel motion to a textscreen button index.
560 
SDLWheelToTXTButton(SDL_MouseWheelEvent * wheel)561 static int SDLWheelToTXTButton(SDL_MouseWheelEvent *wheel)
562 {
563     if (wheel->y <= 0)
564     {
565         return TXT_MOUSE_SCROLLDOWN;
566     }
567     else
568     {
569         return TXT_MOUSE_SCROLLUP;
570     }
571 }
572 
MouseHasMoved(void)573 static int MouseHasMoved(void)
574 {
575     static int last_x = 0, last_y = 0;
576     int x, y;
577 
578     TXT_GetMousePosition(&x, &y);
579 
580     if (x != last_x || y != last_y)
581     {
582         last_x = x; last_y = y;
583         return 1;
584     }
585     else
586     {
587         return 0;
588     }
589 }
590 
TXT_GetChar(void)591 signed int TXT_GetChar(void)
592 {
593     SDL_Event ev;
594 
595     while (SDL_PollEvent(&ev))
596     {
597         // If there is an event callback, allow it to intercept this
598         // event.
599 
600         if (event_callback != NULL)
601         {
602             if (event_callback(&ev, event_callback_data))
603             {
604                 continue;
605             }
606         }
607 
608         // Process the event.
609 
610         switch (ev.type)
611         {
612             case SDL_MOUSEBUTTONDOWN:
613                 if (ev.button.button < TXT_MAX_MOUSE_BUTTONS)
614                 {
615                     return SDLButtonToTXTButton(ev.button.button);
616                 }
617                 break;
618 
619             case SDL_MOUSEWHEEL:
620                 return SDLWheelToTXTButton(&ev.wheel);
621 
622             case SDL_KEYDOWN:
623                 switch (input_mode)
624                 {
625                     case TXT_INPUT_RAW:
626                         return TranslateScancode(ev.key.keysym.scancode);
627                     case TXT_INPUT_NORMAL:
628                         return TranslateKeysym(&ev.key.keysym);
629                     case TXT_INPUT_TEXT:
630                         // We ignore key inputs in this mode, except for a
631                         // few special cases needed during text input:
632                         if (ev.key.keysym.sym == SDLK_ESCAPE
633                          || ev.key.keysym.sym == SDLK_BACKSPACE
634                          || ev.key.keysym.sym == SDLK_RETURN)
635                         {
636                             return TranslateKeysym(&ev.key.keysym);
637                         }
638                         break;
639                 }
640                 break;
641 
642             case SDL_TEXTINPUT:
643                 if (input_mode == TXT_INPUT_TEXT)
644                 {
645                     // TODO: Support input of more than just the first char?
646                     const char *p = ev.text.text;
647                     int result = TXT_DecodeUTF8(&p);
648                     // 0-127 is ASCII, but we map non-ASCII Unicode chars into
649                     // a higher range to avoid conflicts with special keys.
650                     return TXT_UNICODE_TO_KEY(result);
651                 }
652                 break;
653 
654             case SDL_QUIT:
655                 // Quit = escape
656                 return 27;
657 
658             case SDL_MOUSEMOTION:
659                 if (MouseHasMoved())
660                 {
661                     return 0;
662                 }
663 
664             default:
665                 break;
666         }
667     }
668 
669     return -1;
670 }
671 
TXT_GetModifierState(txt_modifier_t mod)672 int TXT_GetModifierState(txt_modifier_t mod)
673 {
674     SDL_Keymod state;
675 
676     state = SDL_GetModState();
677 
678     switch (mod)
679     {
680         case TXT_MOD_SHIFT:
681             return (state & KMOD_SHIFT) != 0;
682         case TXT_MOD_CTRL:
683             return (state & KMOD_CTRL) != 0;
684         case TXT_MOD_ALT:
685             return (state & KMOD_ALT) != 0;
686         default:
687             return 0;
688     }
689 }
690 
TXT_UnicodeCharacter(unsigned int c)691 int TXT_UnicodeCharacter(unsigned int c)
692 {
693     unsigned int i;
694 
695     // Check the code page mapping to see if this character maps
696     // to anything.
697 
698     for (i = 0; i < arrlen(code_page_to_unicode); ++i)
699     {
700         if (code_page_to_unicode[i] == c)
701         {
702             return i;
703         }
704     }
705 
706     return -1;
707 }
708 
709 // Returns true if the given UTF8 key name is printable to the screen.
PrintableName(const char * s)710 static int PrintableName(const char *s)
711 {
712     const char *p;
713     unsigned int c;
714 
715     p = s;
716     while (*p != '\0')
717     {
718         c = TXT_DecodeUTF8(&p);
719         if (TXT_UnicodeCharacter(c) < 0)
720         {
721             return 0;
722         }
723     }
724 
725     return 1;
726 }
727 
NameForKey(int key)728 static const char *NameForKey(int key)
729 {
730     const char *result;
731     int i;
732 
733     // Overrides purely for aesthetical reasons, so that default
734     // window accelerator keys match those of setup.exe.
735     switch (key)
736     {
737         case KEY_ESCAPE: return "ESC";
738         case KEY_ENTER:  return "ENTER";
739         default:
740             break;
741     }
742 
743     // This key presumably maps to a scan code that is listed in the
744     // translation table. Find which mapping and once we have a scancode,
745     // we can convert it into a virtual key, then a string via SDL.
746     for (i = 0; i < arrlen(scancode_translate_table); ++i)
747     {
748         if (scancode_translate_table[i] == key)
749         {
750             result = SDL_GetKeyName(SDL_GetKeyFromScancode(i));
751             if (TXT_UTF8_Strlen(result) > 6 || !PrintableName(result))
752             {
753                 break;
754             }
755             return result;
756         }
757     }
758 
759     // Use US English fallback names, if the localized name is too long,
760     // not found in the scancode table, or contains unprintable chars
761     // (non-extended ASCII character set):
762     for (i = 0; i < arrlen(key_names); ++i)
763     {
764         if (key_names[i].key == key)
765         {
766             return key_names[i].name;
767         }
768     }
769 
770     return NULL;
771 }
772 
TXT_GetKeyDescription(int key,char * buf,size_t buf_len)773 void TXT_GetKeyDescription(int key, char *buf, size_t buf_len)
774 {
775     const char *keyname;
776     int i;
777 
778     keyname = NameForKey(key);
779 
780     if (keyname != NULL)
781     {
782         TXT_StringCopy(buf, keyname, buf_len);
783 
784         // Key description should be all-uppercase to match setup.exe.
785         for (i = 0; buf[i] != '\0'; ++i)
786         {
787             buf[i] = toupper(buf[i]);
788         }
789     }
790     else
791     {
792         TXT_snprintf(buf, buf_len, "??%i", key);
793     }
794 }
795 
796 // Searches the desktop screen buffer to determine whether there are any
797 // blinking characters.
798 
TXT_ScreenHasBlinkingChars(void)799 int TXT_ScreenHasBlinkingChars(void)
800 {
801     int x, y;
802     unsigned char *p;
803 
804     // Check all characters in screen buffer
805 
806     for (y=0; y<TXT_SCREEN_H; ++y)
807     {
808         for (x=0; x<TXT_SCREEN_W; ++x)
809         {
810             p = &screendata[(y * TXT_SCREEN_W + x) * 2];
811 
812             if (p[1] & 0x80)
813             {
814                 // This character is blinking
815 
816                 return 1;
817             }
818         }
819     }
820 
821     // None found
822 
823     return 0;
824 }
825 
826 // Sleeps until an event is received, the screen needs to be redrawn,
827 // or until timeout expires (if timeout != 0)
828 
TXT_Sleep(int timeout)829 void TXT_Sleep(int timeout)
830 {
831     unsigned int start_time;
832 
833     if (TXT_ScreenHasBlinkingChars())
834     {
835         int time_to_next_blink;
836 
837         time_to_next_blink = BLINK_PERIOD - (SDL_GetTicks() % BLINK_PERIOD);
838 
839         // There are blinking characters on the screen, so we
840         // must time out after a while
841 
842         if (timeout == 0 || timeout > time_to_next_blink)
843         {
844             // Add one so it is always positive
845 
846             timeout = time_to_next_blink + 1;
847         }
848     }
849 
850     if (timeout == 0)
851     {
852         // We can just wait forever until an event occurs
853 
854         SDL_WaitEvent(NULL);
855     }
856     else
857     {
858         // Sit in a busy loop until the timeout expires or we have to
859         // redraw the blinking screen
860 
861         start_time = SDL_GetTicks();
862 
863         while (SDL_GetTicks() < start_time + timeout)
864         {
865             if (SDL_PollEvent(NULL) != 0)
866             {
867                 // Received an event, so stop waiting
868 
869                 break;
870             }
871 
872             // Don't hog the CPU
873 
874             SDL_Delay(1);
875         }
876     }
877 }
878 
TXT_SetInputMode(txt_input_mode_t mode)879 void TXT_SetInputMode(txt_input_mode_t mode)
880 {
881     if (mode == TXT_INPUT_TEXT && !SDL_IsTextInputActive())
882     {
883         SDL_StartTextInput();
884     }
885     else if (SDL_IsTextInputActive() && mode != TXT_INPUT_TEXT)
886     {
887         SDL_StopTextInput();
888     }
889 
890     input_mode = mode;
891 }
892 
TXT_SetWindowTitle(char * title)893 void TXT_SetWindowTitle(char *title)
894 {
895     SDL_SetWindowTitle(TXT_SDLWindow, title);
896 }
897 
TXT_SDL_SetEventCallback(TxtSDLEventCallbackFunc callback,void * user_data)898 void TXT_SDL_SetEventCallback(TxtSDLEventCallbackFunc callback, void *user_data)
899 {
900     event_callback = callback;
901     event_callback_data = user_data;
902 }
903 
904 // Safe string functions.
905 
TXT_StringCopy(char * dest,const char * src,size_t dest_len)906 void TXT_StringCopy(char *dest, const char *src, size_t dest_len)
907 {
908     if (dest_len < 1)
909     {
910         return;
911     }
912 
913     dest[dest_len - 1] = '\0';
914     strncpy(dest, src, dest_len - 1);
915 }
916 
TXT_StringConcat(char * dest,const char * src,size_t dest_len)917 void TXT_StringConcat(char *dest, const char *src, size_t dest_len)
918 {
919     size_t offset;
920 
921     offset = strlen(dest);
922     if (offset > dest_len)
923     {
924         offset = dest_len;
925     }
926 
927     TXT_StringCopy(dest + offset, src, dest_len - offset);
928 }
929 
930 // On Windows, vsnprintf() is _vsnprintf().
931 #ifdef _WIN32
932 #if _MSC_VER < 1400 /* not needed for Visual Studio 2008 */
933 #define vsnprintf _vsnprintf
934 #endif
935 #endif
936 
937 // Safe, portable vsnprintf().
TXT_vsnprintf(char * buf,size_t buf_len,const char * s,va_list args)938 int TXT_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args)
939 {
940     int result;
941 
942     if (buf_len < 1)
943     {
944         return 0;
945     }
946 
947     // Windows (and other OSes?) has a vsnprintf() that doesn't always
948     // append a trailing \0. So we must do it, and write into a buffer
949     // that is one byte shorter; otherwise this function is unsafe.
950     result = vsnprintf(buf, buf_len, s, args);
951 
952     // If truncated, change the final char in the buffer to a \0.
953     // A negative result indicates a truncated buffer on Windows.
954     if (result < 0 || result >= buf_len)
955     {
956         buf[buf_len - 1] = '\0';
957         result = buf_len - 1;
958     }
959 
960     return result;
961 }
962 
963 // Safe, portable snprintf().
TXT_snprintf(char * buf,size_t buf_len,const char * s,...)964 int TXT_snprintf(char *buf, size_t buf_len, const char *s, ...)
965 {
966     va_list args;
967     int result;
968     va_start(args, s);
969     result = TXT_vsnprintf(buf, buf_len, s, args);
970     va_end(args);
971     return result;
972 }
973 
974