shithub: choc

Download patch

ref: 5b62ddb6cbd74ad5f12c2324cba9c90bd7060c51
parent: 9c3d852db282e59996933316e65141504a756edd
author: Simon Howard <fraggle@soulsphere.org>
date: Sun Jan 3 19:16:14 EST 2016

input: Initial implementation of text input.

This should allow us to support eg. tablet devices with on-screen
keyboards in the future, but the implementation is kind of a hack.
Extend the ev_keydown event to support a third data type that
contains fully modified input characters. As a proof of concept,
switch over the Doom multiplayer chat code to use it.

--- a/src/d_event.h
+++ b/src/d_event.h
@@ -36,9 +36,14 @@
     //           pressed or released. This is the key as it appears
     //           on a US keyboard layout, and does not change with
     //           layout.
-    //    data2: ASCII version of the character that was pressed.
-    //           Changes with the keyboard layout; eg. if 'Z' is
+    // For ev_keydown only:
+    //    data2: ASCII representation of the key that was pressed that
+    //           changes with the keyboard layout; eg. if 'Z' is
     //           pressed on a German keyboard, data1='y',data2='z'.
+    //           Not affected by modifier keys.
+    //    data3: ASCII input, fully modified according to keyboard
+    //           layout and any modifier keys that are held down.
+    //           Only set if I_StartTextInput() has been called.
     ev_keydown,
     ev_keyup,
 
--- a/src/doom/hu_stuff.c
+++ b/src/doom/hu_stuff.c
@@ -24,6 +24,7 @@
 #include "z_zone.h"
 
 #include "deh_main.h"
+#include "i_input.h"
 #include "i_swap.h"
 #include "i_video.h"
 
@@ -560,6 +561,21 @@
     return c;
 }
 
+static void StartChatInput(int dest)
+{
+    chat_on = true;
+    HUlib_resetIText(&w_chat);
+    HU_queueChatChar(HU_BROADCAST);
+
+    I_StartTextInput(0, 8, SCREENWIDTH, 16);
+}
+
+static void StopChatInput(void)
+{
+    chat_on = false;
+    I_StopTextInput();
+}
+
 boolean HU_Responder(event_t *ev)
 {
 
@@ -600,9 +616,8 @@
 	}
 	else if (netgame && ev->data2 == key_multi_msg)
 	{
-	    eatkey = chat_on = true;
-	    HUlib_resetIText(&w_chat);
-	    HU_queueChatChar(HU_BROADCAST);
+	    eatkey = true;
+            StartChatInput(HU_BROADCAST);
 	}
 	else if (netgame && numplayers > 2)
 	{
@@ -612,9 +627,8 @@
 		{
 		    if (playeringame[i] && i!=consoleplayer)
 		    {
-			eatkey = chat_on = true;
-			HUlib_resetIText(&w_chat);
-			HU_queueChatChar(i+1);
+			eatkey = true;
+                        StartChatInput(i + 1);
 			break;
 		    }
 		    else if (i == consoleplayer)
@@ -645,17 +659,17 @@
 		return false;
 	    // fprintf(stderr, "got here\n");
 	    macromessage = chat_macros[c];
-	    
+
 	    // kill last message with a '\n'
 	    HU_queueChatChar(KEY_ENTER); // DEBUG!!!
-	    
+
 	    // send the macro message
 	    while (*macromessage)
 		HU_queueChatChar(*macromessage++);
 	    HU_queueChatChar(KEY_ENTER);
-	    
+
             // leave chat mode and notify that it was sent
-            chat_on = false;
+            StopChatInput();
             M_StringCopy(lastmessage, chat_macros[c], sizeof(lastmessage));
             plr->message = lastmessage;
             eatkey = true;
@@ -662,7 +676,7 @@
 	}
 	else
 	{
-            c = ev->data2;
+            c = ev->data3;
 
 	    eatkey = HUlib_keyInIText(&w_chat, c);
 	    if (eatkey)
@@ -669,13 +683,13 @@
 	    {
 		// static unsigned char buf[20]; // DEBUG
 		HU_queueChatChar(c);
-		
+
 		// M_snprintf(buf, sizeof(buf), "KEY: %d => %d", ev->data1, c);
 		//        plr->message = buf;
 	    }
 	    if (c == KEY_ENTER)
 	    {
-		chat_on = false;
+		StopChatInput();
                 if (w_chat.l.len)
                 {
                     M_StringCopy(lastmessage, w_chat.l.l, sizeof(lastmessage));
@@ -683,10 +697,11 @@
                 }
 	    }
 	    else if (c == KEY_ESCAPE)
-		chat_on = false;
+	    {
+                StopChatInput();
+            }
 	}
     }
 
     return eatkey;
