From 65d1428dd117f2446e70c8352190f0955125cc36 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 27 Jan 2015 20:13:00 +0100 Subject: [PATCH] Make Unicode input work for SDL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The d3bfg internal SE_CHAR events were documented as "evValue is an ascii char", but are actually at least UTF-16, as returned by Windows WM_CHAR events. We now assume it's UTF-32 (UTF-16 has the same values mostly) and the SDL backend now puts UTF-32 chars into SE_CHAR events. In the Windows backend I make sure that no surrogate UTF-16 chars are emitted + I added support for WM_UNICODE messages. Now I can input Ümläuts intö the conßole window \o/ --- neo/sys/sdl/sdl_events.cpp | 122 +++++++++++++++++++++++----------- neo/sys/sys_public.h | 6 +- neo/sys/win32/win_wndproc.cpp | 13 ++++ 3 files changed, 98 insertions(+), 43 deletions(-) diff --git a/neo/sys/sdl/sdl_events.cpp b/neo/sys/sdl/sdl_events.cpp index fe7a8224..740116e1 100644 --- a/neo/sys/sdl/sdl_events.cpp +++ b/neo/sys/sdl/sdl_events.cpp @@ -163,6 +163,42 @@ static SDL_Scancode KeyNumToSDLScanCode( int keyNum ) return SDL_SCANCODE_UNKNOWN; } +// both strings are expected to have at most SDL_TEXTINPUTEVENT_TEXT_SIZE chars/ints (including terminating null) +static void ConvertUTF8toUTF32(const char* utf8str, int32* utf32buf) +{ + static SDL_iconv_t cd = SDL_iconv_t(-1); + + if( cd == SDL_iconv_t(-1) ) + { + const char* toFormat = "UTF-32LE"; // TODO: what does CEGUI expect on big endian machines? + cd = SDL_iconv_open(toFormat, "UTF-8"); + if( cd == SDL_iconv_t(-1) ) + { + common->Warning("Couldn't initialize SDL_iconv for UTF-8 to UTF-32!"); // TODO: or error? + return; + } + } + + size_t len = strlen(utf8str); + + size_t inbytesleft = len; + size_t outbytesleft = 4 * SDL_TEXTINPUTEVENT_TEXT_SIZE; // *4 because utf-32 needs 4x as much space as utf-8 + char* outbuf = (char*)utf32buf; + size_t n = SDL_iconv(cd, &utf8str, &inbytesleft, &outbuf, &outbytesleft); + + if( n == size_t(-1) ) // some error occured during iconv + { + common->Warning("Converting UTF-8 string \"%s\" from SDL_TEXTINPUT to UTF-32 failed!", utf8str); + + // clear utf32-buffer, just to be sure there's no garbage.. + memset(utf32buf, 0, SDL_TEXTINPUTEVENT_TEXT_SIZE*sizeof(int32)); + } + + // reset cd so it can be used again + SDL_iconv(cd, NULL, &inbytesleft, NULL, &outbytesleft); + +} + #else // SDL1.2 static int SDL_KeyToDoom3Key( SDL_Keycode key, bool& isChar ) { @@ -802,24 +838,26 @@ sysEvent_t Sys_GetEvent() static int previous_hat_state = SDL_HAT_CENTERED; #if SDL_VERSION_ATLEAST(2, 0, 0) - static char str[SDL_TEXTINPUTEVENT_TEXT_SIZE] = {0}; - static size_t str_pos = 0; - - if( str_pos != 0 ) + // utf-32 version of the textinput event + static int32 uniStr[SDL_TEXTINPUTEVENT_TEXT_SIZE] = {0}; + static size_t uniStrPos = 0; + + if(uniStr[0] != 0) { res.evType = SE_CHAR; - res.evValue = str[str_pos]; - - ++str_pos; - if( !str[str_pos] ) + res.evValue = uniStr[uniStrPos]; + + ++uniStrPos; + + if( !uniStr[uniStrPos] || uniStrPos == SDL_TEXTINPUTEVENT_TEXT_SIZE ) { - memset( str, 0, sizeof( str ) ); - str_pos = 0; + memset(uniStr, 0, sizeof(uniStr)); + uniStrPos = 0; } - + return res; } - + // DG: fake a "mousewheel not pressed anymore" event for SDL2 // so scrolling in menus stops after one step static int mwheelRel = 0; @@ -834,18 +872,18 @@ sysEvent_t Sys_GetEvent() // DG end #endif - static byte c = 0; + static int32 uniChar = 0; - if( c ) + if(uniChar) { res.evType = SE_CHAR; - res.evValue = c; - - c = 0; - + res.evValue = uniChar; + + uniChar = 0; + return res; } - + // loop until there is an event we care about (will return then) or no more events while( SDL_PollEvent( &ev ) ) { @@ -979,11 +1017,10 @@ sysEvent_t Sys_GetEvent() #if ! SDL_VERSION_ATLEAST(2, 0, 0) // DG: only do this for key-down, don't care about isChar from SDL_KeyToDoom3Key. - // if unicode is not 0 and is translatable to ASCII it should work.. - if( ev.key.state == SDL_PRESSED && ( ev.key.keysym.unicode & 0xff80 ) == 0 ) + // if unicode is not 0 it should work.. + if( ev.key.state == SDL_PRESSED ) { - // FIXME: can we support utf32? - c = ev.key.keysym.unicode & 0x7f; + uniChar = ev.key.keysym.unicode; // for SE_CHAR } // DG end #endif @@ -997,7 +1034,7 @@ sysEvent_t Sys_GetEvent() if( ev.key.keysym.scancode == SDL_SCANCODE_GRAVE ) { key = K_GRAVE; - c = K_BACKSPACE; // bad hack to get empty console inputline.. + uniChar = K_BACKSPACE; // bad hack to get empty console inputline.. } // DG end, the original code is in the else case else { @@ -1022,24 +1059,23 @@ sysEvent_t Sys_GetEvent() if( uc == Sys_GetConsoleKey( false ) || uc == Sys_GetConsoleKey( true ) ) { key = K_GRAVE; - c = K_BACKSPACE; // bad hack to get empty console inputline.. + uniChar = K_BACKSPACE; // bad hack to get empty console inputline.. } else { - if( c ) + if(uniChar) { res.evType = SE_CHAR; - res.evValue = c; - - c = 0; - + res.evValue = uniChar; + + uniChar = 0; + return res; } if( ev.type == SDL_KEYDOWN ) // FIXME: don't complain if this was an ASCII char and the console is open? common->Warning( "unmapped SDL key %d (0x%x) scancode %d", ev.key.keysym.sym, ev.key.keysym.unicode, ev.key.keysym.scancode ); - - + continue; // just handle next event } } @@ -1053,7 +1089,7 @@ sysEvent_t Sys_GetEvent() kbd_polls.Append( kbd_poll_t( key, ev.key.state == SDL_PRESSED ) ); if( key == K_BACKSPACE && ev.key.state == SDL_PRESSED ) - c = key; + uniChar = key; return res; } @@ -1062,15 +1098,21 @@ sysEvent_t Sys_GetEvent() case SDL_TEXTINPUT: if( ev.text.text[0] != '\0' ) { - // FIXME: all this really only works for ascii.. convert to unicode etc - if( ev.text.text[1] ) - { - // more than 1 char => handle the next chars later - idStr::Copynz( str, ev.text.text + 1, sizeof( str ) ); - } + // fill uniStr array for SE_CHAR events + ConvertUTF8toUTF32(ev.text.text, uniStr); + // return an event with the first/only char res.evType = SE_CHAR; - res.evValue = ev.text.text[0]; + res.evValue = uniStr[0]; + + uniStrPos = 1; + + if( uniStr[1] == 0) + { + // it's just this one character, clear uniStr + uniStr[0] = 0; + uniStrPos = 0; + } return res; } diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index efc34c11..772984bb 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -101,11 +101,11 @@ enum sysEventType_t { SE_NONE, // evTime is still valid SE_KEY, // evValue is a key code, evValue2 is the down flag - SE_CHAR, // evValue is an ascii char FIXME: not really ascii, supports umlauts... - SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_CHAR, // evValue is an Unicode UTF-32 char (or non-surrogate UTF-16) + SE_MOUSE, // evValue and evValue2 are relative signed x / y moves SE_MOUSE_ABSOLUTE, // evValue and evValue2 are absolute coordinates in the window's client area. SE_MOUSE_LEAVE, // evValue and evValue2 are meaninless, this indicates the mouse has left the client area. - SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127) SE_CONSOLE // evPtr is a char*, from typing something at a non-game console }; diff --git a/neo/sys/win32/win_wndproc.cpp b/neo/sys/win32/win_wndproc.cpp index e33850f5..bf159b70 100644 --- a/neo/sys/win32/win_wndproc.cpp +++ b/neo/sys/win32/win_wndproc.cpp @@ -338,6 +338,7 @@ LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) key = K_NUMLOCK; } Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 ); + break; case WM_SYSKEYUP: @@ -359,8 +360,20 @@ LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) break; case WM_CHAR: + // DG: make sure it's an utf-16 non-surrogate character (and thus a valid utf-32 character as well) + // TODO: will there ever be two messages with surrogate characters that should be combined? + // (probably not, some people claim it's actually UCS-2, not UTF-16) + if( wParam < 0xD800 || wParam > 0xDFFF ) + { + Sys_QueEvent( SE_CHAR, wParam, 0, 0, NULL, 0 ); + } + break; + + // DG: support utf-32 input via WM_UNICHAR + case WM_UNICHAR: Sys_QueEvent( SE_CHAR, wParam, 0, 0, NULL, 0 ); break; + // DG end case WM_NCLBUTTONDOWN: // win32.movingWindow = true;