From 6ac705928d3ae0a4087ce79c540a2581351e3195 Mon Sep 17 00:00:00 2001 From: meag Date: Fri, 9 Oct 2015 17:14:26 +0100 Subject: [PATCH] Initial changes to support non-american keyboard layouts & cyrillic characters. Uses SDL2 TextInput event, unfortunately the unicode character is sent after the keydown event, so we need to know if the keydown event is going to produce a character or not... currently the system lets the keydown event produce that character, but then backspaces and replaces it with the unicode version, which seems ugly. The console still translates and stores in wchar at the moment, so true utf8 printing isn't possible, we only support up to 3-byte utf8 characters. Can now also choose the encoding method on outgoing messages: cl_textencoding Controls method to encode outgoing extended characters (> 255) 0 - koi8 encoding (wrapped with =`k8:`=) 1 - utf8 encoding (wrapped with =`utf8:`=) 2 - FTE encoding (convert to ^Uxxxx where xxxx is hex) 0 can encode limited set of characters, but compatible with 2.2 clients 1 encodes all characters correctly for all but only other 3.0 clients 2 compatible with FTE & 3.0 Regardless of this setting, the client will continue to decode svc_print messages using all of the above incoming methods. Previously only -cyr suffix was supported for extended character sets. Now all are supported, from -001 to -256. If -004 isn't present it will fall back to loading -cyr. This is a lot of textures to load and we might need to look at methods to reduce the number. To support compatibility with existing configuration files (and passing config files between machines with different keyboard layouts), the bind and unbind commands look up the key based on the keyboard layout only when con_bindphysical is set to 0. All configuration files will start with con_bindphysical set to 1, and it is reset back to its previous value once the configuration file has completed. If configuration files want to bind to a key based on layout (a demo-watching configuration where the user should press R to toggle radar for example) then the configuration file should set con_bindphysical to 0 before binding any keys. Hopefully this is a solution to non-american keyboards that is natural to use in the console and is backwards-compatible with existing configs. --- cmd.c | 17 ++- config_manager.c | 5 +- console.c | 9 +- draw.h | 2 +- gl_draw.c | 17 ++- keys.c | 59 ++++++++-- keys.h | 2 + menu.c | 5 +- teamplay.c | 2 +- textencoding.c | 275 +++++++++++++++++++++++++++++++++++++++++------ vid_sdl2.c | 61 +++++++++-- 11 files changed, 392 insertions(+), 62 deletions(-) diff --git a/cmd.c b/cmd.c index b7649c12e..4708b4443 100644 --- a/cmd.c +++ b/cmd.c @@ -445,6 +445,7 @@ void Cmd_StuffCmds_f (void) void Cmd_Exec_f (void) { char *f, name[MAX_OSPATH]; + char reset_bindphysical[128]; int mark; if (Cmd_Argc () != 2) { @@ -471,14 +472,20 @@ void Cmd_Exec_f (void) Com_Printf("execing %s/%s\n", FS_Locate_GetPath(name), name); } + // All config files default to con_bindphysical 1, and would have to over-ride if they + // want different behaviour. + sprintf(reset_bindphysical, "\ncon_bindphysical %d\n", con_bindphysical.integer); if (cbuf_current == &cbuf_svc) { + Cbuf_AddTextEx (&cbuf_main, "con_bindphysical 1\n"); Cbuf_AddTextEx (&cbuf_main, f); - Cbuf_AddTextEx (&cbuf_main, "\n"); - } else - { - Cbuf_InsertText ("\n"); - Cbuf_InsertText (f); + Cbuf_AddTextEx (&cbuf_main, reset_bindphysical); + } + else { + Cbuf_InsertTextEx (&cbuf_main, reset_bindphysical); + Cbuf_InsertTextEx (&cbuf_main, f); + Cbuf_InsertTextEx (&cbuf_main, "con_bindphysical 1\n"); } + Hunk_FreeToLowMark (mark); } diff --git a/config_manager.c b/config_manager.c index 310611f8b..be837e709 100644 --- a/config_manager.c +++ b/config_manager.c @@ -104,8 +104,9 @@ void DumpBindings (FILE *f) static qbool Config_Unsaved_Cvar(const char *name) { return (!strcmp(name, "cl_delay_packet")) - || (!strcmp(name, "cl_proxyaddr")) - || (!strcmp(name, "hud_planmode")); + || (!strcmp(name, "cl_proxyaddr")) + || (!strcmp(name, "hud_planmode")) + || (!strcmp(name, "con_bindphysical")); } #define CONFIG_MAX_COL 60 diff --git a/console.c b/console.c index 85967a238..bab8d535e 100644 --- a/console.c +++ b/console.c @@ -73,8 +73,9 @@ cvar_t con_sound_mm2_volume = {"s_mm2_volume", "1"}; cvar_t con_sound_spec_volume = {"s_spec_volume", "1"}; cvar_t con_sound_other_volume = {"s_otherchat_volume", "1"}; -cvar_t con_timestamps = {"con_timestamps", "0"}; -cvar_t con_shift = {"con_shift", "-10"}; +cvar_t con_timestamps = {"con_timestamps", "0"}; +cvar_t con_shift = {"con_shift", "-10"}; +cvar_t cl_textEncoding = {"cl_textencoding", "0"}; #define NUM_CON_TIMES 16 float con_times[NUM_CON_TIMES]; // cls.realtime time the line was generated @@ -472,6 +473,8 @@ void Con_Init (void) { Cvar_ResetCurrentGroup(); + Cvar_Register(&cl_textEncoding); + Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f); Cmd_AddCommand ("messagemode", Con_MessageMode_f); Cmd_AddCommand ("messagemode2", Con_MessageMode2_f); @@ -648,7 +651,7 @@ void Con_PrintW (wchar *txt) { Con_Linefeed (); y = con.current % con_totallines; idx = y * con_linewidth + con.x; - con.text[idx] = c | mask | con_ormask; + con.text[idx] = c | (c <= 0x7F ? mask | con_ormask : 0); // only apply mask if in 'standard' charset memset(&con.clr[idx], 0, sizeof(clrinfo_t)); // zeroing whole struct con.clr[idx].c = color; con.clr[idx].i = idx; // no, that not stupid :P diff --git a/draw.h b/draw.h index 0a56bf3fb..98c4f8a36 100644 --- a/draw.h +++ b/draw.h @@ -24,7 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifndef __DRAW_H__ #define __DRAW_H__ -#define MAX_CHARSETS 16 +#define MAX_CHARSETS 256 extern int char_textures[MAX_CHARSETS]; typedef struct diff --git a/gl_draw.c b/gl_draw.c index 6cf4c6187..72feb0722 100644 --- a/gl_draw.c +++ b/gl_draw.c @@ -689,6 +689,7 @@ static int Draw_LoadCharset(const char *name) { int flags = TEX_ALPHA | TEX_NOCOMPRESS | TEX_NOSCALE | TEX_NO_TEXTUREMODE; int texnum; + int i = 0; qbool loaded = false; // @@ -716,9 +717,21 @@ static int Draw_LoadCharset(const char *name) return 1; } - // Load cyrillic charset if available - Load_Locale_Charset(name, "cyr", 1, 0x0400, flags); + // Load alternate charsets if available + for (i = 1; i < MAX_CHARSETS; ++i) + { + char charsetName[10]; + int textureNumber; + + snprintf(charsetName, sizeof(charsetName), "%03d", i); + + textureNumber = Load_Locale_Charset(name, charsetName, i, i << 8, flags); + // 2.2 only supported -cyr suffix + if (i == 4 && !textureNumber) + Load_Locale_Charset(name, "cyr", 4, 0x0400, flags); + } + // apply gl_smoothfont Apply_OnChange_gl_smoothfont(gl_smoothfont.integer); diff --git a/keys.c b/keys.c index 4a42ca1e0..a2e151503 100644 --- a/keys.c +++ b/keys.c @@ -54,6 +54,7 @@ cvar_t con_tilde_mode = {"con_tilde_mode", "0"}; cvar_t con_completion_format = {"con_completion_format", "2"}; cvar_t con_hide_chat_input = {"con_hide_chat_input", "1"}; cvar_t con_completion_changed_mark = {"con_completion_changed_mark", "1"}; +cvar_t con_bindphysical = {"con_bindphysical", "0" }; char* escape_regex(char* string); void OnChange_con_prompt_charcode(cvar_t *var, char *string, qbool *cancel); @@ -102,6 +103,10 @@ int key_repeats[UNKNOWN + 256]; // if > 1, it is autorepeating qbool keydown[UNKNOWN + 256]; qbool keyactive[UNKNOWN + 256]; +// SDL2 sends a KEYDOWN event and then an optional TEXTINPUT event for the actual character generated +// This stores the last quake key (K_*) from the KEYDOWN, or 0 if any subsequent TEXTINPUT event should be ignored. +static int lastKeyDown = 0; + typedef struct { char *name; @@ -821,6 +826,14 @@ void Key_ClearTyping (void) key_linepos = 1; } +void Key_Console_Backspace(void) +{ + if (key_linepos > 1) + { + qwcscpy(key_lines[edit_line] + key_linepos - 1, key_lines[edit_line] + key_linepos); + key_linepos--; + } +} // // Interactive line editing and console scrollback. @@ -988,11 +1001,7 @@ void Key_Console (int key, int unichar) } else { - if(key_linepos > 1) - { - qwcscpy(key_lines[edit_line] + key_linepos - 1, key_lines[edit_line] + key_linepos); - key_linepos--; - } + Key_Console_Backspace(); } // disable red chars mode if the last character was deleted @@ -1508,6 +1517,9 @@ void Key_Message (int key, wchar unichar) { //Returns a key number to be used to index keybindings[] by looking at the given string. //Single ascii characters return themselves, while the K_* names are matched up. #define UNKNOWN_S "UNKNOWN" + +int Key_CharacterToQuakeCode(char ch); + int Key_StringToKeynum (const char *str) { keyname_t *kn; @@ -1515,12 +1527,20 @@ int Key_StringToKeynum (const char *str) if (!str || !str[0]) return -1; + if (!str[1] && !con_bindphysical.value) + { + int value = Key_CharacterToQuakeCode(str[0]); + + if (value != 0) + return value; + } + if (!str[1]) return (int)(unsigned char)str[0]; for (kn = keynames; kn->name; kn++) { if (!strcasecmp (str,kn->name)) - return kn->keynum; + return kn->keynum; } return -1; @@ -1858,6 +1878,7 @@ void Key_Init (void) { Cvar_Register(&con_completion_padding); Cvar_Register(&con_completion_color_title); Cvar_Register(&con_completion_changed_mark); + Cvar_Register(&con_bindphysical); Cvar_ResetCurrentGroup(); } @@ -2175,6 +2196,9 @@ void Key_EventEx (int key, wchar unichar, qbool down) void Key_Event (int key, qbool down) { + qbool processTextInput = (key_dest == key_console || key_dest == key_message); + qbool consoleToggle = (key == '`' || key == '~') && !con_tilde_mode.integer; + assert (key >= 0 && key <= 255); if (key >= K_MOUSE1 && key <= K_MOUSE8) @@ -2197,8 +2221,31 @@ void Key_Event (int key, qbool down) unichar = keydown[K_SHIFT] ? keyshift[key] : key; if (unichar < 32 || unichar > 127) unichar = 0; + Key_EventEx (key, unichar, down); } + + // Store this as we may need it for subsequent SDL_TextInput event + lastKeyDown = 0; + if (down && processTextInput && !consoleToggle) + lastKeyDown = key; +} + +void Key_Event_TextInput(wchar unichar) +{ + if (!lastKeyDown) + return; + + if (key_dest == key_message) + { + Key_Message(K_BACKSPACE, K_BACKSPACE); + Key_Message(lastKeyDown, unichar); + } + else if (key_dest == key_console) + { + Key_Console_Backspace(); + Key_Console(lastKeyDown, unichar); + } } void Key_ClearStates (void) diff --git a/keys.h b/keys.h index d1bc4640b..3e15e9274 100644 --- a/keys.h +++ b/keys.h @@ -227,6 +227,8 @@ extern int key_linepos; extern int edit_line; extern qbool con_redchars; +extern cvar_t con_bindphysical; + #define CONSOLE_LINE_EMPTY() (!key_lines[edit_line][1]) // } diff --git a/menu.c b/menu.c index fd2e03c02..565e4b8d9 100644 --- a/menu.c +++ b/menu.c @@ -488,7 +488,10 @@ void M_Main_Key (int key) { switch (key) { case K_ESCAPE: case K_MOUSE2: - key_dest = key_game; + if (cls.state < ca_active) + key_dest = key_console; + else + key_dest = key_game; m_state = m_none; break; diff --git a/teamplay.c b/teamplay.c index b91c0bcf7..8a36cfe3e 100644 --- a/teamplay.c +++ b/teamplay.c @@ -1165,7 +1165,7 @@ wchar *TP_ParseWhiteText (const wchar *s, qbool team, int offset) continue; } } - if (*p != 10 && *p != 13 && !(p==s && (*p==1 || *p==2))) + if (*p != 10 && *p != 13 && !(p==s && (*p==1 || *p==2)) && *p <= 0x7F) *out++ = *p | 128; // convert to red else *out++ = *p; diff --git a/textencoding.c b/textencoding.c index 9d00cb87c..368012c09 100644 --- a/textencoding.c +++ b/textencoding.c @@ -18,36 +18,118 @@ along with this program. If not, see . //#include "q_shared.h" #include "common.h" // Com_DPrintf #include "textencoding.h" +#include "utils.h" +#define ENCODED_BUFFER_SIZE 1024 + +// Each encoding function should return the number of bytes written to the output string +// maxCharacters gives the characters before end of string, if function can't generate in given space, output ? instead +typedef int(*EncodingFunction)(char* output, wchar input, int maxCharacters); + +static int koiEncoder(char* output, wchar input, int maxCharacters); +static int utf8Encoder(char* output, wchar input, int maxCharacters); +static int fteEncoder(char* output, wchar input, int maxCharacters); + +static const char* encodingStrings[] = { "=`k8:", "=`utf8:", "" }; +static const char* encodingSuffixes[] = { "`=", "`=", "" }; +static const EncodingFunction encodingFunctions[] = { koiEncoder, utf8Encoder, fteEncoder }; + +extern cvar_t cl_textEncoding; extern qbool R_CharAvailable (wchar c); /* KOI8-R encodes Russian capital hard sign as 0xFF, but we can't use it because it breaks older clients (qwcl). We use 0xaf ('/'+ 0x80) instead. */ static char *wc2koi_table = -"?3??4?67??" "??" "??" ">?" -"abwgdevzijklmnop" -"rstufhc~{}/yx|`q" -"ABWGDEVZIJKLMNOP" -"RSTUFHC^[]_YX\\@Q" -"?#??$?&'??" "??" "??.?"; - -static char wc2koi (wchar wc) { - if (wc <= 127) - return (char)wc; - if (wc >= 0x400 && wc <= 0x45f) - return wc2koi_table[wc - 0x400] + 128; + "?3??4?67??" "??" "??" ">?" + "abwgdevzijklmnop" + "rstufhc~{}/yx|`q" + "ABWGDEVZIJKLMNOP" + "RSTUFHC^[]_YX\\@Q" + "?#??$?&'??" "??" "??.?"; + +static int koiEncoder(char* out, wchar in, int maxCharacters) { + if (in <= 255) + out[0] = in; + else if (in >= 0x400 && in <= 0x45f) + out[0] = wc2koi_table[in - 0x400] + 128; else - return '?'; - + out[0] = '?'; + return 1; +} + +static int utf8Encoder(char* out, wchar in, int maxCharacters) { + if (in <= 0x7F) { + // <= 127 is encoded as single byte, no translation + out[0] = in; + return 1; + } + else if (in <= 0x7FF && maxCharacters >= 2) { + // Two byte characters... 5 bits then 6 + out[0] = 0xC0 | ((in >> 6) & 0x1F); + out[1] = 0x80 | (in & 0x3F); + return 2; + } + else if (in <= 0xFFFF && maxCharacters >= 3) { + // Three byte characters... 4 bits then 6 then 6 + out[0] = 0xE0 | ((in >> 12) & 0x0F); + out[1] = 0x80 | ((in >> 6) & 0x3F); + out[2] = 0x80 | (in & 0x3F); + return 3; + } + else { + // Can't support four characters at the moment - values would be higher than wchar's two bytes + // If we're here for a character we could support, we don't have room for it... + out[0] = '?'; + return 1; + } +} + +// FIXME: Should we encode ^ as ^^? FTE interprets ^^ as ^, but doesn't encode that way. +static int fteEncoder(char* out, wchar in, int maxCharacters) { + if (in <= 0x7F) { + // <= 127 is encoded as single byte, no translation + out[0] = in; + return 1; + } + + // Otherwise it is ^UXXXX where XXXX is the codepage in hex - so need an extra 5 characters + if (maxCharacters < sizeof(wchar) * 2 + 1) + { + out[0] = '?'; + return 1; + } + + snprintf(out, maxCharacters + 1, "^U%04x", in); + return 6; } +static int TextEncodingMethod() +{ + return bound(0, cl_textEncoding.value, sizeof(encodingFunctions) / sizeof(encodingFunctions[0])); +} + +int TextEncodingEncode(char* out, wchar input, int maxBytes) +{ + return (*encodingFunctions[TextEncodingMethod()])(out, input, maxBytes); +} + +const char* TextEncodingPrefix() +{ + return encodingStrings[TextEncodingMethod()]; +} + +const char* TextEncodingSuffix() +{ + return encodingSuffixes[TextEncodingMethod()]; +} char *encode_say (wchar *in) { - static char buf[1024]; + static char buf[ENCODED_BUFFER_SIZE]; wchar *p; char *out; + memset(buf, 0, sizeof(buf)); for (p = in; *p; p++) if (*p > 255) goto encode; @@ -56,32 +138,32 @@ char *encode_say (wchar *in) encode: strlcpy (buf, wcs2str(in), min(p - in + 1, sizeof(buf))); in = p; - strlcat (buf, "=`k8:", sizeof(buf)); + strlcat (buf, TextEncodingPrefix(), sizeof(buf)); out = buf + strlen(buf); - while (*in && (out - buf < sizeof(buf)/sizeof(buf[0]))) + + char* lastValidCharacter = &buf[ENCODED_BUFFER_SIZE - strlen(TextEncodingSuffix()) - 1]; // Leave space for terminator + while (*in && out <= lastValidCharacter) { - if (*in <= 255) - *out++ = *in; - else { - *out++ = wc2koi(*in); - } - in++; + out += TextEncodingEncode(out, *in, lastValidCharacter - out + 1); + ++in; } - *out++ = '`'; - *out++ = '='; - *out++ = 0; + strlcat(buf, TextEncodingSuffix(), sizeof(buf)); return buf; } -/* koi2wc_table is also used in vid_glx.c */ -char koi2wc_table[64] = { -0x4e,0x30,0x31,0x46,0x34,0x35,0x44,0x33,0x45,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e, -0x3f,0x4f,0x40,0x41,0x42,0x43,0x36,0x32,0x4c,0x4b,0x37,0x48,0x4d,0x49,0x47,0x4a, -0x2e,0x10,0x11,0x26,0x14,0x15,0x24,0x13,0x25,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e, -0x1f,0x2f,0x20,0x21,0x22,0x23,0x16,0x12,0x2c,0x2b,0x17,0x28,0x2d,0x29,0x27,0x2a }; +// +// Decoding functions +// static wchar koi2wc (char c) { + static char koi2wc_table[64] = { + 0x4e,0x30,0x31,0x46,0x34,0x35,0x44,0x33,0x45,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e, + 0x3f,0x4f,0x40,0x41,0x42,0x43,0x36,0x32,0x4c,0x4b,0x37,0x48,0x4d,0x49,0x47,0x4a, + 0x2e,0x10,0x11,0x26,0x14,0x15,0x24,0x13,0x25,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e, + 0x1f,0x2f,0x20,0x21,0x22,0x23,0x16,0x12,0x2c,0x2b,0x17,0x28,0x2d,0x29,0x27,0x2a + }; + unsigned char uc = c; if (uc >= 192 /* && (unsigned char)c <= 255 */) @@ -160,6 +242,67 @@ wchar *decode_cp1251 (char *str) { return buf; }; +wchar TextEncodingDecodeUTF8(char* str, int* index) +{ + static const int masks[3] = { 0x7F, 0x1F, 0xF }; + int stringLength = strlen(str); + int extraBytes = 0; + int i = 0; + wchar result = 0; + + if (!(str[*index] & 0x80)) + { + // Standard ASCII + result = str[*index]; + } + else + { + char valueMask = 0x7F; + char lengthBits = (str[*index] << 1); + + // First character is 1 + while (lengthBits & 0x80) + { + ++extraBytes; + lengthBits = (lengthBits << 1); + + valueMask >>= 1; + } + + // Extract value from first character + result = str[*index] & valueMask; + + while (extraBytes--) + { + *index = *index + 1; + + // Extra characters must be 10xx xxxx + if (*index >= stringLength || (str[*index] & 0xC0) != 0x80) + return 0; + + result <<= 6; + result += str[*index] & 0x3F; + } + } + + return result; +} + +wchar* decode_utf8(char* str) { + wchar *buf, *out; + int length = strlen(str); + int i = 0; + + buf = out = Q_malloc((length + 1)*sizeof(wchar)); // This size would be worst case scenario + for (i = 0; i < length; ++i) + { + *out = TextEncodingDecodeUTF8(str, &i); + out++; + } + *out = 0; + return buf; +}; + typedef wchar *(*decodeFUNC) (char *); static struct { @@ -171,9 +314,72 @@ static struct { {"k8", decode_koi8q}, {"cp1251", decode_cp1251}, {"wr", decode_cp1251}, // wc = Windows Cyrillic wr = Windows Russian + {"utf8", decode_utf8}, {NULL, NULL} }; +qbool ishex(char ch) +{ + return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'); +} + +qbool wc_ishex(wchar ch) +{ + return ch < 127 && ishex(ch); +} + +int wc_hextodec(wchar ch) +{ + return ch < 127 ? HexToInt(ch) : 0; +} + +void decode_fte_string(wchar* s) +{ + while (s[0] && s[1]) + { + if (s[0] == '^' && s[1] == '^') + { + // ^^ => ^ + qwcscpy(s + 1, s + 2); + } + else if (s[0] == '^' && s[1] == 'U' && s[2] && s[3] && s[4] && s[5] && wc_ishex(s[2]) && wc_ishex(s[3]) && wc_ishex(s[4]) && wc_ishex(s[5])) + { + // ^UXXXX, XXXX is hex for wchar + s[0] = (wc_hextodec(s[2]) << 12) + (wc_hextodec(s[3]) << 8) + (wc_hextodec(s[4]) << 4) + wc_hextodec(s[5]); + + qwcscpy(s + 1, s + 6); + } + else if (s[0] == '^' && s[1] == '{') + { + // ^U{XXX...}, XXX.. is hex for wchar + wchar* num = s + 2; + wchar result = 0; + + while (*num && wc_ishex(*num)) + { + result = result * 16 + wc_hextodec(*num); + + ++num; + } + + if (*num == '}') + { + if (result) + s[0] = result; + else + s[0] = '?'; + + qwcscpy(s + 1, num + 1); + } + else + { + s[0] = '?'; + } + } + ++s; + } +} + wchar *decode_string (const char *s) { static wchar buf[2048]; // should be enough for everyone!!! @@ -226,6 +432,7 @@ wchar *decode_string (const char *s) if (!decode_table[i].name) { // unknown encoding + Con_DPrintf("Unknown encoding %s\n", encoding); p = r + 2; continue; } @@ -238,6 +445,10 @@ wchar *decode_string (const char *s) // copy remainder as is qwcslcat (buf, str2wcs(s), sizeof(buf)/sizeof(buf[0])); + + // FTE encodes strings differently. Decoded is always smaller, can execute in-place + decode_fte_string(buf); + return maybe_transliterate(buf); } diff --git a/vid_sdl2.c b/vid_sdl2.c index 9ae021c3c..970ed9e66 100644 --- a/vid_sdl2.c +++ b/vid_sdl2.c @@ -310,17 +310,54 @@ static const byte scantokey[128] = { #endif }; -static void keyb_event(SDL_KeyboardEvent *event) +byte Key_ScancodeToQuakeCode(int scancode) { - byte result; + if (scancode < 120) + return scantokey[scancode]; + if (scancode >= 224 && scancode < 224 + 8) + return scantokey[scancode - 104]; + return 0; +} + +byte Key_CharacterToQuakeCode(char ch) +{ + // Uses fact that SDLK_a == 'a'... is this okay? + + // Convert from key-code to scan-code to see what physical button they pressed + int scancode = SDL_GetScancodeFromKey(ch); + + return Key_ScancodeToQuakeCode(scancode); +} + +wchar Key_Event_TextInput(wchar unichar); + +static void keyb_textinputevent(char* text) +{ + int i = 0; + int len = 0; + wchar unichar = 0; + + // Only process text input messages here + if (key_dest != key_console && key_dest != key_message) + return; - if (event->keysym.scancode < 120) - result = scantokey[event->keysym.scancode]; - else if (event->keysym.scancode >= 224 && event->keysym.scancode < 224 + 8) - result = scantokey[event->keysym.scancode - 104]; - else - result = 0; + if (!*text) + return; + + len = strlen(text); + for (i = 0; i < len; ++i) + { + unichar = TextEncodingDecodeUTF8(text, &i); + if (unichar) + Key_Event_TextInput(unichar); + } +} + +static void keyb_event(SDL_KeyboardEvent *event) +{ + byte result = Key_ScancodeToQuakeCode(event->keysym.scancode); + if (result == 0) { Com_Printf("%s: unknown scancode %d\n", __func__, event->keysym.scancode); return; @@ -386,6 +423,9 @@ static void HandleEvents() case SDL_KEYUP: keyb_event(&event.key); break; + case SDL_TEXTINPUT: + keyb_textinputevent(event.text.text); + break; case SDL_MOUSEMOTION: if (mouse_active && !SDL_GetRelativeMouseMode()) { mx = old_x - event.motion.x; @@ -412,6 +452,8 @@ void VID_Shutdown(void) { IN_DeactivateMouse(); + SDL_StopTextInput(); + if (sdl_context) { SDL_GL_DeleteContext(sdl_context); sdl_context = NULL; @@ -429,7 +471,6 @@ void VID_Shutdown(void) Q_free(modelist); modelist_count = 0; - } static int VID_SDL_InitSubSystem(void) @@ -441,6 +482,8 @@ static int VID_SDL_InitSubSystem(void) } } + SDL_StartTextInput(); + return 0; }