-
 }
--- a/src/i_input.c
+++ b/src/i_input.c
@@ -18,6 +18,7 @@
 
 
 #include "SDL.h"
+#include "SDL_keycode.h"
 
 #include "doomkeys.h"
 #include "doomtype.h"
@@ -29,8 +30,8 @@
 static const int scancode_translate_table[] = SCANCODE_TO_KEYS_ARRAY;
 
 // Lookup table for mapping ASCII characters to their equivalent when
-// shift is pressed on an American layout keyboard:
-
+// shift is pressed on a US layout keyboard. This is the original table
+// as found in the Doom sources, comments and all.
 static const char shiftxform[] =
 {
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
@@ -70,6 +71,10 @@
     '{', '|', '}', '~', 127
 };
 
+// If true, I_StartTextInput() has been called, and we are populating
+// the data3 field of ev_keydown events.
+static boolean text_input_enabled = true;
+
 // Bit mask of mouse button state.
 static unsigned int mouse_button_state = 0;
 
@@ -95,9 +100,7 @@
 float mouse_acceleration = 2.0;
 int mouse_threshold = 10;
 
-//
-// Translates the SDL key
-//
+// Translates the SDL key to a value of the type found in doomkeys.h
 static int TranslateKey(SDL_Keysym *sym)
 {
     int scancode = sym->scancode;
@@ -130,15 +133,46 @@
     }
 }
 
+// Get the localized version of the key press. This takes into account the
+// keyboard layout, but does not apply any changes due to modifiers, (eg.
+// shift-, alt-, etc.)
+static int GetLocalizedKey(SDL_Keysym *sym)
+{
+    // When using Vanilla mapping, we just base everything off the scancode
+    // and always pretend the user is using a US layout keyboard.
+    if (vanilla_keyboard_mapping)
+    {
+        return TranslateKey(sym);
+    }
+    else
+    {
+        int result = sym->sym;
+
+        if (result < 0 || result >= 128)
+        {
+            result = 0;
+        }
+
+        return result;
+    }
+}
+
 // Get the equivalent ASCII (Unicode?) character for a keypress.
-static int GetTypedChar(SDL_Event *event)
+static int GetTypedChar(SDL_Keysym *sym)
 {
+    // We only return typed characters when entering text, after
+    // I_StartTextInput() has been called. Otherwise we return nothing.
+    if (!text_input_enabled)
+    {
+        return 0;
+    }
+
     // If we're strictly emulating Vanilla, we should always act like
     // we're using a US layout keyboard (in ev_keydown, data1=data2).
     // Otherwise we should use the native key mapping.
     if (vanilla_keyboard_mapping)
     {
-        int result = TranslateKey(&event->key.keysym);
+        int result = TranslateKey(sym);
 
         // If shift is held down, apply the original uppercase
         // translation table used under DOS.
@@ -152,16 +186,57 @@
     }
     else
     {
-        int unicode = event->key.keysym.sym;
+        SDL_Event next_event;
 
-        if (unicode < 128)
+        // Special cases, where we always return a fixed value.
+        switch (sym->sym)
         {
-            return unicode;
+            case SDLK_BACKSPACE: return KEY_BACKSPACE;
+            case SDLK_RETURN:    return KEY_ENTER;
+            default:
+                break;
         }
-        else
+
+        // The following is a gross hack, but I don't see an easier way
+        // of doing this within the SDL2 API (in SDL1 it was easier).
+        // We want to get the fully transformed input character associated
+        // with this keypress - correct keyboard layout, appropriately
+        // transformed by any modifier keys, etc. So peek ahead in the SDL
+        // event queue and see if the key press is immediately followed by
+        // an SDL_TEXTINPUT event. If it is, it's reasonable to assume the
+        // key press and the text input are connected. Technically the SDL
+        // API does not guarantee anything of the sort, but in practice this
+        // is what happens and I've verified it through manual inspect of
+        // the SDL source code.
+        //
+        // In an ideal world we'd split out ev_keydown into a separate
+        // ev_textinput event, as SDL2 has done. But this doesn't work
+        // (I experimented with the idea), because lots of Doom's code is
+        // based around different responders "eating" events to stop them
+        // being passed on to another responder. If code is listening for
+        // a text input, it cannot block the corresponding keydown events
+        // which can affect other responders.
+        //
+        // So we're stuck with this as a rather fragile alternative.
+
+        if (SDL_PeepEvents(&next_event, 1, SDL_PEEKEVENT,
+                           SDL_FIRSTEVENT, SDL_LASTEVENT) == 1
+         && next_event.type == SDL_TEXTINPUT)
         {
-            return 0;
+            // If an SDL_TEXTINPUT event is found, we always assume it
+            // matches the key press. The input text must be a single
+            // ASCII character - if it isn't, it's possible the input
+            // char is a Unicode value instead; better to send a null
+            // character than the unshifted key.
+            if (strlen(next_event.text.text) == 1
+             && (next_event.text.text[0] & 0x80) == 0)
+            {
+                return next_event.text.text[0];
+            }
         }
+
+        // Failed to find anything :/
+        return 0;
     }
 }
 
@@ -172,11 +247,10 @@
     switch (sdlevent->type)
     {
         case SDL_KEYDOWN:
-            // data1 has the key pressed, data2 has the character
-            // (shift-translated, etc)
             event.type = ev_keydown;
             event.data1 = TranslateKey(&sdlevent->key.keysym);
-            event.data2 = GetTypedChar(sdlevent);
+            event.data2 = GetLocalizedKey(&sdlevent->key.keysym);
+            event.data3 = GetTypedChar(&sdlevent->key.keysym);
 
             if (event.data1 != 0)
             {
@@ -188,7 +262,7 @@
             event.type = ev_keyup;
             event.data1 = TranslateKey(&sdlevent->key.keysym);
 
-            // data2 is just initialized to zero for ev_keyup.
+            // data2/data3 are initialized to zero for ev_keyup.
             // For ev_keydown it's the shifted Unicode character
             // that was typed, but if something wants to detect
             // key releases it should do so based on data1
@@ -195,6 +269,7 @@
             // (key ID), not the printable char.
 
             event.data2 = 0;
+            event.data3 = 0;
 
             if (event.data1 != 0)
             {
@@ -204,6 +279,27 @@
 
         default:
             break;
+    }
+}
+
+void I_StartTextInput(int x1, int y1, int x2, int y2)
+{
+    text_input_enabled = true;
+
+    if (!vanilla_keyboard_mapping)
+    {
+        // SDL2-TODO: SDL_SetTextInputRect(...);
+        SDL_StartTextInput();
+    }
+}
+
+void I_StopTextInput(void)
+{
+    text_input_enabled = false;
+
+    if (!vanilla_keyboard_mapping)
+    {
+        SDL_StopTextInput();
     }
 }
 
--- a/src/i_input.h
+++ b/src/i_input.h
@@ -31,4 +31,13 @@
 void I_ReadMouse(void);
 void I_InputCheckCommandLine(void);
 
+// I_StartTextInput begins text input, activating the on-screen keyboard
+// (if one is used). The caller indicates that any entered text will be
+// displayed in the rectangle given by the provided set of coordinates.
+void I_StartTextInput(int x1, int y1, int x2, int y2);
+
+// I_StopTextInput finishes text input, deactivating the on-screen keyboard
+// (if one is used).
+void I_StopTextInput(void);
+
 #endif