Factor out an InputMap class for doing keypress decoding.
This commit is contained in:
parent
42838973de
commit
c60f748fb3
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
62
src/agent/InputMap.cc
Executable 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
52
src/agent/InputMap.h
Executable 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
|
@ -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 \
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user