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