mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
1041 lines
34 KiB
C
1041 lines
34 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
│ │
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/assert.h"
|
|
#include "libc/calls/createfileflags.internal.h"
|
|
#include "libc/calls/internal.h"
|
|
#include "libc/calls/sig.internal.h"
|
|
#include "libc/calls/state.internal.h"
|
|
#include "libc/calls/struct/iovec.h"
|
|
#include "libc/calls/struct/sigset.internal.h"
|
|
#include "libc/calls/struct/timespec.h"
|
|
#include "libc/calls/struct/timespec.internal.h"
|
|
#include "libc/calls/syscall_support-nt.internal.h"
|
|
#include "libc/cosmo.h"
|
|
#include "libc/ctype.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/itoa.h"
|
|
#include "libc/intrin/describeflags.h"
|
|
#include "libc/intrin/dll.h"
|
|
#include "libc/intrin/fds.h"
|
|
#include "libc/intrin/kprintf.h"
|
|
#include "libc/intrin/nomultics.h"
|
|
#include "libc/intrin/strace.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/macros.h"
|
|
#include "libc/nt/console.h"
|
|
#include "libc/nt/createfile.h"
|
|
#include "libc/nt/enum/accessmask.h"
|
|
#include "libc/nt/enum/consolemodeflags.h"
|
|
#include "libc/nt/enum/creationdisposition.h"
|
|
#include "libc/nt/enum/filesharemode.h"
|
|
#include "libc/nt/enum/vk.h"
|
|
#include "libc/nt/enum/wait.h"
|
|
#include "libc/nt/errors.h"
|
|
#include "libc/nt/events.h"
|
|
#include "libc/nt/runtime.h"
|
|
#include "libc/nt/struct/inputrecord.h"
|
|
#include "libc/nt/synchronization.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/str/utf16.h"
|
|
#include "libc/sysv/consts/limits.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/sicode.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
#include "libc/thread/posixthread.internal.h"
|
|
#include "libc/thread/thread.h"
|
|
#include "libc/thread/tls.h"
|
|
#ifdef __x86_64__
|
|
|
|
/**
|
|
* @fileoverview Cosmopolitan Standard Input
|
|
*
|
|
* This file implements pollable terminal i/o for Windows consoles. On
|
|
* Windows 10 the "virtual terminal processing" feature works great on
|
|
* output but their solution for input processing isn't good enough to
|
|
* support running Linux programs like Emacs. This polyfill fixes that
|
|
* and it most importantly ensures we can poll() standard input, which
|
|
* would otherwise have been impossible. We aren't using threads. What
|
|
* we do instead is have termios behaviors e.g. canonical mode editing
|
|
* happen on demand as a side effect of read/poll/ioctl activity.
|
|
*/
|
|
|
|
struct VirtualKey {
|
|
int vk;
|
|
int normal_str;
|
|
int shift_str;
|
|
int ctrl_str;
|
|
int shift_ctrl_str;
|
|
};
|
|
|
|
#define S(s) W(s "\0\0")
|
|
#define W(s) (s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
|
|
|
|
static struct VirtualKey kVirtualKey[] = {
|
|
{kNtVkUp, S("A"), S("1;2A"), S("1;5A"), S("1;6A")}, // order matters
|
|
{kNtVkDown, S("B"), S("1;2B"), S("1;5B"), S("1;6B")}, // order matters
|
|
{kNtVkRight, S("C"), S("1;2C"), S("1;5C"), S("1;6C")}, // order matters
|
|
{kNtVkLeft, S("D"), S("1;2D"), S("1;5D"), S("1;6D")}, // order matters
|
|
{kNtVkEnd, S("F"), S("1;2F"), S("1;5F"), S("1;6F")}, // order matters
|
|
{kNtVkHome, S("H"), S("1;2H"), S("1;5H"), S("1;6H")}, // order matters
|
|
{kNtVkInsert, S("2~"), S("2;2~"), S("2;5~"), S("2;6~")},
|
|
{kNtVkDelete, S("3~"), S("3;2~"), S("3;5~"), S("3;6~")},
|
|
{kNtVkPrior, S("5~"), S("5;2~"), S("5;5~"), S("5;6~")},
|
|
{kNtVkNext, S("6~"), S("6;2~"), S("6;5~"), S("6;6~")},
|
|
{kNtVkF1, -S("OP"), S("1;2P"), S("11^"), S("1;6P")},
|
|
{kNtVkF2, -S("OQ"), S("1;2Q"), S("12^"), S("1;6Q")},
|
|
{kNtVkF3, -S("OR"), S("1;2R"), S("13^"), S("1;6R")},
|
|
{kNtVkF4, -S("OS"), S("1;2S"), S("14^"), S("1;6S")},
|
|
{kNtVkF5, S("15~"), S("28~"), S("15^"), S("28^")},
|
|
{kNtVkF6, S("17~"), S("29~"), S("17^"), S("29^")},
|
|
{kNtVkF7, S("18~"), S("31~"), S("18^"), S("31^")},
|
|
{kNtVkF8, S("19~"), S("32~"), S("19^"), S("32^")},
|
|
{kNtVkF9, S("20~"), S("33~"), S("20^"), S("33^")},
|
|
{kNtVkF10, S("21~"), S("34~"), S("21^"), S("34^")},
|
|
{kNtVkF11, S("23~"), S("23$"), S("23^"), S("23@")},
|
|
{kNtVkF12, S("24~"), S("24$"), S("24^"), S("24@")},
|
|
{0},
|
|
};
|
|
|
|
#define KEYSTROKE_CONTAINER(e) DLL_CONTAINER(struct Keystroke, elem, e)
|
|
|
|
struct Keystroke {
|
|
char buf[23];
|
|
unsigned char buflen;
|
|
struct Dll elem;
|
|
};
|
|
|
|
struct Keystrokes {
|
|
atomic_uint once;
|
|
bool end_of_file;
|
|
bool ohno_decckm;
|
|
bool bypass_mode;
|
|
uint16_t utf16hs;
|
|
int16_t freekeys;
|
|
int64_t cin, cot;
|
|
struct Dll *list;
|
|
struct Dll *line;
|
|
struct Dll *free;
|
|
pthread_mutex_t lock;
|
|
struct Keystroke pool[512];
|
|
};
|
|
|
|
static struct Keystrokes __keystroke;
|
|
|
|
textwindows void WipeKeystrokes(void) {
|
|
bzero(&__keystroke, sizeof(__keystroke));
|
|
}
|
|
|
|
textwindows static void FreeKeystrokeImpl(struct Dll *key) {
|
|
dll_make_first(&__keystroke.free, key);
|
|
++__keystroke.freekeys;
|
|
}
|
|
|
|
textwindows static struct Keystroke *NewKeystroke(void) {
|
|
struct Dll *e = dll_first(__keystroke.free);
|
|
if (!e) // See MIN(freekeys) before ReadConsoleInput()
|
|
__builtin_trap();
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
dll_remove(&__keystroke.free, &k->elem);
|
|
--__keystroke.freekeys;
|
|
k->buflen = 0;
|
|
return k;
|
|
}
|
|
|
|
textwindows static void FreeKeystroke(struct Dll **list, struct Dll *key) {
|
|
dll_remove(list, key);
|
|
FreeKeystrokeImpl(key);
|
|
}
|
|
|
|
textwindows static void FreeKeystrokes(struct Dll **list) {
|
|
struct Dll *key;
|
|
while ((key = dll_first(*list)))
|
|
FreeKeystroke(list, key);
|
|
}
|
|
|
|
textwindows static void OpenConsole(void) {
|
|
__keystroke.cin = CreateFile(u"CONIN$", kNtGenericRead | kNtGenericWrite,
|
|
kNtFileShareRead, 0, kNtOpenExisting, 0, 0);
|
|
__keystroke.cot = CreateFile(u"CONOUT$", kNtGenericRead | kNtGenericWrite,
|
|
kNtFileShareWrite, 0, kNtOpenExisting, 0, 0);
|
|
for (int i = 0; i < ARRAYLEN(__keystroke.pool); ++i) {
|
|
dll_init(&__keystroke.pool[i].elem);
|
|
FreeKeystrokeImpl(&__keystroke.pool[i].elem);
|
|
}
|
|
}
|
|
|
|
textwindows static int AddSignal(int sig) {
|
|
atomic_fetch_or_explicit(&__get_tls()->tib_sigpending, 1ull << (sig - 1),
|
|
memory_order_relaxed);
|
|
return 0;
|
|
}
|
|
|
|
textwindows static void InitConsole(void) {
|
|
cosmo_once(&__keystroke.once, OpenConsole);
|
|
}
|
|
|
|
textwindows static void LockKeystrokes(void) {
|
|
pthread_mutex_lock(&__keystroke.lock);
|
|
}
|
|
|
|
textwindows static void UnlockKeystrokes(void) {
|
|
pthread_mutex_unlock(&__keystroke.lock);
|
|
}
|
|
|
|
textwindows int64_t GetConsoleInputHandle(void) {
|
|
InitConsole();
|
|
return __keystroke.cin;
|
|
}
|
|
|
|
textwindows int64_t GetConsoleOutputHandle(void) {
|
|
InitConsole();
|
|
return __keystroke.cot;
|
|
}
|
|
|
|
textwindows static bool IsMouseModeCommand(int x) {
|
|
return x == 1000 || // SET_VT200_MOUSE
|
|
x == 1002 || // SET_BTN_EVENT_MOUSE
|
|
x == 1006 || // SET_SGR_EXT_MODE_MOUSE
|
|
x == 1015; // SET_URXVT_EXT_MODE_MOUSE
|
|
}
|
|
|
|
textwindows static int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
|
|
for (int i = 0; kVirtualKey[i].vk; ++i) {
|
|
if (kVirtualKey[i].vk == vk) {
|
|
if (shift && ctrl) {
|
|
return kVirtualKey[i].shift_ctrl_str;
|
|
} else if (shift) {
|
|
return kVirtualKey[i].shift_str;
|
|
} else if (ctrl) {
|
|
return kVirtualKey[i].ctrl_str;
|
|
} else {
|
|
return kVirtualKey[i].normal_str;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
textwindows static int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
|
|
|
|
uint32_t c = r->Event.KeyEvent.uChar.UnicodeChar;
|
|
uint16_t vk = r->Event.KeyEvent.wVirtualKeyCode;
|
|
uint16_t cks = r->Event.KeyEvent.dwControlKeyState;
|
|
|
|
// ignore keyup events
|
|
if (!r->Event.KeyEvent.bKeyDown && (!c || vk != kNtVkMenu))
|
|
return 0;
|
|
|
|
#if 0
|
|
// this code is useful for troubleshooting why keys don't work
|
|
kprintf("bKeyDown=%hhhd wVirtualKeyCode=%s wVirtualScanCode=%s "
|
|
"UnicodeChar=%#x[%#lc] dwControlKeyState=%s\n",
|
|
r->Event.KeyEvent.bKeyDown,
|
|
DescribeVirtualKeyCode(r->Event.KeyEvent.wVirtualKeyCode),
|
|
DescribeVirtualKeyCode(r->Event.KeyEvent.wVirtualScanCode),
|
|
r->Event.KeyEvent.uChar.UnicodeChar,
|
|
r->Event.KeyEvent.uChar.UnicodeChar,
|
|
DescribeControlKeyState(r->Event.KeyEvent.dwControlKeyState));
|
|
#endif
|
|
|
|
// turn arrow/function keys into vt100/ansi/xterm byte sequences
|
|
int n = 0;
|
|
int v = GetVirtualKey(vk, !!(cks & kNtShiftPressed),
|
|
!!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)));
|
|
if (v) {
|
|
p[n++] = 033;
|
|
if (cks & (kNtLeftAltPressed | kNtRightAltPressed)) {
|
|
p[n++] = 033;
|
|
}
|
|
if (v > 0) {
|
|
p[n++] = '[';
|
|
} else {
|
|
v = -v;
|
|
}
|
|
do
|
|
p[n++] = v;
|
|
while ((v >>= 8));
|
|
return n;
|
|
}
|
|
|
|
// ^/ (crtl+slash) maps to ^_ (ctrl-hyphen) on linux
|
|
if (vk == kNtVkOem_2 && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
p[n++] = 037;
|
|
return n;
|
|
}
|
|
|
|
// handle cases where win32 doesn't provide character
|
|
if (!c) {
|
|
if (vk == '2' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
c = 0; // ctrl-2 → "\000"
|
|
} else if (isascii(vk) && isdigit(vk) &&
|
|
(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
c = 030 + (vk - '0'); // e.g. ctrl-3 → "\033"
|
|
} else if (isascii(vk) && isgraph(vk) &&
|
|
(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
c = vk ^ 0100; // e.g. ctrl-alt-b → "\033\002"
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// convert utf-16 to utf-32
|
|
if (IsHighSurrogate(c)) {
|
|
__keystroke.utf16hs = c;
|
|
return 0;
|
|
}
|
|
if (IsLowSurrogate(c))
|
|
c = MergeUtf16(__keystroke.utf16hs, c);
|
|
|
|
// enter sends \r with raw terminals
|
|
// make it a multics newline instead
|
|
if (c == '\r' && !(__ttyconf.magic & kTtyNoCr2Nl))
|
|
c = '\n';
|
|
|
|
// ctrl-space (^@) is literally zero
|
|
if (c == ' ' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)))
|
|
c = '\0';
|
|
|
|
// make backspace (^?) distinguishable from ctrl-h (^H)
|
|
if (c == kNtVkBack && !(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)))
|
|
c = 0177;
|
|
|
|
// handle ctrl-\ and ctrl-c
|
|
// note we define _POSIX_VDISABLE as zero
|
|
// tcsetattr() lets anyone reconfigure these keybindings
|
|
if (c && !(__ttyconf.magic & kTtyNoIsigs) && !__keystroke.bypass_mode) {
|
|
if (c == __ttyconf.vintr) {
|
|
return AddSignal(SIGINT);
|
|
} else if (c == __ttyconf.vquit) {
|
|
return AddSignal(SIGQUIT);
|
|
}
|
|
}
|
|
|
|
// handle ctrl-d which generates end-of-file, unless pending line data
|
|
// is present, in which case we flush that without the newline instead
|
|
if (c && c == __ttyconf.veof && //
|
|
!__keystroke.bypass_mode && //
|
|
!(__ttyconf.magic & kTtyUncanon)) {
|
|
if (dll_is_empty(__keystroke.line)) {
|
|
__keystroke.end_of_file = true;
|
|
} else {
|
|
dll_make_last(&__keystroke.list, __keystroke.line);
|
|
__keystroke.line = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// insert esc prefix when alt is held
|
|
// for example "h" becomes "\033h" (alt-h)
|
|
// if up arrow is "\033[A" then alt-up is "\033\033[A"
|
|
if ((cks & (kNtLeftAltPressed | kNtRightAltPressed)) &&
|
|
r->Event.KeyEvent.bKeyDown) {
|
|
p[n++] = 033;
|
|
}
|
|
|
|
// finally apply thompson-pike varint encoding
|
|
uint64_t w = tpenc(c);
|
|
do
|
|
p[n++] = w;
|
|
while ((w >>= 8));
|
|
return n;
|
|
}
|
|
|
|
// To use the tty mouse events feature:
|
|
// - write(1, "\e[?1000;1002;1015;1006h") to enable
|
|
// - write(1, "\e[?1000;1002;1015;1006l") to disable
|
|
// See o//examples/ttyinfo and o//tool/viz/life
|
|
textwindows static int ProcessMouseEvent(const struct NtInputRecord *r,
|
|
char *b) {
|
|
char *p = b;
|
|
unsigned char e = 0;
|
|
uint32_t currentbs = __ttyconf.mousebs;
|
|
uint32_t ev = r->Event.MouseEvent.dwEventFlags;
|
|
uint32_t bs = r->Event.MouseEvent.dwButtonState;
|
|
ev &= kNtMouseMoved | kNtMouseWheeled;
|
|
bs &= kNtFromLeft1stButtonPressed | kNtRightmostButtonPressed;
|
|
if (ev & kNtMouseWheeled) {
|
|
// scroll wheel (unnatural mode)
|
|
bool isup = ((int)r->Event.MouseEvent.dwButtonState >> 16) > 0;
|
|
if (__ttyconf.magic & kTtyXtMouse) {
|
|
if (r->Event.MouseEvent.dwControlKeyState &
|
|
(kNtLeftCtrlPressed | kNtRightCtrlPressed)) {
|
|
e = isup ? 80 : 81;
|
|
} else {
|
|
e = isup ? 64 : 65;
|
|
}
|
|
goto OutputXtermMouseEvent;
|
|
} else if (!(r->Event.MouseEvent.dwControlKeyState &
|
|
(kNtShiftPressed | kNtLeftCtrlPressed | kNtRightCtrlPressed |
|
|
kNtLeftAltPressed | kNtRightAltPressed))) {
|
|
// we disable mouse highlighting when the tty is put in raw mode
|
|
// to mouse wheel events with widely understood vt100 arrow keys
|
|
for (int i = 0; i < 3; ++i) {
|
|
*p++ = 033;
|
|
*p++ = !__keystroke.ohno_decckm ? '[' : 'O';
|
|
if (isup) {
|
|
*p++ = 'A';
|
|
} else {
|
|
*p++ = 'B';
|
|
}
|
|
}
|
|
}
|
|
} else if ((bs || currentbs) && (__ttyconf.magic & kTtyXtMouse)) {
|
|
if (bs && (ev & kNtMouseMoved) && currentbs)
|
|
e |= 32; // dragging
|
|
if ((bs | currentbs) & kNtRightmostButtonPressed)
|
|
e |= 2; // right
|
|
OutputXtermMouseEvent:
|
|
*p++ = 033;
|
|
*p++ = '[';
|
|
*p++ = '<';
|
|
p = FormatInt32(p, e);
|
|
*p++ = ';';
|
|
p = FormatInt32(p, (r->Event.MouseEvent.dwMousePosition.X + 1) & 0x7fff);
|
|
*p++ = ';';
|
|
p = FormatInt32(p, (r->Event.MouseEvent.dwMousePosition.Y + 1) & 0x7fff);
|
|
if (!bs && currentbs) {
|
|
*p++ = 'm'; // up
|
|
} else {
|
|
*p++ = 'M'; // down
|
|
}
|
|
__ttyconf.mousebs = bs;
|
|
}
|
|
return p - b;
|
|
}
|
|
|
|
textwindows static int ConvertConsoleInputToAnsi(const struct NtInputRecord *r,
|
|
char p[hasatleast 23]) {
|
|
switch (r->EventType) {
|
|
case kNtKeyEvent:
|
|
return ProcessKeyEvent(r, p);
|
|
case kNtMouseEvent:
|
|
return ProcessMouseEvent(r, p);
|
|
case kNtWindowBufferSizeEvent:
|
|
return AddSignal(SIGWINCH);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
textwindows static void WriteTty(const char *p, size_t n) {
|
|
WriteFile(__keystroke.cot, p, n, 0, 0);
|
|
}
|
|
|
|
textwindows static bool IsCtl(int c, bool escape_harder) {
|
|
return isascii(c) && iscntrl(c) &&
|
|
(escape_harder || (c != '\n' && c != '\t'));
|
|
}
|
|
|
|
textwindows static void WriteCtl(const char *p, size_t n, bool escape_harder) {
|
|
size_t i;
|
|
for (i = 0; i < n; ++i) {
|
|
if (IsCtl(p[i], escape_harder)) {
|
|
char ctl[2];
|
|
ctl[0] = '^';
|
|
ctl[1] = p[i] ^ 0100;
|
|
WriteTty(ctl, 2);
|
|
} else {
|
|
WriteTty(p + i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
textwindows static void EchoTty(const char *p, size_t n, bool escape_harder) {
|
|
if (!(__ttyconf.magic & kTtySilence)) {
|
|
if (__ttyconf.magic & kTtyEchoRaw) {
|
|
WriteTty(p, n);
|
|
} else {
|
|
WriteCtl(p, n, escape_harder);
|
|
}
|
|
}
|
|
}
|
|
|
|
textwindows static void EraseCharacter(bool should_echo) {
|
|
if (should_echo)
|
|
WriteTty("\b \b", 3);
|
|
}
|
|
|
|
textwindows static bool EraseKeystroke(bool should_echo) {
|
|
struct Dll *e;
|
|
if ((e = dll_last(__keystroke.line))) {
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
FreeKeystroke(&__keystroke.line, e);
|
|
for (int i = k->buflen; i--;) {
|
|
if ((k->buf[i] & 0300) == 0200)
|
|
continue; // utf-8 cont
|
|
EraseCharacter(should_echo);
|
|
if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i], true))
|
|
EraseCharacter(should_echo);
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
textwindows static int IsLookingAtSpace(void) {
|
|
struct Dll *e;
|
|
if ((e = dll_last(__keystroke.line))) {
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
return k->buflen == 1 && isascii(k->buf[0]) && isspace(k->buf[0]);
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
textwindows static void IngestConsoleInputRecord(struct NtInputRecord *r) {
|
|
|
|
// convert win32 console event into ansi
|
|
int len;
|
|
char buf[23];
|
|
if (!(len = ConvertConsoleInputToAnsi(r, buf)))
|
|
return;
|
|
|
|
// handle ctrl-v in canonical mode
|
|
// the next keystroke will bypass input processing
|
|
if (!(__ttyconf.magic & kTtyUncanon) && // ICANON
|
|
!(__ttyconf.magic & kTtyNoIexten)) { // IEXTEN
|
|
if (__keystroke.bypass_mode) {
|
|
struct Keystroke *k = NewKeystroke();
|
|
memcpy(k->buf, buf, sizeof(k->buf));
|
|
k->buflen = len;
|
|
dll_make_last(&__keystroke.line, &k->elem);
|
|
EchoTty(buf, len, true);
|
|
if (!__keystroke.freekeys) {
|
|
dll_make_last(&__keystroke.list, __keystroke.line);
|
|
__keystroke.line = 0;
|
|
}
|
|
__keystroke.bypass_mode = false;
|
|
return;
|
|
} else if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.vlnext) {
|
|
__keystroke.bypass_mode = true;
|
|
if (!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
!(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL
|
|
WriteTty("^\b", 2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// handle backspace in canonical mode
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.verase && //
|
|
!(__ttyconf.magic & kTtyUncanon) && //
|
|
!(__ttyconf.magic & kTtyNoIexten)) {
|
|
bool should_visually_erase = //
|
|
!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
!(__ttyconf.magic & kTtyNoEchoe); // ECHOE
|
|
EraseKeystroke(should_visually_erase);
|
|
if (!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
(__ttyconf.magic & kTtyNoEchoe) && // !ECHOE
|
|
!(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL
|
|
WriteCtl(buf, len, true);
|
|
return;
|
|
}
|
|
|
|
// handle ctrl-w in canonical mode
|
|
// this lets you erase the last word
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.vwerase && //
|
|
!(__ttyconf.magic & kTtyUncanon) && //
|
|
!(__ttyconf.magic & kTtyNoIexten)) {
|
|
bool should_visually_erase = //
|
|
!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
!(__ttyconf.magic & kTtyNoEchoe); // ECHOE
|
|
while (IsLookingAtSpace() == 1)
|
|
EraseKeystroke(should_visually_erase);
|
|
while (IsLookingAtSpace() == 0)
|
|
EraseKeystroke(should_visually_erase);
|
|
if (!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
(__ttyconf.magic & kTtyNoEchoe) && // !ECHOE
|
|
!(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL
|
|
WriteCtl(buf, len, true);
|
|
return;
|
|
}
|
|
|
|
// handle kill in canonical mode
|
|
// this clears the line you're editing
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.vkill && //
|
|
!(__ttyconf.magic & kTtyUncanon) && //
|
|
!(__ttyconf.magic & kTtyNoIexten)) {
|
|
bool should_visually_kill = //
|
|
!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
!(__ttyconf.magic & kTtyNoEchok) && // ECHOK
|
|
!(__ttyconf.magic & kTtyNoEchoke); // ECHOKE
|
|
while (EraseKeystroke(should_visually_kill)) {
|
|
}
|
|
if (!(__ttyconf.magic & kTtySilence) && // ECHO
|
|
!(__ttyconf.magic & kTtyNoEchok) && // ECHOK
|
|
(__ttyconf.magic & kTtyNoEchoke) && // !ECHOKE
|
|
!(__ttyconf.magic & kTtyEchoRaw)) // ECHOCTL
|
|
WriteCtl(buf, len, true);
|
|
return;
|
|
}
|
|
|
|
// handle ctrl-r in canonical mode
|
|
// this reprints the line you're editing
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.vreprint && //
|
|
!(__ttyconf.magic & kTtyUncanon) && // ICANON
|
|
!(__ttyconf.magic & kTtyNoIexten) && // IEXTEN
|
|
!(__ttyconf.magic & kTtySilence)) { // ECHO
|
|
struct Dll *e;
|
|
if (!(__ttyconf.magic & kTtyEchoRaw))
|
|
WriteCtl(buf, len, true);
|
|
WriteTty("\r\n", 2);
|
|
for (e = dll_first(__keystroke.line); e;
|
|
e = dll_next(__keystroke.line, e)) {
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
WriteCtl(k->buf, k->buflen, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// allocate object to hold keystroke
|
|
struct Keystroke *k = NewKeystroke();
|
|
memcpy(k->buf, buf, sizeof(k->buf));
|
|
k->buflen = len;
|
|
|
|
// echo input if it was successfully recorded
|
|
// assuming the win32 console isn't doing it already
|
|
EchoTty(buf, len, false);
|
|
|
|
// save keystroke to appropriate list
|
|
if (__ttyconf.magic & kTtyUncanon) {
|
|
dll_make_last(&__keystroke.list, &k->elem);
|
|
} else {
|
|
dll_make_last(&__keystroke.line, &k->elem);
|
|
|
|
// flush canonical mode line if oom or enter
|
|
if (!__keystroke.freekeys || (len == 1 && buf[0] &&
|
|
((buf[0] & 255) == '\n' || //
|
|
(buf[0] & 255) == __ttyconf.veol || //
|
|
((buf[0] & 255) == __ttyconf.veol2 &&
|
|
!(__ttyconf.magic & kTtyNoIexten))))) {
|
|
dll_make_last(&__keystroke.list, __keystroke.line);
|
|
__keystroke.line = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
textwindows static void IngestConsoleInput(void) {
|
|
uint32_t i, n;
|
|
struct NtInputRecord records[16];
|
|
for (;;) {
|
|
if (!__keystroke.freekeys)
|
|
return;
|
|
if (__keystroke.end_of_file)
|
|
return;
|
|
if (!GetNumberOfConsoleInputEvents(__keystroke.cin, &n))
|
|
goto UnexpectedEof;
|
|
if (!n || !__keystroke.freekeys)
|
|
return;
|
|
n = MIN(__keystroke.freekeys, MIN(ARRAYLEN(records), n));
|
|
if (!ReadConsoleInput(__keystroke.cin, records, n, &n))
|
|
goto UnexpectedEof;
|
|
for (i = 0; i < n && !__keystroke.end_of_file; ++i)
|
|
IngestConsoleInputRecord(records + i);
|
|
}
|
|
UnexpectedEof:
|
|
STRACE("console read error %d", GetLastError());
|
|
__keystroke.end_of_file = true;
|
|
}
|
|
|
|
// Discards all unread stdin bytes.
|
|
textwindows int FlushConsoleInputBytes(void) {
|
|
BLOCK_SIGNALS;
|
|
InitConsole();
|
|
LockKeystrokes();
|
|
FlushConsoleInputBuffer(__keystroke.cin);
|
|
FreeKeystrokes(&__keystroke.list);
|
|
FreeKeystrokes(&__keystroke.line);
|
|
UnlockKeystrokes();
|
|
ALLOW_SIGNALS;
|
|
return 0;
|
|
}
|
|
|
|
// Returns number of stdin bytes that may be read without blocking.
|
|
textwindows int CountConsoleInputBytes(void) {
|
|
struct Dll *e;
|
|
int count = 0;
|
|
BLOCK_SIGNALS;
|
|
InitConsole();
|
|
LockKeystrokes();
|
|
IngestConsoleInput();
|
|
for (e = dll_first(__keystroke.list); e; e = dll_next(__keystroke.list, e))
|
|
count += KEYSTROKE_CONTAINER(e)->buflen;
|
|
if (!count && __keystroke.end_of_file)
|
|
count = -1;
|
|
UnlockKeystrokes();
|
|
ALLOW_SIGNALS;
|
|
return count;
|
|
}
|
|
|
|
// Intercept ANSI TTY commands that enable features.
|
|
textwindows void InterceptTerminalCommands(const char *data, size_t size) {
|
|
int i;
|
|
unsigned x;
|
|
bool ismouse;
|
|
uint32_t cm, cm2;
|
|
enum { ASC, ESC, CSI, CMD } t;
|
|
GetConsoleMode(GetConsoleInputHandle(), &cm), cm2 = cm;
|
|
for (ismouse = false, x = i = t = 0; i < size; ++i) {
|
|
switch (t) {
|
|
case ASC:
|
|
if (data[i] == 033) {
|
|
t = ESC;
|
|
}
|
|
break;
|
|
case ESC:
|
|
if (data[i] == '[') {
|
|
t = CSI;
|
|
} else {
|
|
t = ASC;
|
|
}
|
|
break;
|
|
case CSI:
|
|
if (data[i] == '?') {
|
|
t = CMD;
|
|
x = 0;
|
|
} else {
|
|
t = ASC;
|
|
}
|
|
break;
|
|
case CMD:
|
|
if ('0' <= data[i] && data[i] <= '9') {
|
|
x *= 10;
|
|
x += data[i] - '0';
|
|
} else if (data[i] == ';') {
|
|
ismouse |= IsMouseModeCommand(x);
|
|
x = 0;
|
|
} else if (data[i] == 'h') {
|
|
if (x == 1) {
|
|
// \e[?1h decckm on
|
|
__keystroke.ohno_decckm = true;
|
|
kVirtualKey[0].normal_str = -S("OA"); // kNtVkUp
|
|
kVirtualKey[1].normal_str = -S("OB"); // kNtVkDown
|
|
kVirtualKey[2].normal_str = -S("OC"); // kNtVkRight
|
|
kVirtualKey[3].normal_str = -S("OD"); // kNtVkLeft
|
|
kVirtualKey[4].normal_str = -S("OF"); // kNtVkEnd
|
|
kVirtualKey[5].normal_str = -S("OH"); // kNtVkHome
|
|
} else if ((ismouse |= IsMouseModeCommand(x))) {
|
|
__ttyconf.magic |= kTtyXtMouse;
|
|
cm2 |= kNtEnableMouseInput;
|
|
cm2 &= ~kNtEnableQuickEditMode; // take mouse
|
|
}
|
|
t = ASC;
|
|
} else if (data[i] == 'l') {
|
|
if (x == 1) {
|
|
// \e[?1l decckm off
|
|
__keystroke.ohno_decckm = false;
|
|
kVirtualKey[0].normal_str = S("A"); // kNtVkUp
|
|
kVirtualKey[1].normal_str = S("B"); // kNtVkDown
|
|
kVirtualKey[2].normal_str = S("C"); // kNtVkRight
|
|
kVirtualKey[3].normal_str = S("D"); // kNtVkLeft
|
|
kVirtualKey[4].normal_str = S("F"); // kNtVkEnd
|
|
kVirtualKey[5].normal_str = S("H"); // kNtVkHome
|
|
} else if ((ismouse |= IsMouseModeCommand(x))) {
|
|
__ttyconf.magic &= ~kTtyXtMouse;
|
|
cm2 |= kNtEnableQuickEditMode; // release mouse
|
|
}
|
|
t = ASC;
|
|
}
|
|
break;
|
|
default:
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
if (cm2 != cm)
|
|
SetConsoleMode(GetConsoleInputHandle(), cm2);
|
|
}
|
|
|
|
textwindows static bool DigestConsoleInput(char *data, size_t size, int *rc) {
|
|
|
|
// handle eof once available input is consumed
|
|
if (dll_is_empty(__keystroke.list) && __keystroke.end_of_file) {
|
|
*rc = 0;
|
|
return true;
|
|
}
|
|
|
|
// copy keystroke(s) into user buffer
|
|
int toto = 0;
|
|
struct Dll *e;
|
|
while (size && (e = dll_first(__keystroke.list))) {
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
uint32_t got = MIN(size, k->buflen);
|
|
uint32_t remain = k->buflen - got;
|
|
if (got) {
|
|
memcpy(data, k->buf, got);
|
|
data += got;
|
|
size -= got;
|
|
toto += got;
|
|
}
|
|
if (remain) {
|
|
memmove(k->buf, k->buf + got, remain);
|
|
k->buflen = remain;
|
|
} else {
|
|
FreeKeystroke(&__keystroke.list, e);
|
|
}
|
|
if ((__ttyconf.magic & kTtyUncanon) && toto >= __ttyconf.vmin)
|
|
break;
|
|
}
|
|
|
|
// return result
|
|
if (toto) {
|
|
*rc = toto;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
textwindows static uint32_t DisableProcessedInput(void) {
|
|
// the time has come to ensure that ctrl-v ctrl-c works in icanon mode
|
|
// we're perfectly capable of generating a SIGINT or SIGQUIT ourselves
|
|
// while the cosmo termios driver is in control; so we disable windows
|
|
// console input processing for now; we'll turn it back on when we are
|
|
// done, since it's useful for ensuring asynchronous signal deliveries
|
|
uint32_t inmode = 0;
|
|
if (GetConsoleMode(__keystroke.cin, &inmode))
|
|
if (inmode & kNtEnableProcessedInput)
|
|
SetConsoleMode(__keystroke.cin, inmode & ~kNtEnableProcessedInput);
|
|
return inmode;
|
|
}
|
|
|
|
textwindows static void RestoreProcessedInput(uint32_t inmode) {
|
|
// re-enable win32 console input processing, if it was enabled when we
|
|
// started, and no signal handler callbacks changed things in-between.
|
|
if (inmode & kNtEnableProcessedInput) {
|
|
uint32_t inmode2;
|
|
if (GetConsoleMode(__keystroke.cin, &inmode2))
|
|
if (inmode2 == (inmode & ~kNtEnableProcessedInput))
|
|
SetConsoleMode(__keystroke.cin, inmode);
|
|
}
|
|
}
|
|
|
|
textwindows static int CountConsoleInputBytesBlockingImpl(uint32_t ms,
|
|
sigset_t waitmask,
|
|
bool restartable) {
|
|
InitConsole();
|
|
struct timespec deadline =
|
|
timespec_add(sys_clock_gettime_monotonic_nt(), timespec_frommillis(ms));
|
|
for (;;) {
|
|
int sig = 0;
|
|
intptr_t sev;
|
|
if (!(sev = CreateEvent(0, 0, 0, 0)))
|
|
return __winerr();
|
|
struct PosixThread *pt = _pthread_self();
|
|
pt->pt_event = sev;
|
|
pt->pt_blkmask = waitmask;
|
|
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT,
|
|
memory_order_release);
|
|
if (_check_cancel() == -1) {
|
|
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
|
|
CloseHandle(sev);
|
|
return -1;
|
|
}
|
|
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
|
|
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
|
|
CloseHandle(sev);
|
|
goto DeliverSignal;
|
|
}
|
|
struct timespec now = sys_clock_gettime_monotonic_nt();
|
|
struct timespec remain = timespec_subz(deadline, now);
|
|
int64_t millis = timespec_tomillis(remain);
|
|
uint32_t waitms = MIN(millis, 0xffffffffu);
|
|
intptr_t hands[] = {__keystroke.cin, sev};
|
|
uint32_t wi = WaitForMultipleObjects(2, hands, 0, waitms);
|
|
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
|
|
CloseHandle(sev);
|
|
if (wi == -1u)
|
|
return __winerr();
|
|
|
|
// check for wait timeout
|
|
if (wi == kNtWaitTimeout)
|
|
return etimedout();
|
|
|
|
// handle event on console handle. this means we can now read from the
|
|
// conosle without blocking. so the first thing we do is slurp up your
|
|
// keystroke data. some of those keystrokes might cause a signal to be
|
|
// raised. so we need to check for pending signals again and handle it
|
|
if (wi == 0) {
|
|
int got = CountConsoleInputBytes();
|
|
// we might have read a keystroke that generated a signal
|
|
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))
|
|
goto DeliverSignal;
|
|
if (got == -1)
|
|
// this is a bona fide eof and console errors are logged to strace
|
|
return 0;
|
|
if (got == 0)
|
|
// this can happen for multiple reasons. first our driver controls
|
|
// user interactions in canonical mode. secondly we could lose the
|
|
// race with another thread that's reading input.
|
|
continue;
|
|
return got;
|
|
}
|
|
|
|
if (wi == 1 && _weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
|
|
// handle event on throwaway semaphore, it is poked by signal delivery
|
|
DeliverSignal:;
|
|
int handler_was_called = 0;
|
|
do {
|
|
handler_was_called |= _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
|
|
} while ((sig = _weaken(__sig_get)(waitmask)));
|
|
if (_check_cancel() == -1)
|
|
return -1;
|
|
if (handler_was_called & SIG_HANDLED_NO_RESTART)
|
|
return eintr();
|
|
if (handler_was_called & SIG_HANDLED_SA_RESTART)
|
|
if (!restartable)
|
|
return eintr();
|
|
}
|
|
}
|
|
}
|
|
|
|
textwindows static int CountConsoleInputBytesBlocking(uint32_t ms,
|
|
sigset_t waitmask) {
|
|
int got = CountConsoleInputBytes();
|
|
if (got == -1)
|
|
return 0;
|
|
if (got > 0)
|
|
return got;
|
|
uint32_t inmode = DisableProcessedInput();
|
|
int rc = CountConsoleInputBytesBlockingImpl(ms, waitmask, true);
|
|
RestoreProcessedInput(inmode);
|
|
return rc;
|
|
}
|
|
|
|
textwindows static int WaitToReadFromConsole(struct Fd *f, sigset_t waitmask) {
|
|
uint32_t ms = -1;
|
|
if (!__ttyconf.vmin) {
|
|
if (!__ttyconf.vtime) {
|
|
return 0; // non-blocking w/o raising eagain
|
|
} else {
|
|
ms = __ttyconf.vtime * 100;
|
|
}
|
|
}
|
|
if (f->flags & _O_NONBLOCK)
|
|
return eagain();
|
|
int olderr = errno;
|
|
int rc = CountConsoleInputBytesBlockingImpl(ms, waitmask, true);
|
|
if (rc == -1 && errno == ETIMEDOUT) {
|
|
// read() never raises ETIMEDOUT so if vtime elapses we raise an EOF
|
|
errno = olderr;
|
|
rc = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
textwindows static ssize_t ReadFromConsole(struct Fd *f, void *data,
|
|
size_t size, sigset_t waitmask) {
|
|
int rc;
|
|
InitConsole();
|
|
uint32_t inmode = DisableProcessedInput();
|
|
do {
|
|
LockKeystrokes();
|
|
IngestConsoleInput();
|
|
bool done = DigestConsoleInput(data, size, &rc);
|
|
UnlockKeystrokes();
|
|
if (done)
|
|
break;
|
|
} while ((rc = WaitToReadFromConsole(f, waitmask)) > 0);
|
|
RestoreProcessedInput(inmode);
|
|
return rc;
|
|
}
|
|
|
|
textwindows ssize_t ReadBuffer(int fd, void *data, size_t size, int64_t offset,
|
|
sigset_t waitmask) {
|
|
|
|
// switch to terminal polyfill if reading from win32 console
|
|
struct Fd *f = g_fds.p + fd;
|
|
|
|
if (f->kind == kFdDevNull)
|
|
return 0;
|
|
|
|
if (f->kind == kFdDevRandom)
|
|
return ProcessPrng(data, size) ? size : __winerr();
|
|
|
|
if (f->kind == kFdConsole)
|
|
return ReadFromConsole(f, data, size, waitmask);
|
|
|
|
// perform heavy lifting
|
|
ssize_t rc;
|
|
rc = sys_readwrite_nt(fd, data, size, offset, f->handle, waitmask, ReadFile);
|
|
if (rc != -2)
|
|
return rc;
|
|
|
|
// mops up win32 errors
|
|
switch (GetLastError()) {
|
|
case kNtErrorBrokenPipe: // broken pipe
|
|
case kNtErrorNoData: // closing named pipe
|
|
case kNtErrorHandleEof: // pread read past EOF
|
|
return 0; //
|
|
case kNtErrorAccessDenied: // read doesn't return EACCESS
|
|
return ebadf(); //
|
|
default:
|
|
return __winerr();
|
|
}
|
|
}
|
|
|
|
textwindows static ssize_t ReadIovecs(int fd, const struct iovec *iov,
|
|
size_t iovlen, int64_t opt_offset,
|
|
sigset_t waitmask) {
|
|
ssize_t rc;
|
|
size_t i, total;
|
|
if (opt_offset < -1)
|
|
return einval();
|
|
while (iovlen && !iov[0].iov_len)
|
|
iov++, iovlen--;
|
|
if (iovlen) {
|
|
for (total = i = 0; i < iovlen; ++i) {
|
|
if (!iov[i].iov_len)
|
|
continue;
|
|
rc =
|
|
ReadBuffer(fd, iov[i].iov_base, iov[i].iov_len, opt_offset, waitmask);
|
|
if (rc == -1) {
|
|
if (total && errno != ECANCELED) {
|
|
return total;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
total += rc;
|
|
if (opt_offset != -1)
|
|
opt_offset += rc;
|
|
if (rc < iov[i].iov_len)
|
|
break;
|
|
}
|
|
return total;
|
|
} else {
|
|
return ReadBuffer(fd, NULL, 0, opt_offset, waitmask);
|
|
}
|
|
}
|
|
|
|
textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen,
|
|
int64_t opt_offset) {
|
|
ssize_t rc;
|
|
sigset_t m = __sig_block();
|
|
rc = ReadIovecs(fd, iov, iovlen, opt_offset, m);
|
|
__sig_unblock(m);
|
|
return rc;
|
|
}
|
|
|
|
#endif /* __x86_64__ */
|