Merge "adb shell: Win32: fix Ctrl-C, tab completion, line editing, server echo"
This commit is contained in:
commit
87378814a0
|
@ -245,13 +245,10 @@ int usage()
|
|||
|
||||
#if defined(_WIN32)
|
||||
|
||||
// Windows does not have <termio.h>.
|
||||
static void stdin_raw_init(int fd) {
|
||||
|
||||
}
|
||||
|
||||
static void stdin_raw_restore(int fd) {
|
||||
|
||||
// Implemented in sysdeps_win32.c.
|
||||
extern "C" {
|
||||
void stdin_raw_init(int fd);
|
||||
void stdin_raw_restore(int fd);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -150,10 +150,8 @@ static __inline__ int unix_close(int fd)
|
|||
#undef close
|
||||
#define close ____xxx_close
|
||||
|
||||
static __inline__ int unix_read(int fd, void* buf, size_t len)
|
||||
{
|
||||
return read(fd, buf, len);
|
||||
}
|
||||
extern int unix_read(int fd, void* buf, size_t len);
|
||||
|
||||
#undef read
|
||||
#define read ___xxx_read
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <windows.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
@ -2250,3 +2251,905 @@ cont:
|
|||
}
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
/***** *****/
|
||||
/***** Console Window Terminal Emulation *****/
|
||||
/***** *****/
|
||||
/**************************************************************************/
|
||||
/**************************************************************************/
|
||||
|
||||
// This reads input from a Win32 console window and translates it into Unix
|
||||
// terminal-style sequences. This emulates mostly Gnome Terminal (in Normal
|
||||
// mode, not Application mode), which itself emulates xterm. Gnome Terminal
|
||||
// is emulated instead of xterm because it is probably more popular than xterm:
|
||||
// Ubuntu's default Ctrl-Alt-T shortcut opens Gnome Terminal, Gnome Terminal
|
||||
// supports modern fonts, etc. It seems best to emulate the terminal that most
|
||||
// Android developers use because they'll fix apps (the shell, etc.) to keep
|
||||
// working with that terminal's emulation.
|
||||
//
|
||||
// The point of this emulation is not to be perfect or to solve all issues with
|
||||
// console windows on Windows, but to be better than the original code which
|
||||
// just called read() (which called ReadFile(), which called ReadConsoleA())
|
||||
// which did not support Ctrl-C, tab completion, shell input line editing
|
||||
// keys, server echo, and more.
|
||||
//
|
||||
// This implementation reconfigures the console with SetConsoleMode(), then
|
||||
// calls ReadConsoleInput() to get raw input which it remaps to Unix
|
||||
// terminal-style sequences which is returned via unix_read() which is used
|
||||
// by the 'adb shell' command.
|
||||
//
|
||||
// Code organization:
|
||||
//
|
||||
// * stdin_raw_init() and stdin_raw_restore() reconfigure the console.
|
||||
// * unix_read() detects console windows (as opposed to pipes, files, etc.).
|
||||
// * _console_read() is the main code of the emulation.
|
||||
|
||||
|
||||
// Read an input record from the console; one that should be processed.
|
||||
static bool _get_interesting_input_record_uncached(const HANDLE console,
|
||||
INPUT_RECORD* const input_record) {
|
||||
for (;;) {
|
||||
DWORD read_count = 0;
|
||||
memset(input_record, 0, sizeof(*input_record));
|
||||
if (!ReadConsoleInputA(console, input_record, 1, &read_count)) {
|
||||
D("_get_interesting_input_record_uncached: ReadConsoleInputA() "
|
||||
"failure, error %ld\n", GetLastError());
|
||||
errno = EIO;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read_count == 0) { // should be impossible
|
||||
fatal("ReadConsoleInputA returned 0");
|
||||
}
|
||||
|
||||
if (read_count != 1) { // should be impossible
|
||||
fatal("ReadConsoleInputA did not return one input record");
|
||||
}
|
||||
|
||||
if ((input_record->EventType == KEY_EVENT) &&
|
||||
(input_record->Event.KeyEvent.bKeyDown)) {
|
||||
if (input_record->Event.KeyEvent.wRepeatCount == 0) {
|
||||
fatal("ReadConsoleInputA returned a key event with zero repeat"
|
||||
" count");
|
||||
}
|
||||
|
||||
// Got an interesting INPUT_RECORD, so return
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cached input record (in case _console_read() is passed a buffer that doesn't
|
||||
// have enough space to fit wRepeatCount number of key sequences). A non-zero
|
||||
// wRepeatCount indicates that a record is cached.
|
||||
static INPUT_RECORD _win32_input_record;
|
||||
|
||||
// Get the next KEY_EVENT_RECORD that should be processed.
|
||||
static KEY_EVENT_RECORD* _get_key_event_record(const HANDLE console) {
|
||||
// If nothing cached, read directly from the console until we get an
|
||||
// interesting record.
|
||||
if (_win32_input_record.Event.KeyEvent.wRepeatCount == 0) {
|
||||
if (!_get_interesting_input_record_uncached(console,
|
||||
&_win32_input_record)) {
|
||||
// There was an error, so make sure wRepeatCount is zero because
|
||||
// that signifies no cached input record.
|
||||
_win32_input_record.Event.KeyEvent.wRepeatCount = 0;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return &_win32_input_record.Event.KeyEvent;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_shift_pressed(const DWORD control_key_state) {
|
||||
return (control_key_state & SHIFT_PRESSED) != 0;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_ctrl_pressed(const DWORD control_key_state) {
|
||||
return (control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_alt_pressed(const DWORD control_key_state) {
|
||||
return (control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_numlock_on(const DWORD control_key_state) {
|
||||
return (control_key_state & NUMLOCK_ON) != 0;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_capslock_on(const DWORD control_key_state) {
|
||||
return (control_key_state & CAPSLOCK_ON) != 0;
|
||||
}
|
||||
|
||||
static __inline__ bool _is_enhanced_key(const DWORD control_key_state) {
|
||||
return (control_key_state & ENHANCED_KEY) != 0;
|
||||
}
|
||||
|
||||
// Constants from MSDN for ToAscii().
|
||||
static const BYTE TOASCII_KEY_OFF = 0x00;
|
||||
static const BYTE TOASCII_KEY_DOWN = 0x80;
|
||||
static const BYTE TOASCII_KEY_TOGGLED_ON = 0x01; // for CapsLock
|
||||
|
||||
// Given a key event, ignore a modifier key and return the character that was
|
||||
// entered without the modifier. Writes to *ch and returns the number of bytes
|
||||
// written.
|
||||
static size_t _get_char_ignoring_modifier(char* const ch,
|
||||
const KEY_EVENT_RECORD* const key_event, const DWORD control_key_state,
|
||||
const WORD modifier) {
|
||||
// If there is no character from Windows, try ignoring the specified
|
||||
// modifier and look for a character. Note that if AltGr is being used,
|
||||
// there will be a character from Windows.
|
||||
if (key_event->uChar.AsciiChar == '\0') {
|
||||
// Note that we read the control key state from the passed in argument
|
||||
// instead of from key_event since the argument has been normalized.
|
||||
if (((modifier == VK_SHIFT) &&
|
||||
_is_shift_pressed(control_key_state)) ||
|
||||
((modifier == VK_CONTROL) &&
|
||||
_is_ctrl_pressed(control_key_state)) ||
|
||||
((modifier == VK_MENU) && _is_alt_pressed(control_key_state))) {
|
||||
|
||||
BYTE key_state[256] = {0};
|
||||
key_state[VK_SHIFT] = _is_shift_pressed(control_key_state) ?
|
||||
TOASCII_KEY_DOWN : TOASCII_KEY_OFF;
|
||||
key_state[VK_CONTROL] = _is_ctrl_pressed(control_key_state) ?
|
||||
TOASCII_KEY_DOWN : TOASCII_KEY_OFF;
|
||||
key_state[VK_MENU] = _is_alt_pressed(control_key_state) ?
|
||||
TOASCII_KEY_DOWN : TOASCII_KEY_OFF;
|
||||
key_state[VK_CAPITAL] = _is_capslock_on(control_key_state) ?
|
||||
TOASCII_KEY_TOGGLED_ON : TOASCII_KEY_OFF;
|
||||
|
||||
// cause this modifier to be ignored
|
||||
key_state[modifier] = TOASCII_KEY_OFF;
|
||||
|
||||
WORD translated = 0;
|
||||
if (ToAscii(key_event->wVirtualKeyCode,
|
||||
key_event->wVirtualScanCode, key_state, &translated, 0) == 1) {
|
||||
// Ignoring the modifier, we found a character.
|
||||
*ch = (CHAR)translated;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just use whatever Windows told us originally.
|
||||
*ch = key_event->uChar.AsciiChar;
|
||||
|
||||
// If the character from Windows is NULL, return a size of zero.
|
||||
return (*ch == '\0') ? 0 : 1;
|
||||
}
|
||||
|
||||
// If a Ctrl key is pressed, lookup the character, ignoring the Ctrl key,
|
||||
// but taking into account the shift key. This is because for a sequence like
|
||||
// Ctrl-Alt-0, we want to find the character '0' and for Ctrl-Alt-Shift-0,
|
||||
// we want to find the character ')'.
|
||||
//
|
||||
// Note that Windows doesn't seem to pass bKeyDown for Ctrl-Shift-NoAlt-0
|
||||
// because it is the default key-sequence to switch the input language.
|
||||
// This is configurable in the Region and Language control panel.
|
||||
static __inline__ size_t _get_non_control_char(char* const ch,
|
||||
const KEY_EVENT_RECORD* const key_event, const DWORD control_key_state) {
|
||||
return _get_char_ignoring_modifier(ch, key_event, control_key_state,
|
||||
VK_CONTROL);
|
||||
}
|
||||
|
||||
// Get without Alt.
|
||||
static __inline__ size_t _get_non_alt_char(char* const ch,
|
||||
const KEY_EVENT_RECORD* const key_event, const DWORD control_key_state) {
|
||||
return _get_char_ignoring_modifier(ch, key_event, control_key_state,
|
||||
VK_MENU);
|
||||
}
|
||||
|
||||
// Ignore the control key, find the character from Windows, and apply any
|
||||
// Control key mappings (for example, Ctrl-2 is a NULL character). Writes to
|
||||
// *pch and returns number of bytes written.
|
||||
static size_t _get_control_character(char* const pch,
|
||||
const KEY_EVENT_RECORD* const key_event, const DWORD control_key_state) {
|
||||
const size_t len = _get_non_control_char(pch, key_event,
|
||||
control_key_state);
|
||||
|
||||
if ((len == 1) && _is_ctrl_pressed(control_key_state)) {
|
||||
char ch = *pch;
|
||||
switch (ch) {
|
||||
case '2':
|
||||
case '@':
|
||||
case '`':
|
||||
ch = '\0';
|
||||
break;
|
||||
case '3':
|
||||
case '[':
|
||||
case '{':
|
||||
ch = '\x1b';
|
||||
break;
|
||||
case '4':
|
||||
case '\\':
|
||||
case '|':
|
||||
ch = '\x1c';
|
||||
break;
|
||||
case '5':
|
||||
case ']':
|
||||
case '}':
|
||||
ch = '\x1d';
|
||||
break;
|
||||
case '6':
|
||||
case '^':
|
||||
case '~':
|
||||
ch = '\x1e';
|
||||
break;
|
||||
case '7':
|
||||
case '-':
|
||||
case '_':
|
||||
ch = '\x1f';
|
||||
break;
|
||||
case '8':
|
||||
ch = '\x7f';
|
||||
break;
|
||||
case '/':
|
||||
if (!_is_alt_pressed(control_key_state)) {
|
||||
ch = '\x1f';
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
if (!_is_alt_pressed(control_key_state)) {
|
||||
ch = '\x7f';
|
||||
}
|
||||
break;
|
||||
}
|
||||
*pch = ch;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static DWORD _normalize_altgr_control_key_state(
|
||||
const KEY_EVENT_RECORD* const key_event) {
|
||||
DWORD control_key_state = key_event->dwControlKeyState;
|
||||
|
||||
// If we're in an AltGr situation where the AltGr key is down (depending on
|
||||
// the keyboard layout, that might be the physical right alt key which
|
||||
// produces a control_key_state where Right-Alt and Left-Ctrl are down) or
|
||||
// AltGr-equivalent keys are down (any Ctrl key + any Alt key), and we have
|
||||
// a character (which indicates that there was an AltGr mapping), then act
|
||||
// as if alt and control are not really down for the purposes of modifiers.
|
||||
// This makes it so that if the user with, say, a German keyboard layout
|
||||
// presses AltGr-] (which we see as Right-Alt + Left-Ctrl + key), we just
|
||||
// output the key and we don't see the Alt and Ctrl keys.
|
||||
if (_is_ctrl_pressed(control_key_state) &&
|
||||
_is_alt_pressed(control_key_state)
|
||||
&& (key_event->uChar.AsciiChar != '\0')) {
|
||||
// Try to remove as few bits as possible to improve our chances of
|
||||
// detecting combinations like Left-Alt + AltGr, Right-Ctrl + AltGr, or
|
||||
// Left-Alt + Right-Ctrl + AltGr.
|
||||
if ((control_key_state & RIGHT_ALT_PRESSED) != 0) {
|
||||
// Remove Right-Alt.
|
||||
control_key_state &= ~RIGHT_ALT_PRESSED;
|
||||
// If uChar is set, a Ctrl key is pressed, and Right-Alt is
|
||||
// pressed, Left-Ctrl is almost always set, except if the user
|
||||
// presses Right-Ctrl, then AltGr (in that specific order) for
|
||||
// whatever reason. At any rate, make sure the bit is not set.
|
||||
control_key_state &= ~LEFT_CTRL_PRESSED;
|
||||
} else if ((control_key_state & LEFT_ALT_PRESSED) != 0) {
|
||||
// Remove Left-Alt.
|
||||
control_key_state &= ~LEFT_ALT_PRESSED;
|
||||
// Whichever Ctrl key is down, remove it from the state. We only
|
||||
// remove one key, to improve our chances of detecting the
|
||||
// corner-case of Left-Ctrl + Left-Alt + Right-Ctrl.
|
||||
if ((control_key_state & LEFT_CTRL_PRESSED) != 0) {
|
||||
// Remove Left-Ctrl.
|
||||
control_key_state &= ~LEFT_CTRL_PRESSED;
|
||||
} else if ((control_key_state & RIGHT_CTRL_PRESSED) != 0) {
|
||||
// Remove Right-Ctrl.
|
||||
control_key_state &= ~RIGHT_CTRL_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this logic isn't 100% perfect because Windows doesn't
|
||||
// allow us to detect all combinations because a physical AltGr key
|
||||
// press shows up as two bits, plus some combinations are ambiguous
|
||||
// about what is actually physically pressed.
|
||||
}
|
||||
|
||||
return control_key_state;
|
||||
}
|
||||
|
||||
// If NumLock is on and Shift is pressed, SHIFT_PRESSED is not set in
|
||||
// dwControlKeyState for the following keypad keys: period, 0-9. If we detect
|
||||
// this scenario, set the SHIFT_PRESSED bit so we can add modifiers
|
||||
// appropriately.
|
||||
static DWORD _normalize_keypad_control_key_state(const WORD vk,
|
||||
const DWORD control_key_state) {
|
||||
if (!_is_numlock_on(control_key_state)) {
|
||||
return control_key_state;
|
||||
}
|
||||
if (!_is_enhanced_key(control_key_state)) {
|
||||
switch (vk) {
|
||||
case VK_INSERT: // 0
|
||||
case VK_DELETE: // .
|
||||
case VK_END: // 1
|
||||
case VK_DOWN: // 2
|
||||
case VK_NEXT: // 3
|
||||
case VK_LEFT: // 4
|
||||
case VK_CLEAR: // 5
|
||||
case VK_RIGHT: // 6
|
||||
case VK_HOME: // 7
|
||||
case VK_UP: // 8
|
||||
case VK_PRIOR: // 9
|
||||
return control_key_state | SHIFT_PRESSED;
|
||||
}
|
||||
}
|
||||
|
||||
return control_key_state;
|
||||
}
|
||||
|
||||
static const char* _get_keypad_sequence(const DWORD control_key_state,
|
||||
const char* const normal, const char* const shifted) {
|
||||
if (_is_shift_pressed(control_key_state)) {
|
||||
// Shift is pressed and NumLock is off
|
||||
return shifted;
|
||||
} else {
|
||||
// Shift is not pressed and NumLock is off, or,
|
||||
// Shift is pressed and NumLock is on, in which case we want the
|
||||
// NumLock and Shift to neutralize each other, thus, we want the normal
|
||||
// sequence.
|
||||
return normal;
|
||||
}
|
||||
// If Shift is not pressed and NumLock is on, a different virtual key code
|
||||
// is returned by Windows, which can be taken care of by a different case
|
||||
// statement in _console_read().
|
||||
}
|
||||
|
||||
// Write sequence to buf and return the number of bytes written.
|
||||
static size_t _get_modifier_sequence(char* const buf, const WORD vk,
|
||||
DWORD control_key_state, const char* const normal) {
|
||||
// Copy the base sequence into buf.
|
||||
const size_t len = strlen(normal);
|
||||
memcpy(buf, normal, len);
|
||||
|
||||
int code = 0;
|
||||
|
||||
control_key_state = _normalize_keypad_control_key_state(vk,
|
||||
control_key_state);
|
||||
|
||||
if (_is_shift_pressed(control_key_state)) {
|
||||
code |= 0x1;
|
||||
}
|
||||
if (_is_alt_pressed(control_key_state)) { // any alt key pressed
|
||||
code |= 0x2;
|
||||
}
|
||||
if (_is_ctrl_pressed(control_key_state)) { // any control key pressed
|
||||
code |= 0x4;
|
||||
}
|
||||
// If some modifier was held down, then we need to insert the modifier code
|
||||
if (code != 0) {
|
||||
if (len == 0) {
|
||||
// Should be impossible because caller should pass a string of
|
||||
// non-zero length.
|
||||
return 0;
|
||||
}
|
||||
size_t index = len - 1;
|
||||
const char lastChar = buf[index];
|
||||
if (lastChar != '~') {
|
||||
buf[index++] = '1';
|
||||
}
|
||||
buf[index++] = ';'; // modifier separator
|
||||
// 2 = shift, 3 = alt, 4 = shift & alt, 5 = control,
|
||||
// 6 = shift & control, 7 = alt & control, 8 = shift & alt & control
|
||||
buf[index++] = '1' + code;
|
||||
buf[index++] = lastChar; // move ~ (or other last char) to the end
|
||||
return index;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// Write sequence to buf and return the number of bytes written.
|
||||
static size_t _get_modifier_keypad_sequence(char* const buf, const WORD vk,
|
||||
const DWORD control_key_state, const char* const normal,
|
||||
const char shifted) {
|
||||
if (_is_shift_pressed(control_key_state)) {
|
||||
// Shift is pressed and NumLock is off
|
||||
if (shifted != '\0') {
|
||||
buf[0] = shifted;
|
||||
return sizeof(buf[0]);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// Shift is not pressed and NumLock is off, or,
|
||||
// Shift is pressed and NumLock is on, in which case we want the
|
||||
// NumLock and Shift to neutralize each other, thus, we want the normal
|
||||
// sequence.
|
||||
return _get_modifier_sequence(buf, vk, control_key_state, normal);
|
||||
}
|
||||
// If Shift is not pressed and NumLock is on, a different virtual key code
|
||||
// is returned by Windows, which can be taken care of by a different case
|
||||
// statement in _console_read().
|
||||
}
|
||||
|
||||
// The decimal key on the keypad produces a '.' for U.S. English and a ',' for
|
||||
// Standard German. Figure this out at runtime so we know what to output for
|
||||
// Shift-VK_DELETE.
|
||||
static char _get_decimal_char() {
|
||||
return (char)MapVirtualKeyA(VK_DECIMAL, MAPVK_VK_TO_CHAR);
|
||||
}
|
||||
|
||||
// Prefix the len bytes in buf with the escape character, and then return the
|
||||
// new buffer length.
|
||||
size_t _escape_prefix(char* const buf, const size_t len) {
|
||||
// If nothing to prefix, don't do anything. We might be called with
|
||||
// len == 0, if alt was held down with a dead key which produced nothing.
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memmove(&buf[1], buf, len);
|
||||
buf[0] = '\x1b';
|
||||
return len + 1;
|
||||
}
|
||||
|
||||
// Writes to buffer buf (of length len), returning number of bytes written or
|
||||
// -1 on error. Never returns zero because Win32 consoles are never 'closed'
|
||||
// (as far as I can tell).
|
||||
static int _console_read(const HANDLE console, void* buf, size_t len) {
|
||||
for (;;) {
|
||||
KEY_EVENT_RECORD* const key_event = _get_key_event_record(console);
|
||||
if (key_event == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const WORD vk = key_event->wVirtualKeyCode;
|
||||
const CHAR ch = key_event->uChar.AsciiChar;
|
||||
const DWORD control_key_state = _normalize_altgr_control_key_state(
|
||||
key_event);
|
||||
|
||||
// The following emulation code should write the output sequence to
|
||||
// either seqstr or to seqbuf and seqbuflen.
|
||||
const char* seqstr = NULL; // NULL terminated C-string
|
||||
// Enough space for max sequence string below, plus modifiers and/or
|
||||
// escape prefix.
|
||||
char seqbuf[16];
|
||||
size_t seqbuflen = 0; // Space used in seqbuf.
|
||||
|
||||
#define MATCH(vk, normal) \
|
||||
case (vk): \
|
||||
{ \
|
||||
seqstr = (normal); \
|
||||
} \
|
||||
break;
|
||||
|
||||
// Modifier keys should affect the output sequence.
|
||||
#define MATCH_MODIFIER(vk, normal) \
|
||||
case (vk): \
|
||||
{ \
|
||||
seqbuflen = _get_modifier_sequence(seqbuf, (vk), \
|
||||
control_key_state, (normal)); \
|
||||
} \
|
||||
break;
|
||||
|
||||
// The shift key should affect the output sequence.
|
||||
#define MATCH_KEYPAD(vk, normal, shifted) \
|
||||
case (vk): \
|
||||
{ \
|
||||
seqstr = _get_keypad_sequence(control_key_state, (normal), \
|
||||
(shifted)); \
|
||||
} \
|
||||
break;
|
||||
|
||||
// The shift key and other modifier keys should affect the output
|
||||
// sequence.
|
||||
#define MATCH_MODIFIER_KEYPAD(vk, normal, shifted) \
|
||||
case (vk): \
|
||||
{ \
|
||||
seqbuflen = _get_modifier_keypad_sequence(seqbuf, (vk), \
|
||||
control_key_state, (normal), (shifted)); \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define ESC "\x1b"
|
||||
#define CSI ESC "["
|
||||
#define SS3 ESC "O"
|
||||
|
||||
// Only support normal mode, not application mode.
|
||||
|
||||
// Enhanced keys:
|
||||
// * 6-pack: insert, delete, home, end, page up, page down
|
||||
// * cursor keys: up, down, right, left
|
||||
// * keypad: divide, enter
|
||||
// * Undocumented: VK_PAUSE (Ctrl-NumLock), VK_SNAPSHOT,
|
||||
// VK_CANCEL (Ctrl-Pause/Break), VK_NUMLOCK
|
||||
if (_is_enhanced_key(control_key_state)) {
|
||||
switch (vk) {
|
||||
case VK_RETURN: // Enter key on keypad
|
||||
if (_is_ctrl_pressed(control_key_state)) {
|
||||
seqstr = "\n";
|
||||
} else {
|
||||
seqstr = "\r";
|
||||
}
|
||||
break;
|
||||
|
||||
MATCH_MODIFIER(VK_PRIOR, CSI "5~"); // Page Up
|
||||
MATCH_MODIFIER(VK_NEXT, CSI "6~"); // Page Down
|
||||
|
||||
// gnome-terminal currently sends SS3 "F" and SS3 "H", but that
|
||||
// will be fixed soon to match xterm which sends CSI "F" and
|
||||
// CSI "H". https://bugzilla.redhat.com/show_bug.cgi?id=1119764
|
||||
MATCH(VK_END, CSI "F");
|
||||
MATCH(VK_HOME, CSI "H");
|
||||
|
||||
MATCH_MODIFIER(VK_LEFT, CSI "D");
|
||||
MATCH_MODIFIER(VK_UP, CSI "A");
|
||||
MATCH_MODIFIER(VK_RIGHT, CSI "C");
|
||||
MATCH_MODIFIER(VK_DOWN, CSI "B");
|
||||
|
||||
MATCH_MODIFIER(VK_INSERT, CSI "2~");
|
||||
MATCH_MODIFIER(VK_DELETE, CSI "3~");
|
||||
|
||||
MATCH(VK_DIVIDE, "/");
|
||||
}
|
||||
} else { // Non-enhanced keys:
|
||||
switch (vk) {
|
||||
case VK_BACK: // backspace
|
||||
if (_is_alt_pressed(control_key_state)) {
|
||||
seqstr = ESC "\x7f";
|
||||
} else {
|
||||
seqstr = "\x7f";
|
||||
}
|
||||
break;
|
||||
|
||||
case VK_TAB:
|
||||
if (_is_shift_pressed(control_key_state)) {
|
||||
seqstr = CSI "Z";
|
||||
} else {
|
||||
seqstr = "\t";
|
||||
}
|
||||
break;
|
||||
|
||||
// Number 5 key in keypad when NumLock is off, or if NumLock is
|
||||
// on and Shift is down.
|
||||
MATCH_KEYPAD(VK_CLEAR, CSI "E", "5");
|
||||
|
||||
case VK_RETURN: // Enter key on main keyboard
|
||||
if (_is_alt_pressed(control_key_state)) {
|
||||
seqstr = ESC "\n";
|
||||
} else if (_is_ctrl_pressed(control_key_state)) {
|
||||
seqstr = "\n";
|
||||
} else {
|
||||
seqstr = "\r";
|
||||
}
|
||||
break;
|
||||
|
||||
// VK_ESCAPE: Don't do any special handling. The OS uses many
|
||||
// of the sequences with Escape and many of the remaining
|
||||
// sequences don't produce bKeyDown messages, only !bKeyDown
|
||||
// for whatever reason.
|
||||
|
||||
case VK_SPACE:
|
||||
if (_is_alt_pressed(control_key_state)) {
|
||||
seqstr = ESC " ";
|
||||
} else if (_is_ctrl_pressed(control_key_state)) {
|
||||
seqbuf[0] = '\0'; // NULL char
|
||||
seqbuflen = 1;
|
||||
} else {
|
||||
seqstr = " ";
|
||||
}
|
||||
break;
|
||||
|
||||
MATCH_MODIFIER_KEYPAD(VK_PRIOR, CSI "5~", '9'); // Page Up
|
||||
MATCH_MODIFIER_KEYPAD(VK_NEXT, CSI "6~", '3'); // Page Down
|
||||
|
||||
MATCH_KEYPAD(VK_END, CSI "4~", "1");
|
||||
MATCH_KEYPAD(VK_HOME, CSI "1~", "7");
|
||||
|
||||
MATCH_MODIFIER_KEYPAD(VK_LEFT, CSI "D", '4');
|
||||
MATCH_MODIFIER_KEYPAD(VK_UP, CSI "A", '8');
|
||||
MATCH_MODIFIER_KEYPAD(VK_RIGHT, CSI "C", '6');
|
||||
MATCH_MODIFIER_KEYPAD(VK_DOWN, CSI "B", '2');
|
||||
|
||||
MATCH_MODIFIER_KEYPAD(VK_INSERT, CSI "2~", '0');
|
||||
MATCH_MODIFIER_KEYPAD(VK_DELETE, CSI "3~",
|
||||
_get_decimal_char());
|
||||
|
||||
case 0x30: // 0
|
||||
case 0x31: // 1
|
||||
case 0x39: // 9
|
||||
case VK_OEM_1: // ;:
|
||||
case VK_OEM_PLUS: // =+
|
||||
case VK_OEM_COMMA: // ,<
|
||||
case VK_OEM_PERIOD: // .>
|
||||
case VK_OEM_7: // '"
|
||||
case VK_OEM_102: // depends on keyboard, could be <> or \|
|
||||
case VK_OEM_2: // /?
|
||||
case VK_OEM_3: // `~
|
||||
case VK_OEM_4: // [{
|
||||
case VK_OEM_5: // \|
|
||||
case VK_OEM_6: // ]}
|
||||
{
|
||||
seqbuflen = _get_control_character(seqbuf, key_event,
|
||||
control_key_state);
|
||||
|
||||
if (_is_alt_pressed(control_key_state)) {
|
||||
seqbuflen = _escape_prefix(seqbuf, seqbuflen);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x32: // 2
|
||||
case 0x36: // 6
|
||||
case VK_OEM_MINUS: // -_
|
||||
{
|
||||
seqbuflen = _get_control_character(seqbuf, key_event,
|
||||
control_key_state);
|
||||
|
||||
// If Alt is pressed and it isn't Ctrl-Alt-ShiftUp, then
|
||||
// prefix with escape.
|
||||
if (_is_alt_pressed(control_key_state) &&
|
||||
!(_is_ctrl_pressed(control_key_state) &&
|
||||
!_is_shift_pressed(control_key_state))) {
|
||||
seqbuflen = _escape_prefix(seqbuf, seqbuflen);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x33: // 3
|
||||
case 0x34: // 4
|
||||
case 0x35: // 5
|
||||
case 0x37: // 7
|
||||
case 0x38: // 8
|
||||
{
|
||||
seqbuflen = _get_control_character(seqbuf, key_event,
|
||||
control_key_state);
|
||||
|
||||
// If Alt is pressed and it isn't Ctrl-Alt-ShiftUp, then
|
||||
// prefix with escape.
|
||||
if (_is_alt_pressed(control_key_state) &&
|
||||
!(_is_ctrl_pressed(control_key_state) &&
|
||||
!_is_shift_pressed(control_key_state))) {
|
||||
seqbuflen = _escape_prefix(seqbuf, seqbuflen);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x41: // a
|
||||
case 0x42: // b
|
||||
case 0x43: // c
|
||||
case 0x44: // d
|
||||
case 0x45: // e
|
||||
case 0x46: // f
|
||||
case 0x47: // g
|
||||
case 0x48: // h
|
||||
case 0x49: // i
|
||||
case 0x4a: // j
|
||||
case 0x4b: // k
|
||||
case 0x4c: // l
|
||||
case 0x4d: // m
|
||||
case 0x4e: // n
|
||||
case 0x4f: // o
|
||||
case 0x50: // p
|
||||
case 0x51: // q
|
||||
case 0x52: // r
|
||||
case 0x53: // s
|
||||
case 0x54: // t
|
||||
case 0x55: // u
|
||||
case 0x56: // v
|
||||
case 0x57: // w
|
||||
case 0x58: // x
|
||||
case 0x59: // y
|
||||
case 0x5a: // z
|
||||
{
|
||||
seqbuflen = _get_non_alt_char(seqbuf, key_event,
|
||||
control_key_state);
|
||||
|
||||
// If Alt is pressed, then prefix with escape.
|
||||
if (_is_alt_pressed(control_key_state)) {
|
||||
seqbuflen = _escape_prefix(seqbuf, seqbuflen);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// These virtual key codes are generated by the keys on the
|
||||
// keypad *when NumLock is on* and *Shift is up*.
|
||||
MATCH(VK_NUMPAD0, "0");
|
||||
MATCH(VK_NUMPAD1, "1");
|
||||
MATCH(VK_NUMPAD2, "2");
|
||||
MATCH(VK_NUMPAD3, "3");
|
||||
MATCH(VK_NUMPAD4, "4");
|
||||
MATCH(VK_NUMPAD5, "5");
|
||||
MATCH(VK_NUMPAD6, "6");
|
||||
MATCH(VK_NUMPAD7, "7");
|
||||
MATCH(VK_NUMPAD8, "8");
|
||||
MATCH(VK_NUMPAD9, "9");
|
||||
|
||||
MATCH(VK_MULTIPLY, "*");
|
||||
MATCH(VK_ADD, "+");
|
||||
MATCH(VK_SUBTRACT, "-");
|
||||
// VK_DECIMAL is generated by the . key on the keypad *when
|
||||
// NumLock is on* and *Shift is up* and the sequence is not
|
||||
// Ctrl-Alt-NoShift-. (which causes Ctrl-Alt-Del and the
|
||||
// Windows Security screen to come up).
|
||||
case VK_DECIMAL:
|
||||
// U.S. English uses '.', Germany German uses ','.
|
||||
seqbuflen = _get_non_control_char(seqbuf, key_event,
|
||||
control_key_state);
|
||||
break;
|
||||
|
||||
MATCH_MODIFIER(VK_F1, SS3 "P");
|
||||
MATCH_MODIFIER(VK_F2, SS3 "Q");
|
||||
MATCH_MODIFIER(VK_F3, SS3 "R");
|
||||
MATCH_MODIFIER(VK_F4, SS3 "S");
|
||||
MATCH_MODIFIER(VK_F5, CSI "15~");
|
||||
MATCH_MODIFIER(VK_F6, CSI "17~");
|
||||
MATCH_MODIFIER(VK_F7, CSI "18~");
|
||||
MATCH_MODIFIER(VK_F8, CSI "19~");
|
||||
MATCH_MODIFIER(VK_F9, CSI "20~");
|
||||
MATCH_MODIFIER(VK_F10, CSI "21~");
|
||||
MATCH_MODIFIER(VK_F11, CSI "23~");
|
||||
MATCH_MODIFIER(VK_F12, CSI "24~");
|
||||
|
||||
MATCH_MODIFIER(VK_F13, CSI "25~");
|
||||
MATCH_MODIFIER(VK_F14, CSI "26~");
|
||||
MATCH_MODIFIER(VK_F15, CSI "28~");
|
||||
MATCH_MODIFIER(VK_F16, CSI "29~");
|
||||
MATCH_MODIFIER(VK_F17, CSI "31~");
|
||||
MATCH_MODIFIER(VK_F18, CSI "32~");
|
||||
MATCH_MODIFIER(VK_F19, CSI "33~");
|
||||
MATCH_MODIFIER(VK_F20, CSI "34~");
|
||||
|
||||
// MATCH_MODIFIER(VK_F21, ???);
|
||||
// MATCH_MODIFIER(VK_F22, ???);
|
||||
// MATCH_MODIFIER(VK_F23, ???);
|
||||
// MATCH_MODIFIER(VK_F24, ???);
|
||||
}
|
||||
}
|
||||
|
||||
#undef MATCH
|
||||
#undef MATCH_MODIFIER
|
||||
#undef MATCH_KEYPAD
|
||||
#undef MATCH_MODIFIER_KEYPAD
|
||||
#undef ESC
|
||||
#undef CSI
|
||||
#undef SS3
|
||||
|
||||
const char* out;
|
||||
size_t outlen;
|
||||
|
||||
// Check for output in any of:
|
||||
// * seqstr is set (and strlen can be used to determine the length).
|
||||
// * seqbuf and seqbuflen are set
|
||||
// Fallback to ch from Windows.
|
||||
if (seqstr != NULL) {
|
||||
out = seqstr;
|
||||
outlen = strlen(seqstr);
|
||||
} else if (seqbuflen > 0) {
|
||||
out = seqbuf;
|
||||
outlen = seqbuflen;
|
||||
} else if (ch != '\0') {
|
||||
// Use whatever Windows told us it is.
|
||||
seqbuf[0] = ch;
|
||||
seqbuflen = 1;
|
||||
out = seqbuf;
|
||||
outlen = seqbuflen;
|
||||
} else {
|
||||
// No special handling for the virtual key code and Windows isn't
|
||||
// telling us a character code, then we don't know how to translate
|
||||
// the key press.
|
||||
//
|
||||
// Consume the input and 'continue' to cause us to get a new key
|
||||
// event.
|
||||
D("_console_read: unknown virtual key code: %d, enhanced: %s\n",
|
||||
vk, _is_enhanced_key(control_key_state) ? "true" : "false");
|
||||
key_event->wRepeatCount = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
// put output wRepeatCount times into buf/len
|
||||
while (key_event->wRepeatCount > 0) {
|
||||
if (len >= outlen) {
|
||||
// Write to buf/len
|
||||
memcpy(buf, out, outlen);
|
||||
buf = (void*)((char*)buf + outlen);
|
||||
len -= outlen;
|
||||
bytesRead += outlen;
|
||||
|
||||
// consume the input
|
||||
--key_event->wRepeatCount;
|
||||
} else {
|
||||
// Not enough space, so just leave it in _win32_input_record
|
||||
// for a subsequent retrieval.
|
||||
if (bytesRead == 0) {
|
||||
// We didn't write anything because there wasn't enough
|
||||
// space to even write one sequence. This should never
|
||||
// happen if the caller uses sensible buffer sizes
|
||||
// (i.e. >= maximum sequence length which is probably a
|
||||
// few bytes long).
|
||||
D("_console_read: no buffer space to write one sequence; "
|
||||
"buffer: %ld, sequence: %ld\n", (long)len,
|
||||
(long)outlen);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
} else {
|
||||
// Stop trying to write to buf/len, just return whatever
|
||||
// we wrote so far.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD _old_console_mode; // previous GetConsoleMode() result
|
||||
static HANDLE _console_handle; // when set, console mode should be restored
|
||||
|
||||
void stdin_raw_init(const int fd) {
|
||||
if (STDIN_FILENO == fd) {
|
||||
const HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
|
||||
if ((in == INVALID_HANDLE_VALUE) || (in == NULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetFileType(in) != FILE_TYPE_CHAR) {
|
||||
// stdin might be a file or pipe.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetConsoleMode(in, &_old_console_mode)) {
|
||||
// If GetConsoleMode() fails, stdin is probably is not a console.
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable ENABLE_PROCESSED_INPUT so that Ctrl-C is read instead of
|
||||
// calling the process Ctrl-C routine (configured by
|
||||
// SetConsoleCtrlHandler()).
|
||||
// Disable ENABLE_LINE_INPUT so that input is immediately sent.
|
||||
// Disable ENABLE_ECHO_INPUT to disable local echo. Disabling this
|
||||
// flag also seems necessary to have proper line-ending processing.
|
||||
if (!SetConsoleMode(in, _old_console_mode & ~(ENABLE_PROCESSED_INPUT |
|
||||
ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))) {
|
||||
// This really should not fail.
|
||||
D("stdin_raw_init: SetConsoleMode() failure, error %ld\n",
|
||||
GetLastError());
|
||||
}
|
||||
|
||||
// Once this is set, it means that stdin has been configured for
|
||||
// reading from and that the old console mode should be restored later.
|
||||
_console_handle = in;
|
||||
|
||||
// Note that we don't need to configure C Runtime line-ending
|
||||
// translation because _console_read() does not call the C Runtime to
|
||||
// read from the console.
|
||||
}
|
||||
}
|
||||
|
||||
void stdin_raw_restore(const int fd) {
|
||||
if (STDIN_FILENO == fd) {
|
||||
if (_console_handle != NULL) {
|
||||
const HANDLE in = _console_handle;
|
||||
_console_handle = NULL; // clear state
|
||||
|
||||
if (!SetConsoleMode(in, _old_console_mode)) {
|
||||
// This really should not fail.
|
||||
D("stdin_raw_restore: SetConsoleMode() failure, error %ld\n",
|
||||
GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by 'adb shell' command to read from stdin.
|
||||
int unix_read(int fd, void* buf, size_t len) {
|
||||
if ((fd == STDIN_FILENO) && (_console_handle != NULL)) {
|
||||
// If it is a request to read from stdin, and stdin_raw_init() has been
|
||||
// called, and it successfully configured the console, then read from
|
||||
// the console using Win32 console APIs and partially emulate a unix
|
||||
// terminal.
|
||||
return _console_read(_console_handle, buf, len);
|
||||
} else {
|
||||
// Just call into C Runtime which can read from pipes/files and which
|
||||
// can do LF/CR translation.
|
||||
#undef read
|
||||
return read(fd, buf, len);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue