Make Unicode input work for SDL

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/
This commit is contained in:
Daniel Gibson 2015-01-27 20:13:00 +01:00
parent d691002296
commit 65d1428dd1
3 changed files with 98 additions and 43 deletions

View file

@ -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;
}

View file

@ -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
};

View file

@ -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;