939 lines
23 KiB
C
939 lines
23 KiB
C
//
|
|
// Copyright(C) 2005-2014 Simon Howard
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
//
|
|
// Text mode emulation in SDL
|
|
//
|
|
|
|
#include "SDL.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "doomkeys.h"
|
|
|
|
#include "txt_main.h"
|
|
#include "txt_sdl.h"
|
|
|
|
#if defined(_MSC_VER) && !defined(__cplusplus)
|
|
#define inline __inline
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
unsigned char *data;
|
|
unsigned int w;
|
|
unsigned int h;
|
|
} txt_font_t;
|
|
|
|
// Fonts:
|
|
|
|
#include "txt_font.h"
|
|
#include "txt_largefont.h"
|
|
#include "txt_smallfont.h"
|
|
|
|
// Time between character blinks in ms
|
|
|
|
#define BLINK_PERIOD 250
|
|
|
|
static SDL_Surface *screen;
|
|
static SDL_Surface *screenbuffer;
|
|
static unsigned char *screendata;
|
|
static int key_mapping = 1;
|
|
|
|
static TxtSDLEventCallbackFunc event_callback;
|
|
static void *event_callback_data;
|
|
|
|
static int modifier_state[TXT_NUM_MODIFIERS];
|
|
|
|
// Font we are using:
|
|
|
|
static txt_font_t *font;
|
|
|
|
//#define TANGO
|
|
|
|
#ifndef TANGO
|
|
|
|
static SDL_Color ega_colors[] =
|
|
{
|
|
{0x00, 0x00, 0x00, 0x00}, // 0: Black
|
|
{0x00, 0x00, 0xa8, 0x00}, // 1: Blue
|
|
{0x00, 0xa8, 0x00, 0x00}, // 2: Green
|
|
{0x00, 0xa8, 0xa8, 0x00}, // 3: Cyan
|
|
{0xa8, 0x00, 0x00, 0x00}, // 4: Red
|
|
{0xa8, 0x00, 0xa8, 0x00}, // 5: Magenta
|
|
{0xa8, 0x54, 0x00, 0x00}, // 6: Brown
|
|
{0xa8, 0xa8, 0xa8, 0x00}, // 7: Grey
|
|
{0x54, 0x54, 0x54, 0x00}, // 8: Dark grey
|
|
{0x54, 0x54, 0xfe, 0x00}, // 9: Bright blue
|
|
{0x54, 0xfe, 0x54, 0x00}, // 10: Bright green
|
|
{0x54, 0xfe, 0xfe, 0x00}, // 11: Bright cyan
|
|
{0xfe, 0x54, 0x54, 0x00}, // 12: Bright red
|
|
{0xfe, 0x54, 0xfe, 0x00}, // 13: Bright magenta
|
|
{0xfe, 0xfe, 0x54, 0x00}, // 14: Yellow
|
|
{0xfe, 0xfe, 0xfe, 0x00}, // 15: Bright white
|
|
};
|
|
|
|
#else
|
|
|
|
// Colors that fit the Tango desktop guidelines: see
|
|
// http://tango.freedesktop.org/ also
|
|
// http://uwstopia.nl/blog/2006/07/tango-terminal
|
|
|
|
static SDL_Color ega_colors[] =
|
|
{
|
|
{0x2e, 0x34, 0x36, 0x00}, // 0: Black
|
|
{0x34, 0x65, 0xa4, 0x00}, // 1: Blue
|
|
{0x4e, 0x9a, 0x06, 0x00}, // 2: Green
|
|
{0x06, 0x98, 0x9a, 0x00}, // 3: Cyan
|
|
{0xcc, 0x00, 0x00, 0x00}, // 4: Red
|
|
{0x75, 0x50, 0x7b, 0x00}, // 5: Magenta
|
|
{0xc4, 0xa0, 0x00, 0x00}, // 6: Brown
|
|
{0xd3, 0xd7, 0xcf, 0x00}, // 7: Grey
|
|
{0x55, 0x57, 0x53, 0x00}, // 8: Dark grey
|
|
{0x72, 0x9f, 0xcf, 0x00}, // 9: Bright blue
|
|
{0x8a, 0xe2, 0x34, 0x00}, // 10: Bright green
|
|
{0x34, 0xe2, 0xe2, 0x00}, // 11: Bright cyan
|
|
{0xef, 0x29, 0x29, 0x00}, // 12: Bright red
|
|
{0x34, 0xe2, 0xe2, 0x00}, // 13: Bright magenta
|
|
{0xfc, 0xe9, 0x4f, 0x00}, // 14: Yellow
|
|
{0xee, 0xee, 0xec, 0x00}, // 15: Bright white
|
|
};
|
|
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
// Examine system DPI settings to determine whether to use the large font.
|
|
|
|
static int Win32_UseLargeFont(void)
|
|
{
|
|
HDC hdc = GetDC(NULL);
|
|
int dpix;
|
|
|
|
if (!hdc)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
dpix = GetDeviceCaps(hdc, LOGPIXELSX);
|
|
ReleaseDC(NULL, hdc);
|
|
|
|
// 144 is the DPI when using "150%" scaling. If the user has this set
|
|
// then consider this an appropriate threshold for using the large font.
|
|
|
|
return dpix >= 144;
|
|
}
|
|
|
|
#endif
|
|
|
|
static txt_font_t *FontForName(char *name)
|
|
{
|
|
if (!strcmp(name, "small"))
|
|
{
|
|
return &small_font;
|
|
}
|
|
else if (!strcmp(name, "normal"))
|
|
{
|
|
return &main_font;
|
|
}
|
|
else if (!strcmp(name, "large"))
|
|
{
|
|
return &large_font;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Select the font to use, based on screen resolution
|
|
//
|
|
// If the highest screen resolution available is less than
|
|
// 640x480, use the small font.
|
|
//
|
|
|
|
static void ChooseFont(void)
|
|
{
|
|
const SDL_VideoInfo *info;
|
|
char *env;
|
|
|
|
// Allow normal selection to be overridden from an environment variable:
|
|
|
|
env = getenv("TEXTSCREEN_FONT");
|
|
|
|
if (env != NULL)
|
|
{
|
|
font = FontForName(env);
|
|
|
|
if (font != NULL)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get desktop resolution:
|
|
|
|
info = SDL_GetVideoInfo();
|
|
|
|
// If in doubt and we can't get a list, always prefer to
|
|
// fall back to the normal font:
|
|
|
|
if (info == NULL)
|
|
{
|
|
font = &main_font;
|
|
return;
|
|
}
|
|
|
|
// On tiny low-res screens (eg. palmtops) use the small font.
|
|
// If the screen resolution is at least 1920x1080, this is
|
|
// a modern high-resolution display, and we can use the
|
|
// large font.
|
|
|
|
if (info->current_w < 640 || info->current_h < 480)
|
|
{
|
|
font = &small_font;
|
|
}
|
|
#ifdef _WIN32
|
|
// On Windows we can use the system DPI settings to make a
|
|
// more educated guess about whether to use the large font.
|
|
|
|
else if (Win32_UseLargeFont())
|
|
{
|
|
font = &large_font;
|
|
}
|
|
#endif
|
|
// TODO: Detect high DPI on Linux by inquiring about Gtk+ scale
|
|
// settings. This looks like it should just be a case of shelling
|
|
// out to invoke the 'gsettings' command, eg.
|
|
// gsettings get org.gnome.desktop.interface text-scaling-factor
|
|
// and using large_font if the result is >= 2.
|
|
else
|
|
{
|
|
font = &main_font;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize text mode screen
|
|
//
|
|
// Returns 1 if successful, 0 if an error occurred
|
|
//
|
|
|
|
int TXT_Init(void)
|
|
{
|
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ChooseFont();
|
|
|
|
// Always create the screen at the native screen depth (bpp=0);
|
|
// some systems nowadays don't seem to support true 8-bit palettized
|
|
// screen modes very well and we end up with screwed up colors.
|
|
screen = SDL_SetVideoMode(TXT_SCREEN_W * font->w,
|
|
TXT_SCREEN_H * font->h, 0, 0);
|
|
|
|
if (screen == NULL)
|
|
return 0;
|
|
|
|
// Instead, we draw everything into an intermediate 8-bit surface
|
|
// the same dimensions as the screen. SDL then takes care of all the
|
|
// 8->32 bit (or whatever depth) color conversions for us.
|
|
screenbuffer = SDL_CreateRGBSurface(0, TXT_SCREEN_W * font->w,
|
|
TXT_SCREEN_H * font->h,
|
|
8, 0, 0, 0, 0);
|
|
SDL_SetColors(screenbuffer, ega_colors, 0, 16);
|
|
SDL_EnableUNICODE(1);
|
|
|
|
screendata = malloc(TXT_SCREEN_W * TXT_SCREEN_H * 2);
|
|
memset(screendata, 0, TXT_SCREEN_W * TXT_SCREEN_H * 2);
|
|
|
|
// Ignore all mouse motion events
|
|
|
|
// SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
|
|
|
|
// Repeat key presses so we can hold down arrows to scroll down the
|
|
// menu, for example. This is what setup.exe does.
|
|
|
|
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void TXT_Shutdown(void)
|
|
{
|
|
free(screendata);
|
|
screendata = NULL;
|
|
SDL_FreeSurface(screenbuffer);
|
|
screenbuffer = NULL;
|
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
}
|
|
|
|
unsigned char *TXT_GetScreenData(void)
|
|
{
|
|
return screendata;
|
|
}
|
|
|
|
static inline void UpdateCharacter(int x, int y)
|
|
{
|
|
unsigned char character;
|
|
unsigned char *p;
|
|
unsigned char *s, *s1;
|
|
unsigned int bit, bytes;
|
|
int bg, fg;
|
|
unsigned int x1, y1;
|
|
|
|
p = &screendata[(y * TXT_SCREEN_W + x) * 2];
|
|
character = p[0];
|
|
|
|
fg = p[1] & 0xf;
|
|
bg = (p[1] >> 4) & 0xf;
|
|
|
|
if (bg & 0x8)
|
|
{
|
|
// blinking
|
|
|
|
bg &= ~0x8;
|
|
|
|
if (((SDL_GetTicks() / BLINK_PERIOD) % 2) == 0)
|
|
{
|
|
fg = bg;
|
|
}
|
|
}
|
|
|
|
// How many bytes per line?
|
|
bytes = (font->w + 7) / 8;
|
|
p = &font->data[character * font->h * bytes];
|
|
|
|
s = ((unsigned char *) screenbuffer->pixels)
|
|
+ (y * font->h * screenbuffer->pitch)
|
|
+ (x * font->w);
|
|
|
|
for (y1=0; y1<font->h; ++y1)
|
|
{
|
|
s1 = s;
|
|
bit = 0;
|
|
|
|
for (x1=0; x1<font->w; ++x1)
|
|
{
|
|
if (*p & (1 << (7-bit)))
|
|
{
|
|
*s1++ = fg;
|
|
}
|
|
else
|
|
{
|
|
*s1++ = bg;
|
|
}
|
|
|
|
++bit;
|
|
if (bit == 8)
|
|
{
|
|
++p;
|
|
bit = 0;
|
|
}
|
|
}
|
|
|
|
if (bit != 0)
|
|
{
|
|
++p;
|
|
}
|
|
|
|
s += screenbuffer->pitch;
|
|
}
|
|
}
|
|
|
|
static int LimitToRange(int val, int min, int max)
|
|
{
|
|
if (val < min)
|
|
{
|
|
return min;
|
|
}
|
|
else if (val > max)
|
|
{
|
|
return max;
|
|
}
|
|
else
|
|
{
|
|
return val;
|
|
}
|
|
}
|
|
|
|
void TXT_UpdateScreenArea(int x, int y, int w, int h)
|
|
{
|
|
SDL_Rect rect;
|
|
int x1, y1;
|
|
int x_end;
|
|
int y_end;
|
|
|
|
x_end = LimitToRange(x + w, 0, TXT_SCREEN_W);
|
|
y_end = LimitToRange(y + h, 0, TXT_SCREEN_H);
|
|
x = LimitToRange(x, 0, TXT_SCREEN_W);
|
|
y = LimitToRange(y, 0, TXT_SCREEN_H);
|
|
|
|
for (y1=y; y1<y_end; ++y1)
|
|
{
|
|
for (x1=x; x1<x_end; ++x1)
|
|
{
|
|
UpdateCharacter(x1, y1);
|
|
}
|
|
}
|
|
|
|
rect.x = x * font->w;
|
|
rect.y = y * font->h;
|
|
rect.w = (x_end - x) * font->w;
|
|
rect.h = (y_end - y) * font->h;
|
|
|
|
SDL_BlitSurface(screenbuffer, &rect, screen, &rect);
|
|
SDL_UpdateRects(screen, 1, &rect);
|
|
}
|
|
|
|
void TXT_UpdateScreen(void)
|
|
{
|
|
TXT_UpdateScreenArea(0, 0, TXT_SCREEN_W, TXT_SCREEN_H);
|
|
}
|
|
|
|
void TXT_GetMousePosition(int *x, int *y)
|
|
{
|
|
SDL_GetMouseState(x, y);
|
|
|
|
*x /= font->w;
|
|
*y /= font->h;
|
|
}
|
|
|
|
//
|
|
// Translates the SDL key
|
|
//
|
|
|
|
static int TranslateKey(SDL_keysym *sym)
|
|
{
|
|
switch(sym->sym)
|
|
{
|
|
case SDLK_LEFT: return KEY_LEFTARROW;
|
|
case SDLK_RIGHT: return KEY_RIGHTARROW;
|
|
case SDLK_DOWN: return KEY_DOWNARROW;
|
|
case SDLK_UP: return KEY_UPARROW;
|
|
case SDLK_ESCAPE: return KEY_ESCAPE;
|
|
case SDLK_RETURN: return KEY_ENTER;
|
|
case SDLK_TAB: return KEY_TAB;
|
|
case SDLK_F1: return KEY_F1;
|
|
case SDLK_F2: return KEY_F2;
|
|
case SDLK_F3: return KEY_F3;
|
|
case SDLK_F4: return KEY_F4;
|
|
case SDLK_F5: return KEY_F5;
|
|
case SDLK_F6: return KEY_F6;
|
|
case SDLK_F7: return KEY_F7;
|
|
case SDLK_F8: return KEY_F8;
|
|
case SDLK_F9: return KEY_F9;
|
|
case SDLK_F10: return KEY_F10;
|
|
case SDLK_F11: return KEY_F11;
|
|
case SDLK_F12: return KEY_F12;
|
|
case SDLK_PRINT: return KEY_PRTSCR;
|
|
|
|
case SDLK_BACKSPACE: return KEY_BACKSPACE;
|
|
case SDLK_DELETE: return KEY_DEL;
|
|
|
|
case SDLK_PAUSE: return KEY_PAUSE;
|
|
|
|
case SDLK_LSHIFT:
|
|
case SDLK_RSHIFT:
|
|
return KEY_RSHIFT;
|
|
|
|
case SDLK_LCTRL:
|
|
case SDLK_RCTRL:
|
|
return KEY_RCTRL;
|
|
|
|
case SDLK_LALT:
|
|
case SDLK_RALT:
|
|
case SDLK_LMETA:
|
|
case SDLK_RMETA:
|
|
return KEY_RALT;
|
|
|
|
case SDLK_CAPSLOCK: return KEY_CAPSLOCK;
|
|
case SDLK_SCROLLOCK: return KEY_SCRLCK;
|
|
|
|
case SDLK_HOME: return KEY_HOME;
|
|
case SDLK_INSERT: return KEY_INS;
|
|
case SDLK_END: return KEY_END;
|
|
case SDLK_PAGEUP: return KEY_PGUP;
|
|
case SDLK_PAGEDOWN: return KEY_PGDN;
|
|
|
|
#ifdef SDL_HAVE_APP_KEYS
|
|
case SDLK_APP1: return KEY_F1;
|
|
case SDLK_APP2: return KEY_F2;
|
|
case SDLK_APP3: return KEY_F3;
|
|
case SDLK_APP4: return KEY_F4;
|
|
case SDLK_APP5: return KEY_F5;
|
|
case SDLK_APP6: return KEY_F6;
|
|
#endif
|
|
|
|
default: break;
|
|
}
|
|
|
|
// Returned value is different, depending on whether key mapping is
|
|
// enabled. Key mapping is preferable most of the time, for typing
|
|
// in text, etc. However, when we want to read raw keyboard codes
|
|
// for the setup keyboard configuration dialog, we want the raw
|
|
// key code.
|
|
|
|
if (key_mapping)
|
|
{
|
|
// Unicode characters beyond the ASCII range need to be
|
|
// mapped up into textscreen's Unicode range.
|
|
|
|
if (sym->unicode < 128)
|
|
{
|
|
return sym->unicode;
|
|
}
|
|
else
|
|
{
|
|
return sym->unicode - 128 + TXT_UNICODE_BASE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Keypad mapping is only done when we want a raw value:
|
|
// most of the time, the keypad should behave as it normally
|
|
// does.
|
|
|
|
switch (sym->sym)
|
|
{
|
|
case SDLK_KP0: return KEYP_0;
|
|
case SDLK_KP1: return KEYP_1;
|
|
case SDLK_KP2: return KEYP_2;
|
|
case SDLK_KP3: return KEYP_3;
|
|
case SDLK_KP4: return KEYP_4;
|
|
case SDLK_KP5: return KEYP_5;
|
|
case SDLK_KP6: return KEYP_6;
|
|
case SDLK_KP7: return KEYP_7;
|
|
case SDLK_KP8: return KEYP_8;
|
|
case SDLK_KP9: return KEYP_9;
|
|
|
|
case SDLK_KP_PERIOD: return KEYP_PERIOD;
|
|
case SDLK_KP_MULTIPLY: return KEYP_MULTIPLY;
|
|
case SDLK_KP_PLUS: return KEYP_PLUS;
|
|
case SDLK_KP_MINUS: return KEYP_MINUS;
|
|
case SDLK_KP_DIVIDE: return KEYP_DIVIDE;
|
|
case SDLK_KP_EQUALS: return KEYP_EQUALS;
|
|
case SDLK_KP_ENTER: return KEYP_ENTER;
|
|
|
|
default:
|
|
return tolower(sym->sym);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert an SDL button index to textscreen button index.
|
|
//
|
|
// Note special cases because 2 == mid in SDL, 3 == mid in textscreen/setup
|
|
|
|
static int SDLButtonToTXTButton(int button)
|
|
{
|
|
switch (button)
|
|
{
|
|
case SDL_BUTTON_LEFT:
|
|
return TXT_MOUSE_LEFT;
|
|
case SDL_BUTTON_RIGHT:
|
|
return TXT_MOUSE_RIGHT;
|
|
case SDL_BUTTON_MIDDLE:
|
|
return TXT_MOUSE_MIDDLE;
|
|
default:
|
|
return TXT_MOUSE_BASE + button - 1;
|
|
}
|
|
}
|
|
|
|
static int MouseHasMoved(void)
|
|
{
|
|
static int last_x = 0, last_y = 0;
|
|
int x, y;
|
|
|
|
TXT_GetMousePosition(&x, &y);
|
|
|
|
if (x != last_x || y != last_y)
|
|
{
|
|
last_x = x; last_y = y;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Examine a key press/release and update the modifier key state
|
|
// if necessary.
|
|
|
|
static void UpdateModifierState(SDL_keysym *sym, int pressed)
|
|
{
|
|
txt_modifier_t mod;
|
|
|
|
switch (sym->sym)
|
|
{
|
|
case SDLK_LSHIFT:
|
|
case SDLK_RSHIFT:
|
|
mod = TXT_MOD_SHIFT;
|
|
break;
|
|
|
|
case SDLK_LCTRL:
|
|
case SDLK_RCTRL:
|
|
mod = TXT_MOD_CTRL;
|
|
break;
|
|
|
|
case SDLK_LALT:
|
|
case SDLK_RALT:
|
|
case SDLK_LMETA:
|
|
case SDLK_RMETA:
|
|
mod = TXT_MOD_ALT;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (pressed)
|
|
{
|
|
++modifier_state[mod];
|
|
}
|
|
else
|
|
{
|
|
--modifier_state[mod];
|
|
}
|
|
}
|
|
|
|
signed int TXT_GetChar(void)
|
|
{
|
|
SDL_Event ev;
|
|
|
|
while (SDL_PollEvent(&ev))
|
|
{
|
|
// If there is an event callback, allow it to intercept this
|
|
// event.
|
|
|
|
if (event_callback != NULL)
|
|
{
|
|
if (event_callback(&ev, event_callback_data))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Process the event.
|
|
|
|
switch (ev.type)
|
|
{
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
if (ev.button.button < TXT_MAX_MOUSE_BUTTONS)
|
|
{
|
|
return SDLButtonToTXTButton(ev.button.button);
|
|
}
|
|
break;
|
|
|
|
case SDL_KEYDOWN:
|
|
UpdateModifierState(&ev.key.keysym, 1);
|
|
|
|
return TranslateKey(&ev.key.keysym);
|
|
|
|
case SDL_KEYUP:
|
|
UpdateModifierState(&ev.key.keysym, 0);
|
|
break;
|
|
|
|
case SDL_QUIT:
|
|
// Quit = escape
|
|
return 27;
|
|
|
|
case SDL_MOUSEMOTION:
|
|
if (MouseHasMoved())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int TXT_GetModifierState(txt_modifier_t mod)
|
|
{
|
|
if (mod < TXT_NUM_MODIFIERS)
|
|
{
|
|
return modifier_state[mod] > 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *SpecialKeyName(int key)
|
|
{
|
|
switch (key)
|
|
{
|
|
case ' ': return "SPACE";
|
|
case KEY_RIGHTARROW: return "RIGHT";
|
|
case KEY_LEFTARROW: return "LEFT";
|
|
case KEY_UPARROW: return "UP";
|
|
case KEY_DOWNARROW: return "DOWN";
|
|
case KEY_ESCAPE: return "ESC";
|
|
case KEY_ENTER: return "ENTER";
|
|
case KEY_TAB: return "TAB";
|
|
case KEY_F1: return "F1";
|
|
case KEY_F2: return "F2";
|
|
case KEY_F3: return "F3";
|
|
case KEY_F4: return "F4";
|
|
case KEY_F5: return "F5";
|
|
case KEY_F6: return "F6";
|
|
case KEY_F7: return "F7";
|
|
case KEY_F8: return "F8";
|
|
case KEY_F9: return "F9";
|
|
case KEY_F10: return "F10";
|
|
case KEY_F11: return "F11";
|
|
case KEY_F12: return "F12";
|
|
case KEY_BACKSPACE: return "BKSP";
|
|
case KEY_PAUSE: return "PAUSE";
|
|
case KEY_EQUALS: return "EQUALS";
|
|
case KEY_MINUS: return "MINUS";
|
|
case KEY_RSHIFT: return "SHIFT";
|
|
case KEY_RCTRL: return "CTRL";
|
|
case KEY_RALT: return "ALT";
|
|
case KEY_CAPSLOCK: return "CAPS";
|
|
case KEY_SCRLCK: return "SCRLCK";
|
|
case KEY_HOME: return "HOME";
|
|
case KEY_END: return "END";
|
|
case KEY_PGUP: return "PGUP";
|
|
case KEY_PGDN: return "PGDN";
|
|
case KEY_INS: return "INS";
|
|
case KEY_DEL: return "DEL";
|
|
case KEY_PRTSCR: return "PRTSC";
|
|
/*
|
|
case KEYP_0: return "PAD0";
|
|
case KEYP_1: return "PAD1";
|
|
case KEYP_2: return "PAD2";
|
|
case KEYP_3: return "PAD3";
|
|
case KEYP_4: return "PAD4";
|
|
case KEYP_5: return "PAD5";
|
|
case KEYP_6: return "PAD6";
|
|
case KEYP_7: return "PAD7";
|
|
case KEYP_8: return "PAD8";
|
|
case KEYP_9: return "PAD9";
|
|
case KEYP_UPARROW: return "PAD_U";
|
|
case KEYP_DOWNARROW: return "PAD_D";
|
|
case KEYP_LEFTARROW: return "PAD_L";
|
|
case KEYP_RIGHTARROW: return "PAD_R";
|
|
case KEYP_MULTIPLY: return "PAD*";
|
|
case KEYP_PLUS: return "PAD+";
|
|
case KEYP_MINUS: return "PAD-";
|
|
case KEYP_DIVIDE: return "PAD/";
|
|
*/
|
|
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
void TXT_GetKeyDescription(int key, char *buf, size_t buf_len)
|
|
{
|
|
const char *keyname;
|
|
|
|
keyname = SpecialKeyName(key);
|
|
|
|
if (keyname != NULL)
|
|
{
|
|
TXT_StringCopy(buf, keyname, buf_len);
|
|
}
|
|
else if (isprint(key))
|
|
{
|
|
TXT_snprintf(buf, buf_len, "%c", toupper(key));
|
|
}
|
|
else
|
|
{
|
|
TXT_snprintf(buf, buf_len, "??%i", key);
|
|
}
|
|
}
|
|
|
|
// Searches the desktop screen buffer to determine whether there are any
|
|
// blinking characters.
|
|
|
|
int TXT_ScreenHasBlinkingChars(void)
|
|
{
|
|
int x, y;
|
|
unsigned char *p;
|
|
|
|
// Check all characters in screen buffer
|
|
|
|
for (y=0; y<TXT_SCREEN_H; ++y)
|
|
{
|
|
for (x=0; x<TXT_SCREEN_W; ++x)
|
|
{
|
|
p = &screendata[(y * TXT_SCREEN_W + x) * 2];
|
|
|
|
if (p[1] & 0x80)
|
|
{
|
|
// This character is blinking
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// None found
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Sleeps until an event is received, the screen needs to be redrawn,
|
|
// or until timeout expires (if timeout != 0)
|
|
|
|
void TXT_Sleep(int timeout)
|
|
{
|
|
unsigned int start_time;
|
|
|
|
if (TXT_ScreenHasBlinkingChars())
|
|
{
|
|
int time_to_next_blink;
|
|
|
|
time_to_next_blink = BLINK_PERIOD - (SDL_GetTicks() % BLINK_PERIOD);
|
|
|
|
// There are blinking characters on the screen, so we
|
|
// must time out after a while
|
|
|
|
if (timeout == 0 || timeout > time_to_next_blink)
|
|
{
|
|
// Add one so it is always positive
|
|
|
|
timeout = time_to_next_blink + 1;
|
|
}
|
|
}
|
|
|
|
if (timeout == 0)
|
|
{
|
|
// We can just wait forever until an event occurs
|
|
|
|
SDL_WaitEvent(NULL);
|
|
}
|
|
else
|
|
{
|
|
// Sit in a busy loop until the timeout expires or we have to
|
|
// redraw the blinking screen
|
|
|
|
start_time = SDL_GetTicks();
|
|
|
|
while (SDL_GetTicks() < start_time + timeout)
|
|
{
|
|
if (SDL_PollEvent(NULL) != 0)
|
|
{
|
|
// Received an event, so stop waiting
|
|
|
|
break;
|
|
}
|
|
|
|
// Don't hog the CPU
|
|
|
|
SDL_Delay(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TXT_EnableKeyMapping(int enable)
|
|
{
|
|
key_mapping = enable;
|
|
}
|
|
|
|
void TXT_SetWindowTitle(char *title)
|
|
{
|
|
SDL_WM_SetCaption(title, NULL);
|
|
}
|
|
|
|
void TXT_SDL_SetEventCallback(TxtSDLEventCallbackFunc callback, void *user_data)
|
|
{
|
|
event_callback = callback;
|
|
event_callback_data = user_data;
|
|
}
|
|
|
|
// Safe string functions.
|
|
|
|
void TXT_StringCopy(char *dest, const char *src, size_t dest_len)
|
|
{
|
|
if (dest_len < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dest[dest_len - 1] = '\0';
|
|
strncpy(dest, src, dest_len - 1);
|
|
}
|
|
|
|
void TXT_StringConcat(char *dest, const char *src, size_t dest_len)
|
|
{
|
|
size_t offset;
|
|
|
|
offset = strlen(dest);
|
|
if (offset > dest_len)
|
|
{
|
|
offset = dest_len;
|
|
}
|
|
|
|
TXT_StringCopy(dest + offset, src, dest_len - offset);
|
|
}
|
|
|
|
// On Windows, vsnprintf() is _vsnprintf().
|
|
#ifdef _WIN32
|
|
#if _MSC_VER < 1400 /* not needed for Visual Studio 2008 */
|
|
#define vsnprintf _vsnprintf
|
|
#endif
|
|
#endif
|
|
|
|
// Safe, portable vsnprintf().
|
|
int TXT_vsnprintf(char *buf, size_t buf_len, const char *s, va_list args)
|
|
{
|
|
int result;
|
|
|
|
if (buf_len < 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Windows (and other OSes?) has a vsnprintf() that doesn't always
|
|
// append a trailing \0. So we must do it, and write into a buffer
|
|
// that is one byte shorter; otherwise this function is unsafe.
|
|
result = vsnprintf(buf, buf_len, s, args);
|
|
|
|
// If truncated, change the final char in the buffer to a \0.
|
|
// A negative result indicates a truncated buffer on Windows.
|
|
if (result < 0 || result >= buf_len)
|
|
{
|
|
buf[buf_len - 1] = '\0';
|
|
result = buf_len - 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Safe, portable snprintf().
|
|
int TXT_snprintf(char *buf, size_t buf_len, const char *s, ...)
|
|
{
|
|
va_list args;
|
|
int result;
|
|
va_start(args, s);
|
|
result = TXT_vsnprintf(buf, buf_len, s, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|