From 65d1428dd117f2446e70c8352190f0955125cc36 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 27 Jan 2015 20:13:00 +0100 Subject: [PATCH 1/3] 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; From aa51d38b3c34aa4cbc8c1891bd4b39609a283e7c Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 27 Jan 2015 20:15:30 +0100 Subject: [PATCH 2/3] Support up to 16 mouse buttons (SDL-only) Mostly necessary because SDL doesn't properly return mouse buttons X1/X2 on Linux/X11, see https://bugzilla.libsdl.org/show_bug.cgi?id=2310 Not sure if this is possible with Windows, DIMOFS_BUTTON7 seems to be the highest constant there. Also passing sdlevent.wheel.y directly as scroll delta --- neo/framework/KeyInput.cpp | 11 +++++++ neo/framework/UsercmdGen.cpp | 10 ++++++ neo/sys/sdl/sdl_events.cpp | 63 ++++++++++++++++++++---------------- neo/sys/sys_public.h | 21 ++++++++++++ 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index 564bf989..15678566 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -204,6 +204,17 @@ keyname_t keynames[] = NAMEKEY( MOUSE7, "#str_07060" ), NAMEKEY( MOUSE8, "#str_07061" ), + // DG: some more mouse buttons + NAMEKEY2( MOUSE9 ), + NAMEKEY2( MOUSE10 ), + NAMEKEY2( MOUSE11 ), + NAMEKEY2( MOUSE12 ), + NAMEKEY2( MOUSE13 ), + NAMEKEY2( MOUSE14 ), + NAMEKEY2( MOUSE15 ), + NAMEKEY2( MOUSE16 ), + // DG end + NAMEKEY( MWHEELDOWN, "#str_07132" ), NAMEKEY( MWHEELUP, "#str_07131" ), diff --git a/neo/framework/UsercmdGen.cpp b/neo/framework/UsercmdGen.cpp index 873b7a0b..46c4049f 100644 --- a/neo/framework/UsercmdGen.cpp +++ b/neo/framework/UsercmdGen.cpp @@ -1360,6 +1360,16 @@ void idUsercmdGenLocal::Mouse() case M_ACTION6: case M_ACTION7: case M_ACTION8: + + // DG: support some more mouse buttons + case M_ACTION9: + case M_ACTION10: + case M_ACTION11: + case M_ACTION12: + case M_ACTION13: + case M_ACTION14: + case M_ACTION15: + case M_ACTION16: // DG end mouseButton = K_MOUSE1 + ( action - M_ACTION1 ); mouseDown = ( value != 0 ); Key( mouseButton, mouseDown ); diff --git a/neo/sys/sdl/sdl_events.cpp b/neo/sys/sdl/sdl_events.cpp index 740116e1..840f24cf 100644 --- a/neo/sys/sdl/sdl_events.cpp +++ b/neo/sys/sdl/sdl_events.cpp @@ -827,8 +827,9 @@ Sys_GetEvent */ sysEvent_t Sys_GetEvent() { - SDL_Event ev; sysEvent_t res = { }; + + SDL_Event ev; int key; // when this is returned, it's assumed that there are no more events! @@ -870,7 +871,7 @@ sysEvent_t Sys_GetEvent() return res; } // DG end -#endif +#endif // SDL2 static int32 uniChar = 0; @@ -942,7 +943,7 @@ sysEvent_t Sys_GetEvent() } continue; // handle next event -#else +#else // SDL 1.2 case SDL_ACTIVEEVENT: { // DG: (un-)pause the game when focus is gained, that also (un-)grabs the input @@ -985,8 +986,8 @@ sysEvent_t Sys_GetEvent() PushConsoleEvent( "vid_restart" ); continue; // handle next event } - // DG end -#endif + // DG end +#endif // SDL1.2 case SDL_KEYDOWN: if( ev.key.keysym.sym == SDLK_RETURN && ( ev.key.keysym.mod & KMOD_ALT ) > 0 ) @@ -1023,7 +1024,7 @@ sysEvent_t Sys_GetEvent() uniChar = ev.key.keysym.unicode; // for SE_CHAR } // DG end -#endif +#endif // SDL 1.2 // fall through case SDL_KEYUP: @@ -1049,7 +1050,7 @@ sysEvent_t Sys_GetEvent() continue; // just handle next event } -#else +#else // SDL1.2 key = SDL_KeyToDoom3Key( ev.key.keysym.sym, isChar ); if( key == 0 ) @@ -1079,7 +1080,7 @@ sysEvent_t Sys_GetEvent() continue; // just handle next event } } -#endif +#endif // SDL 1.2 } res.evType = SE_KEY; @@ -1117,7 +1118,7 @@ sysEvent_t Sys_GetEvent() } continue; // just handle next event -#endif +#endif // SDL2 case SDL_MOUSEMOTION: // DG: return event with absolute mouse-coordinates when in menu @@ -1150,25 +1151,16 @@ sysEvent_t Sys_GetEvent() case SDL_MOUSEWHEEL: res.evType = SE_KEY; - if( ev.wheel.y > 0 ) - { - res.evValue = K_MWHEELUP; - mouse_polls.Append( mouse_poll_t( M_DELTAZ, 1 ) ); - } - else - { - res.evValue = K_MWHEELDOWN; - mouse_polls.Append( mouse_poll_t( M_DELTAZ, -1 ) ); - } - - // DG: remember mousewheel direction to issue a "not pressed anymore" event + res.evValue = (ev.wheel.y > 0) ? K_MWHEELUP : K_MWHEELDOWN; + mouse_polls.Append( mouse_poll_t( M_DELTAZ, ev.wheel.y ) ); + + res.evValue2 = 1; // for "pressed" + + // remember mousewheel direction to issue a "not pressed anymore" event mwheelRel = res.evValue; - // DG end - - res.evValue2 = 1; return res; -#endif +#endif // SDL2 case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: @@ -1200,7 +1192,20 @@ sysEvent_t Sys_GetEvent() if( ev.button.state == SDL_PRESSED ) mouse_polls.Append( mouse_poll_t( M_DELTAZ, -1 ) ); break; -#endif +#endif // SDL1.2 + + default: + // handle X1 button and above + if( ev.button.button <= 16 ) // d3bfg doesn't support more than 16 mouse buttons + { + int buttonIndex = ev.button.button - SDL_BUTTON_LEFT; + res.evValue = K_MOUSE1 + buttonIndex; + mouse_polls.Append( mouse_poll_t( M_ACTION1 + buttonIndex, ev.button.state == SDL_PRESSED ? 1 : 0 ) ); + } + else // unsupported mouse button + { + continue; // just ignore + } } res.evValue2 = ev.button.state == SDL_PRESSED ? 1 : 0; @@ -1483,7 +1488,8 @@ sysEvent_t Sys_GetEvent() case SDL_QUIT: PushConsoleEvent( "quit" ); - return no_more_events; // don't handle next event, just quit. + res = no_more_events; // don't handle next event, just quit. + return res; case SDL_USEREVENT: switch( ev.user.code ) @@ -1503,7 +1509,8 @@ sysEvent_t Sys_GetEvent() } } - return no_more_events; + res = no_more_events; + return res; } /* diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 772984bb..c9344576 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -119,6 +119,16 @@ enum sys_mEvents M_ACTION6, M_ACTION7, M_ACTION8, + // DG: support some more mouse buttons + M_ACTION9, + M_ACTION10, + M_ACTION11, + M_ACTION12, + M_ACTION13, + M_ACTION14, + M_ACTION15, + M_ACTION16, + // DG end M_DELTAX, M_DELTAY, M_DELTAZ, @@ -390,6 +400,17 @@ enum keyNum_t K_MOUSE7, K_MOUSE8, + // DG: add some more mouse buttons + K_MOUSE9, + K_MOUSE10, + K_MOUSE11, + K_MOUSE12, + K_MOUSE13, + K_MOUSE14, + K_MOUSE15, + K_MOUSE16, + // DG end + K_MWHEELDOWN, K_MWHEELUP, From 015748f8233c6d239afa32ba0d11ed43748a7bcb Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Tue, 27 Jan 2015 20:24:40 +0100 Subject: [PATCH 3/3] SDL backend: generate SE_MOUSE_LEAVE events The SDL backend now creates SE_MOUSE_LEAVE events when the mouse leaves the window (both SDL1.2 and SDL2). For some reason, both the SWF GUI backend and CEGUI are interested in this. --- neo/sys/sdl/sdl_events.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/neo/sys/sdl/sdl_events.cpp b/neo/sys/sdl/sdl_events.cpp index 840f24cf..2dacbb4e 100644 --- a/neo/sys/sdl/sdl_events.cpp +++ b/neo/sys/sdl/sdl_events.cpp @@ -170,7 +170,7 @@ static void ConvertUTF8toUTF32(const char* utf8str, int32* utf32buf) if( cd == SDL_iconv_t(-1) ) { - const char* toFormat = "UTF-32LE"; // TODO: what does CEGUI expect on big endian machines? + const char* toFormat = "UTF-32LE"; // TODO: what does d3bfg expect on big endian machines? cd = SDL_iconv_open(toFormat, "UTF-8"); if( cd == SDL_iconv_t(-1) ) { @@ -917,6 +917,11 @@ sysEvent_t Sys_GetEvent() cvarSystem->SetCVarBool( "com_pause", true ); // DG end break; + + case SDL_WINDOWEVENT_LEAVE: + // mouse has left the window + res.evType = SE_MOUSE_LEAVE; + return res; // DG: handle resizing and moving of window case SDL_WINDOWEVENT_RESIZED: @@ -939,7 +944,6 @@ sysEvent_t Sys_GetEvent() r_windowY.SetInteger( y ); break; } - // DG end } continue; // handle next event @@ -965,6 +969,14 @@ sysEvent_t Sys_GetEvent() } cvarSystem->SetCVarBool( "com_pause", pause ); + + if( ev.active.state == SDL_APPMOUSEFOCUS && !ev.active.gain ) + { + // the mouse has left the window. + res.evType = SE_MOUSE_LEAVE; + return res; + } + } continue; // handle next event @@ -982,6 +994,7 @@ sysEvent_t Sys_GetEvent() glConfig.nativeScreenWidth = w; glConfig.nativeScreenHeight = h; + // for some reason this needs a vid_restart in SDL1 but not SDL2 so GLimp_SetScreenParms() is called PushConsoleEvent( "vid_restart" ); continue; // handle next event