Factor out an InputMap class for doing keypress decoding.

This commit is contained in:
Ryan Prichard 2015-12-01 04:09:13 -06:00
parent 42838973de
commit c60f748fb3
6 changed files with 192 additions and 124 deletions

View File

@ -40,40 +40,43 @@ const int kIncompleteEscapeTimeoutMs = 1000;
#define CSI ESC"["
#define DIM(x) (sizeof(x) / sizeof((x)[0]))
ConsoleInput::KeyDescriptor ConsoleInput::keyDescriptorTable[] = {
// Ctrl-<letter/digit> seems to be handled OK by the default code path.
// TODO: Alt-ESC is encoded as ESC ESC. Can it be handled?
{ ESC, VK_ESCAPE, '\x1B', 0, },
// Alt-<letter/digit>
{ ESC"O", 'O', 0, LEFT_ALT_PRESSED },
{ ESC"[", '[', 0, LEFT_ALT_PRESSED },
// F1-F4 function keys. F5-F12 seem to be handled more consistently among
// various TERM=xterm terminals (gnome-terminal, konsole, xterm, mintty),
// using a CSI-prefix with an optional extra modifier digit. (putty is
// also TERM=xterm, though, and has completely different modified F5-F12
// encodings.)
{ ESC"OP", VK_F1, 0, 0, }, // xt gt kon
{ ESC"OQ", VK_F2, 0, 0, }, // xt gt kon
{ ESC"OR", VK_F3, 0, 0, }, // xt gt kon
{ ESC"OS", VK_F4, 0, 0, }, // xt gt kon
{ "\x7F", VK_BACK, '\x08', 0, },
{ ESC"\x7F", VK_BACK, '\x08', LEFT_ALT_PRESSED, },
{ ESC"OH", VK_HOME, 0, 0, }, // gnome-terminal
{ ESC"OF", VK_END, 0, 0, }, // gnome-terminal
{ ESC"[Z", VK_TAB, '\t', SHIFT_PRESSED },
};
ConsoleInput::ConsoleInput(DsrSender *dsrSender) :
m_console(new Win32Console),
m_dsrSender(dsrSender),
m_dsrSent(false),
lastWriteTick(0)
{
// Generate CSI encodings and add them to the table.
struct KeyDescriptor {
const char *encoding;
InputMap::Key key;
};
static const KeyDescriptor keyDescriptorTable[] = {
// Ctrl-<letter/digit> seems to be handled OK by the default code path.
// TODO: Alt-ESC is encoded as ESC ESC. Can it be handled?
{ ESC, { VK_ESCAPE, '\x1B', 0, } },
// Alt-<letter/digit>
{ ESC"O", { 'O', '\0', LEFT_ALT_PRESSED } },
{ ESC"[", { '[', '\0', LEFT_ALT_PRESSED } },
// F1-F4 function keys. F5-F12 seem to be handled more consistently among
// various TERM=xterm terminals (gnome-terminal, konsole, xterm, mintty),
// using a CSI-prefix with an optional extra modifier digit. (putty is
// also TERM=xterm, though, and has completely different modified F5-F12
// encodings.)
{ ESC"OP", { VK_F1, '\0', 0, } }, // xt gt kon
{ ESC"OQ", { VK_F2, '\0', 0, } }, // xt gt kon
{ ESC"OR", { VK_F3, '\0', 0, } }, // xt gt kon
{ ESC"OS", { VK_F4, '\0', 0, } }, // xt gt kon
{ "\x7F", { VK_BACK, '\x08', 0, } },
{ ESC"\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
{ ESC"OH", { VK_HOME, '\0', 0, } }, // gt
{ ESC"OF", { VK_END, '\0', 0, } }, // gt
{ ESC"[Z", { VK_TAB, '\t', SHIFT_PRESSED } },
};
struct CsiEncoding {
int id;
char letter;
@ -108,6 +111,7 @@ ConsoleInput::ConsoleInput(DsrSender *dsrSender) :
{ 23, '~', VK_F11 },
{ 24, '~', VK_F12 },
};
const int kCsiShiftModifier = 1;
const int kCsiAltModifier = 2;
const int kCsiCtrlModifier = 4;
@ -118,23 +122,16 @@ ConsoleInput::ConsoleInput(DsrSender *dsrSender) :
sprintf(encoding, CSI"%c", e->letter);
else
sprintf(encoding, CSI"%d%c", e->id, e->letter);
KeyDescriptor *k = new KeyDescriptor;
k->encoding = NULL;
k->encodingLen = strlen(encoding);
k->keyState = 0;
k->unicodeChar = 0;
k->virtualKey = csiEncodings[i].virtualKey;
m_lookup.set(encoding, k);
InputMap::Key k = { csiEncodings[i].virtualKey, 0, 0 };
m_inputMap.set(encoding, k);
int id = !e->id ? 1 : e->id;
for (int mod = 2; mod <= 8; ++mod) {
sprintf(encoding, CSI"%d;%d%c", id, mod, e->letter);
KeyDescriptor *k2 = new KeyDescriptor;
*k2 = *k;
k2->encodingLen = strlen(encoding);
if ((mod - 1) & kCsiShiftModifier) k2->keyState |= SHIFT_PRESSED;
if ((mod - 1) & kCsiAltModifier) k2->keyState |= LEFT_ALT_PRESSED;
if ((mod - 1) & kCsiCtrlModifier) k2->keyState |= LEFT_CTRL_PRESSED;
m_lookup.set(encoding, k2);
k.keyState = 0;
if ((mod - 1) & kCsiShiftModifier) k.keyState |= SHIFT_PRESSED;
if ((mod - 1) & kCsiAltModifier) k.keyState |= LEFT_ALT_PRESSED;
if ((mod - 1) & kCsiCtrlModifier) k.keyState |= LEFT_CTRL_PRESSED;
m_inputMap.set(encoding, k);
}
}
@ -149,26 +146,19 @@ ConsoleInput::ConsoleInput(DsrSender *dsrSender) :
// konsole
sprintf(encoding, ESC"O%d%c", mod, 'P' + fn);
}
KeyDescriptor *k = new KeyDescriptor;
k->encoding = NULL;
k->encodingLen = strlen(encoding);
k->keyState = 0;
if ((mod - 1) & kCsiShiftModifier) k->keyState |= SHIFT_PRESSED;
if ((mod - 1) & kCsiAltModifier) k->keyState |= LEFT_ALT_PRESSED;
if ((mod - 1) & kCsiCtrlModifier) k->keyState |= LEFT_CTRL_PRESSED;
k->unicodeChar = 0;
k->virtualKey = VK_F1 + fn;
m_lookup.set(encoding, k);
InputMap::Key k = { VK_F1 + fn, 0, 0 };
if ((mod - 1) & kCsiShiftModifier) k.keyState |= SHIFT_PRESSED;
if ((mod - 1) & kCsiAltModifier) k.keyState |= LEFT_ALT_PRESSED;
if ((mod - 1) & kCsiCtrlModifier) k.keyState |= LEFT_CTRL_PRESSED;
m_inputMap.set(encoding, k);
}
}
}
// Static key encodings.
for (size_t i = 0; i < sizeof(keyDescriptorTable) / sizeof(keyDescriptorTable[0]); ++i) {
KeyDescriptor *k = new KeyDescriptor;
*k = keyDescriptorTable[i];
k->encodingLen = strlen(k->encoding);
m_lookup.set(k->encoding, k);
for (size_t i = 0; i < DIM(keyDescriptorTable); ++i) {
m_inputMap.set(keyDescriptorTable[i].encoding,
keyDescriptorTable[i].key);
}
}
@ -231,38 +221,6 @@ void ConsoleInput::flushIncompleteEscapeCode()
}
}
ConsoleInput::KeyLookup::KeyLookup() : match(NULL), children(NULL)
{
}
ConsoleInput::KeyLookup::~KeyLookup()
{
delete match;
if (children != NULL) {
for (int i = 0; i < 256; ++i)
delete (*children)[i];
}
delete [] children;
}
void ConsoleInput::KeyLookup::set(const char *encoding,
const KeyDescriptor *descriptor)
{
unsigned char ch = encoding[0];
if (ch == '\0') {
match = descriptor;
return;
}
if (children == NULL) {
children = (KeyLookup*(*)[256])new KeyLookup*[256];
memset(children, 0, sizeof(KeyLookup*) * 256);
}
if ((*children)[ch] == NULL) {
(*children)[ch] = new KeyLookup;
}
(*children)[ch]->set(encoding + 1, descriptor);
}
void ConsoleInput::doWrite(bool isEof)
{
const char *data = m_byteQueue.c_str();
@ -306,10 +264,12 @@ int ConsoleInput::scanKeyPress(std::vector<INPUT_RECORD> &records,
}
// Recognize Alt-<character>.
InputMap *const escapeSequences = m_inputMap.getChild('\x1B');
if (input[0] == '\x1B' &&
input[1] != '\0' &&
input[1] != '\x1B' &&
m_lookup.getChild('\x1B')->getChild(input[1]) == NULL) {
escapeSequences != NULL &&
escapeSequences->getChild(input[1]) == NULL) {
int len = utf8CharLength(input[1]);
if (1 + len > inputSize) {
// Incomplete character.
@ -322,7 +282,8 @@ int ConsoleInput::scanKeyPress(std::vector<INPUT_RECORD> &records,
// Recognize an ESC-encoded keypress.
bool incomplete;
const KeyDescriptor *match = lookupKey(input, isEof, &incomplete);
int matchLen;
const InputMap::Key *match = lookupKey(input, isEof, incomplete, matchLen);
if (incomplete) {
// Incomplete match -- need more characters (or wait for a
// timeout to signify flushed input).
@ -333,7 +294,7 @@ int ConsoleInput::scanKeyPress(std::vector<INPUT_RECORD> &records,
match->virtualKey,
match->unicodeChar,
match->keyState);
return match->encodingLen;
return matchLen;
}
// A UTF-8 character.
@ -571,32 +532,41 @@ int ConsoleInput::utf8CharLength(char firstByte)
}
// Find the longest matching key and node.
const ConsoleInput::KeyDescriptor *
ConsoleInput::lookupKey(const char *encoding, bool isEof, bool *incomplete)
const InputMap::Key *
ConsoleInput::lookupKey(const char *encoding, bool isEof, bool &incompleteOut,
int &matchLenOut)
{
//trace("lookupKey");
//for (int i = 0; encoding[i] != '\0'; ++i)
// trace("%d", encoding[i]);
*incomplete = false;
KeyLookup *node = &m_lookup;
const KeyDescriptor *longestMatch = NULL;
incompleteOut = false;
matchLenOut = 0;
InputMap *node = &m_inputMap;
const InputMap::Key *longestMatch = NULL;
int longestMatchLen = 0;
for (int i = 0; encoding[i] != '\0'; ++i) {
unsigned char ch = encoding[i];
node = node->getChild(ch);
//trace("ch: %d --> node:%p", ch, node);
if (node == NULL) {
matchLenOut = longestMatchLen;
return longestMatch;
} else if (node->getMatch() != NULL) {
longestMatch = node->getMatch();
} else if (node->getKey() != NULL) {
longestMatchLen = i + 1;
longestMatch = node->getKey();
}
}
if (isEof) {
matchLenOut = longestMatchLen;
return longestMatch;
} else if (node->hasChildren()) {
*incomplete = true;
incompleteOut = true;
return NULL;
} else {
matchLenOut = longestMatchLen;
return longestMatch;
}
}

View File

@ -25,6 +25,8 @@
#include <vector>
#include <windows.h>
#include "InputMap.h"
class Win32Console;
class DsrSender;
@ -37,27 +39,6 @@ public:
void flushIncompleteEscapeCode();
private:
struct KeyDescriptor {
const char *encoding;
int virtualKey;
int unicodeChar;
int keyState;
int encodingLen;
};
class KeyLookup {
public:
KeyLookup();
~KeyLookup();
void set(const char *encoding, const KeyDescriptor *descriptor);
const KeyDescriptor *getMatch() const { return match; }
bool hasChildren() const { return children != NULL; }
KeyLookup *getChild(int i) { return children != NULL ? (*children)[i] : NULL; }
private:
const KeyDescriptor *match;
KeyLookup *(*children)[256];
};
void doWrite(bool isEof);
int scanKeyPress(std::vector<INPUT_RECORD> &records,
const char *input,
@ -77,16 +58,16 @@ private:
int unicodeChar,
int keyState);
static int utf8CharLength(char firstByte);
const KeyDescriptor *lookupKey(const char *encoding, bool isEof, bool *incomplete);
const InputMap::Key *lookupKey(const char *encoding, bool isEof,
bool &incompleteOut, int &matchLenOut);
static int matchDsr(const char *encoding);
private:
static KeyDescriptor keyDescriptorTable[];
Win32Console *m_console;
DsrSender *m_dsrSender;
bool m_dsrSent;
std::string m_byteQueue;
KeyLookup m_lookup;
InputMap m_inputMap;
DWORD lastWriteTick;
};

62
src/agent/InputMap.cc Executable file
View File

@ -0,0 +1,62 @@
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#include "InputMap.h"
#include <stdlib.h>
#include <string.h>
InputMap::InputMap() : m_key(NULL), m_children(NULL) {
}
InputMap::~InputMap() {
delete m_key;
if (m_children != NULL) {
for (int i = 0; i < 256; ++i) {
delete (*m_children)[i];
}
}
delete [] m_children;
}
void InputMap::set(const char *encoding, const Key &key) {
unsigned char ch = encoding[0];
if (ch == '\0') {
setKey(key);
} else {
getOrCreateChild(ch)->set(encoding + 1, key);
}
}
void InputMap::setKey(const Key &key) {
delete m_key;
m_key = new Key(key);
}
InputMap *InputMap::getOrCreateChild(unsigned char ch) {
if (m_children == NULL) {
m_children = reinterpret_cast<InputMap*(*)[256]>(new InputMap*[256]);
memset(m_children, 0, sizeof(InputMap*) * 256);
}
if ((*m_children)[ch] == NULL) {
(*m_children)[ch] = new InputMap;
}
return (*m_children)[ch];
}

52
src/agent/InputMap.h Executable file
View File

@ -0,0 +1,52 @@
// Copyright (c) 2011-2015 Ryan Prichard
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#ifndef INPUT_MAP_H
#define INPUT_MAP_H
#include <stdlib.h>
#include "../shared/WinptyAssert.h"
class InputMap {
public:
struct Key {
int virtualKey;
int unicodeChar;
int keyState;
};
InputMap();
~InputMap();
void set(const char *encoding, const Key &key);
void setKey(const Key &key);
const Key *getKey() const { return m_key; }
bool hasChildren() const { return m_children != NULL; }
InputMap *getChild(unsigned char ch) {
return m_children != NULL ? (*m_children)[ch] : NULL;
}
InputMap *getOrCreateChild(unsigned char ch);
private:
const Key *m_key;
InputMap *(*m_children)[256];
};
#endif // INPUT_MAP_H

View File

@ -27,6 +27,7 @@ AGENT_OBJECTS = \
build/mingw/agent/ConsoleLine.o \
build/mingw/agent/Coord.o \
build/mingw/agent/EventLoop.o \
build/mingw/agent/InputMap.o \
build/mingw/agent/LargeConsoleRead.o \
build/mingw/agent/NamedPipe.o \
build/mingw/agent/SmallRect.o \

View File

@ -41,6 +41,8 @@
'agent/DsrSender.h',
'agent/EventLoop.h',
'agent/EventLoop.cc',
'agent/InputMap.h',
'agent/InputMap.cc',
'agent/LargeConsoleRead.h',
'agent/LargeConsoleRead.cc',
'agent/NamedPipe.h',