mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-01 12:03:41 +00:00
748 lines
22 KiB
C
748 lines
22 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net 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/calls/internal.h"
|
|
#include "libc/calls/sig.internal.h"
|
|
#include "libc/calls/struct/fd.internal.h"
|
|
#include "libc/calls/struct/iovec.h"
|
|
#include "libc/calls/syscall_support-nt.internal.h"
|
|
#include "libc/calls/ttydefaults.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/fmt/itoa.h"
|
|
#include "libc/intrin/atomic.h"
|
|
#include "libc/intrin/dll.h"
|
|
#include "libc/intrin/kprintf.h"
|
|
#include "libc/intrin/nomultics.internal.h"
|
|
#include "libc/intrin/strace.internal.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/macros.internal.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/nt/console.h"
|
|
#include "libc/nt/enum/consolemodeflags.h"
|
|
#include "libc/nt/enum/filetype.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/files.h"
|
|
#include "libc/nt/runtime.h"
|
|
#include "libc/nt/struct/inputrecord.h"
|
|
#include "libc/nt/synchronization.h"
|
|
#include "libc/nt/thread.h"
|
|
#include "libc/nt/thunk/msabi.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/str/utf16.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "libc/sysv/consts/termios.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
#include "libc/thread/thread.h"
|
|
#include "libc/thread/tls.h"
|
|
#ifdef __x86_64__
|
|
|
|
static const struct {
|
|
int vk;
|
|
int normal_str;
|
|
int shift_str;
|
|
int ctrl_str;
|
|
int shift_ctrl_str;
|
|
} kVirtualKey[] = {
|
|
#define S(s) W(s "\0\0")
|
|
#define W(s) (s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
|
|
{kNtVkUp, S("A"), S("1;2A"), S("1;5A"), S("1;6A")},
|
|
{kNtVkDown, S("B"), S("1;2B"), S("1;5B"), S("1;6B")},
|
|
{kNtVkLeft, S("D"), S("1;2D"), S("1;5D"), S("1;6D")},
|
|
{kNtVkRight, S("C"), S("1;2C"), S("1;5C"), S("1;6C")},
|
|
{kNtVkInsert, S("2~"), S("2;2~"), S("2;5~"), S("2;6~")},
|
|
{kNtVkDelete, S("3~"), S("3;2~"), S("3;5~"), S("3;6~")},
|
|
{kNtVkHome, S("H"), S("1;2H"), S("1;5H"), S("1;6H")},
|
|
{kNtVkEnd, S("F"), S("1;2F"), S("1;5F"), S("1;6F")},
|
|
{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@")},
|
|
#undef W
|
|
#undef S
|
|
};
|
|
|
|
#define KEYSTROKE_CONTAINER(e) DLL_CONTAINER(struct Keystroke, elem, e)
|
|
|
|
struct Keystroke {
|
|
char buf[23];
|
|
unsigned char buflen;
|
|
struct Dll elem;
|
|
};
|
|
|
|
struct Keystrokes {
|
|
struct Dll *list;
|
|
struct Dll *line;
|
|
struct Dll *free;
|
|
bool end_of_file;
|
|
unsigned char pc;
|
|
uint16_t utf16hs;
|
|
pthread_mutex_t lock;
|
|
struct Keystroke pool[512];
|
|
};
|
|
|
|
static struct Keystrokes __keystroke;
|
|
|
|
static textwindows void LockKeystrokes(void) {
|
|
pthread_mutex_lock(&__keystroke.lock);
|
|
}
|
|
|
|
static textwindows void UnlockKeystrokes(void) {
|
|
pthread_mutex_unlock(&__keystroke.lock);
|
|
}
|
|
|
|
static textwindows uint64_t BlockSignals(void) {
|
|
return atomic_exchange(&__get_tls()->tib_sigmask, -1);
|
|
}
|
|
|
|
static textwindows void UnblockSignals(uint64_t mask) {
|
|
atomic_store_explicit(&__get_tls()->tib_sigmask, mask, memory_order_release);
|
|
}
|
|
|
|
static textwindows int RaiseSignal(int sig) {
|
|
__get_tls()->tib_sigpending |= 1ull << (sig - 1);
|
|
return 0;
|
|
}
|
|
|
|
static textwindows int GetVirtualKey(uint16_t vk, bool shift, bool ctrl) {
|
|
for (int i = 0; i < ARRAYLEN(kVirtualKey); ++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;
|
|
}
|
|
|
|
static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p) {
|
|
|
|
uint16_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;
|
|
}
|
|
|
|
// process arrow keys, function keys, etc.
|
|
int n = 0;
|
|
if (!c) {
|
|
int w;
|
|
w = GetVirtualKey(vk, !!(cks & kNtShiftPressed),
|
|
!!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)));
|
|
if (!w) return 0;
|
|
p[n++] = 033;
|
|
if (cks & (kNtLeftAltPressed | kNtRightAltPressed)) {
|
|
p[n++] = 033;
|
|
}
|
|
if (w > 0) {
|
|
p[n++] = '[';
|
|
} else {
|
|
w = -w;
|
|
}
|
|
do p[n++] = w;
|
|
while ((w >>= 8));
|
|
return n;
|
|
}
|
|
|
|
// translate utf-16 into utf-32
|
|
if (IsHighSurrogate(c)) {
|
|
__keystroke.utf16hs = c;
|
|
return 0;
|
|
}
|
|
if (IsLowSurrogate(c)) {
|
|
c = MergeUtf16(__keystroke.utf16hs, c);
|
|
}
|
|
|
|
// enter sends \r in a raw terminals
|
|
// make it a multics newline instead
|
|
if (c == '\r' && !(__ttyconf.magic & kTtyNoCr2Nl)) {
|
|
c = '\n';
|
|
}
|
|
|
|
// microsoft doesn't encode ctrl-space (^@) as nul
|
|
// detecting it is also impossible w/ kNtEnableVirtualTerminalInput
|
|
if (c == ' ' && (cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
c = '\0';
|
|
}
|
|
|
|
// make it possible to distinguish ctrl-h (^H) from backspace (^?)
|
|
if (c == kNtVkBack && !(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed))) {
|
|
c = 0177;
|
|
}
|
|
|
|
// handle ctrl-c and ctrl-\, which tcsetattr() is able to remap
|
|
if (c && !(__ttyconf.magic & kTtyNoIsigs)) {
|
|
if (c == __ttyconf.vintr) {
|
|
return RaiseSignal(SIGINT);
|
|
} else if (c == __ttyconf.vquit) {
|
|
return RaiseSignal(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 && !(__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
|
|
if ((cks & (kNtLeftAltPressed | kNtRightAltPressed)) &&
|
|
!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)) &&
|
|
r->Event.KeyEvent.bKeyDown) {
|
|
p[n++] = 033;
|
|
}
|
|
|
|
// convert utf-32 to utf-8
|
|
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.com and o//tool/viz/life.com
|
|
static textwindows 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)
|
|
if (!(r->Event.MouseEvent.dwControlKeyState &
|
|
(kNtShiftPressed | kNtLeftCtrlPressed | kNtRightCtrlPressed |
|
|
kNtLeftAltPressed | kNtRightAltPressed))) {
|
|
bool isup = ((int)r->Event.MouseEvent.dwButtonState >> 16) > 0;
|
|
if (__ttyconf.magic & kTtyXtMouse) {
|
|
e = isup ? 80 : 81;
|
|
goto OutputXtermMouseEvent;
|
|
} else {
|
|
// we disable mouse highlighting when the tty is put in raw mode
|
|
// to mouse wheel events with widely understood vt100 arrow keys
|
|
*p++ = 033;
|
|
*p++ = '[';
|
|
if (isup) {
|
|
*p++ = 'A'; // \e[A up
|
|
} else {
|
|
*p++ = 'B'; // \e[B down
|
|
}
|
|
}
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
static textwindows 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 RaiseSignal(SIGWINCH);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static textwindows struct Keystroke *NewKeystroke(void) {
|
|
struct Dll *e;
|
|
struct Keystroke *k = 0;
|
|
int i, n = ARRAYLEN(__keystroke.pool);
|
|
if (atomic_load_explicit(&__keystroke.pc, memory_order_acquire) < n &&
|
|
(i = atomic_fetch_add(&__keystroke.pc, 1)) < n) {
|
|
k = __keystroke.pool + i;
|
|
} else {
|
|
if ((e = dll_first(__keystroke.free))) {
|
|
k = KEYSTROKE_CONTAINER(e);
|
|
dll_remove(&__keystroke.free, &k->elem);
|
|
}
|
|
if (!k) {
|
|
if (_weaken(malloc)) {
|
|
k = _weaken(malloc)(sizeof(struct Keystroke));
|
|
} else {
|
|
enomem();
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if (k) {
|
|
bzero(k, sizeof(*k));
|
|
dll_init(&k->elem);
|
|
}
|
|
return k;
|
|
}
|
|
|
|
static textwindows void WriteTty(struct Fd *f, const char *p, size_t n) {
|
|
int64_t hOutput;
|
|
uint32_t dwConsoleMode;
|
|
if (f->kind == kFdConsole) {
|
|
hOutput = f->extra;
|
|
} else if (g_fds.p[1].kind == kFdFile &&
|
|
GetConsoleMode(g_fds.p[1].handle, &dwConsoleMode)) {
|
|
hOutput = g_fds.p[1].handle;
|
|
} else if (g_fds.p[2].kind == kFdFile &&
|
|
GetConsoleMode(g_fds.p[2].handle, &dwConsoleMode)) {
|
|
hOutput = g_fds.p[2].handle;
|
|
} else {
|
|
hOutput = g_fds.p[1].handle;
|
|
}
|
|
WriteFile(hOutput, p, n, 0, 0);
|
|
}
|
|
|
|
static textwindows bool IsCtl(int c) {
|
|
return isascii(c) && iscntrl(c) && c != '\n' && c != '\t';
|
|
}
|
|
|
|
static textwindows void WriteTtyCtl(struct Fd *f, const char *p, size_t n) {
|
|
size_t i;
|
|
for (i = 0; i < n; ++i) {
|
|
if (IsCtl(p[i])) {
|
|
char ctl[2];
|
|
ctl[0] = '^';
|
|
ctl[1] = p[i] ^ 0100;
|
|
WriteTty(f, ctl, 2);
|
|
} else {
|
|
WriteTty(f, p + i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static textwindows void EchoTty(struct Fd *f, const char *p, size_t n) {
|
|
if (__ttyconf.magic & kTtyEchoRaw) {
|
|
WriteTty(f, p, n);
|
|
} else {
|
|
WriteTtyCtl(f, p, n);
|
|
}
|
|
}
|
|
|
|
static textwindows bool EraseKeystroke(struct Fd *f) {
|
|
struct Dll *e;
|
|
if ((e = dll_last(__keystroke.line))) {
|
|
struct Keystroke *k = KEYSTROKE_CONTAINER(e);
|
|
dll_remove(&__keystroke.line, e);
|
|
dll_make_first(&__keystroke.free, e);
|
|
for (int i = k->buflen; i--;) {
|
|
if ((k->buf[i] & 0300) == 0200) continue;
|
|
WriteTty(f, "\b \b", 3);
|
|
if (!(__ttyconf.magic & kTtyEchoRaw) && IsCtl(k->buf[i])) {
|
|
WriteTty(f, "\b \b", 3);
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static textwindows void IngestConsoleInputRecord(struct Fd *f,
|
|
struct NtInputRecord *r) {
|
|
|
|
// convert win32 console event into ansi
|
|
int len;
|
|
char buf[23];
|
|
if (!(len = ConvertConsoleInputToAnsi(r, buf))) {
|
|
return;
|
|
}
|
|
|
|
// handle backspace in canonical mode
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.verase && //
|
|
!(__ttyconf.magic & kTtyUncanon)) {
|
|
EraseKeystroke(f);
|
|
return;
|
|
}
|
|
|
|
// handle kill in canonical mode
|
|
if (len == 1 && buf[0] && //
|
|
(buf[0] & 255) == __ttyconf.vkill && //
|
|
!(__ttyconf.magic & kTtyUncanon)) {
|
|
while (EraseKeystroke(f)) {
|
|
}
|
|
return;
|
|
}
|
|
|
|
// allocate an object to hold this keystroke
|
|
struct Keystroke *k;
|
|
if (!(k = NewKeystroke())) {
|
|
STRACE("ran out of memory to hold keystroke %#.*s", len, buf);
|
|
return;
|
|
}
|
|
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
|
|
if (!(__ttyconf.magic & kTtySilence)) {
|
|
EchoTty(f, buf, len);
|
|
}
|
|
|
|
// save keystroke to appropriate list
|
|
if (__ttyconf.magic & kTtyUncanon) {
|
|
dll_make_last(&__keystroke.list, &k->elem);
|
|
} else {
|
|
dll_make_last(&__keystroke.line, &k->elem);
|
|
|
|
// handle end-of-line in canonical mode
|
|
if (len == 1 && buf[0] &&
|
|
((buf[0] & 255) == '\n' || //
|
|
(buf[0] & 255) == __ttyconf.veol || //
|
|
(buf[0] & 255) == __ttyconf.veol2)) {
|
|
dll_make_last(&__keystroke.list, __keystroke.line);
|
|
__keystroke.line = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static textwindows void IngestConsoleInput(struct Fd *f) {
|
|
uint32_t i, n;
|
|
struct NtInputRecord records[16];
|
|
if (!__keystroke.end_of_file) {
|
|
do {
|
|
if (GetNumberOfConsoleInputEvents(f->handle, &n)) {
|
|
if (n) {
|
|
n = MIN(ARRAYLEN(records), n);
|
|
if (ReadConsoleInput(f->handle, records, n, &n)) {
|
|
for (i = 0; i < n && !__keystroke.end_of_file; ++i) {
|
|
IngestConsoleInputRecord(f, records + i);
|
|
}
|
|
} else {
|
|
STRACE("ReadConsoleInput failed w/ %d", GetLastError());
|
|
__keystroke.end_of_file = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
STRACE("GetNumberOfConsoleInputRecords failed w/ %d", GetLastError());
|
|
__keystroke.end_of_file = true;
|
|
break;
|
|
}
|
|
} while (n == ARRAYLEN(records));
|
|
}
|
|
}
|
|
|
|
textwindows int FlushConsoleInputBytes(int64_t handle) {
|
|
int rc;
|
|
uint64_t m;
|
|
m = BlockSignals();
|
|
LockKeystrokes();
|
|
if (FlushConsoleInputBuffer(handle)) {
|
|
dll_make_first(&__keystroke.free, __keystroke.list);
|
|
__keystroke.list = 0;
|
|
dll_make_first(&__keystroke.free, __keystroke.line);
|
|
__keystroke.line = 0;
|
|
rc = 0;
|
|
} else {
|
|
rc = __winerr();
|
|
}
|
|
UnlockKeystrokes();
|
|
UnblockSignals(m);
|
|
return rc;
|
|
}
|
|
|
|
textwindows int CountConsoleInputBytes(struct Fd *f) {
|
|
int count = 0;
|
|
struct Dll *e;
|
|
uint64_t m = BlockSignals();
|
|
LockKeystrokes();
|
|
IngestConsoleInput(f);
|
|
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();
|
|
UnblockSignals(m);
|
|
return count;
|
|
}
|
|
|
|
static textwindows 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);
|
|
} else {
|
|
dll_remove(&__keystroke.list, e);
|
|
dll_make_first(&__keystroke.free, e);
|
|
}
|
|
k->buflen = remain;
|
|
if ((__ttyconf.magic & kTtyUncanon) && toto >= __ttyconf.vmin) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// return result
|
|
if (toto) {
|
|
*rc = toto;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static textwindows ssize_t ReadFromWindowsConsole(struct Fd *f, void *data,
|
|
size_t size) {
|
|
int rc = -1;
|
|
for (;;) {
|
|
bool done = false;
|
|
uint64_t m;
|
|
m = BlockSignals();
|
|
LockKeystrokes();
|
|
IngestConsoleInput(f);
|
|
done = DigestConsoleInput(data, size, &rc);
|
|
UnlockKeystrokes();
|
|
UnblockSignals(m);
|
|
if (done) break;
|
|
if (f->flags & O_NONBLOCK) return eagain();
|
|
uint32_t ms = __SIG_POLL_INTERVAL_MS;
|
|
if (!__ttyconf.vmin) {
|
|
if (!__ttyconf.vtime) {
|
|
return 0;
|
|
} else {
|
|
ms = __ttyconf.vtime * 100;
|
|
}
|
|
}
|
|
if (_check_interrupts(kSigOpRestartable)) return -1;
|
|
if (__pause_thread(ms)) {
|
|
if (errno == EAGAIN) {
|
|
errno = EINTR; // TODO(jart): Why do we need it?
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
|
|
int64_t offset) {
|
|
|
|
bool32 ok;
|
|
struct Fd *f;
|
|
uint32_t got;
|
|
int64_t handle;
|
|
struct PosixThread *pt;
|
|
|
|
f = g_fds.p + fd;
|
|
handle = f->handle;
|
|
pt = _pthread_self();
|
|
pt->abort_errno = EAGAIN;
|
|
size = MIN(size, 0x7ffff000);
|
|
|
|
bool pwriting = offset != -1;
|
|
bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
|
|
bool nonblock = !!(f->flags & O_NONBLOCK);
|
|
|
|
if (pwriting && !seekable) {
|
|
return espipe();
|
|
}
|
|
if (!pwriting) {
|
|
offset = 0;
|
|
}
|
|
|
|
uint32_t cm;
|
|
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
|
|
return ReadFromWindowsConsole(f, data, size);
|
|
}
|
|
|
|
if (!pwriting && seekable) {
|
|
pthread_mutex_lock(&f->lock);
|
|
offset = f->pointer;
|
|
}
|
|
|
|
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
|
|
.Pointer = offset};
|
|
// the win32 manual says it's important to *not* put &got here
|
|
// since for overlapped i/o, we always use GetOverlappedResult
|
|
ok = ReadFile(handle, data, size, 0, &overlap);
|
|
if (!ok && GetLastError() == kNtErrorIoPending) {
|
|
BlockingOperation:
|
|
if (!nonblock) {
|
|
pt->ioverlap = &overlap;
|
|
pt->iohandle = handle;
|
|
}
|
|
if (nonblock) {
|
|
CancelIoEx(handle, &overlap);
|
|
} else if (_check_interrupts(kSigOpRestartable)) {
|
|
Interrupted:
|
|
pt->abort_errno = errno;
|
|
CancelIoEx(handle, &overlap);
|
|
} else {
|
|
for (;;) {
|
|
uint32_t i;
|
|
if (g_fds.stdin.inisem) {
|
|
ReleaseSemaphore(g_fds.stdin.inisem, 1, 0);
|
|
}
|
|
i = WaitForSingleObject(overlap.hEvent, __SIG_IO_INTERVAL_MS);
|
|
if (i == kNtWaitTimeout) {
|
|
if (_check_interrupts(kSigOpRestartable)) {
|
|
goto Interrupted;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pt->ioverlap = 0;
|
|
pt->iohandle = 0;
|
|
ok = true;
|
|
}
|
|
if (ok) {
|
|
// overlapped is allocated on stack, so it's important we wait
|
|
// for windows to acknowledge that it's done using that memory
|
|
ok = GetOverlappedResult(handle, &overlap, &got, nonblock);
|
|
if (!ok && GetLastError() == kNtErrorIoIncomplete) {
|
|
goto BlockingOperation;
|
|
}
|
|
}
|
|
CloseHandle(overlap.hEvent);
|
|
|
|
if (!pwriting && seekable) {
|
|
if (ok) f->pointer = offset + got;
|
|
pthread_mutex_unlock(&f->lock);
|
|
}
|
|
|
|
if (ok) {
|
|
return got;
|
|
}
|
|
|
|
errno_t err;
|
|
if (_weaken(pthread_testcancel_np) &&
|
|
(err = _weaken(pthread_testcancel_np)())) {
|
|
return ecanceled();
|
|
}
|
|
|
|
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(); //
|
|
case kNtErrorOperationAborted:
|
|
errno = pt->abort_errno;
|
|
return -1;
|
|
default:
|
|
return __winerr();
|
|
}
|
|
}
|
|
|
|
textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen,
|
|
int64_t opt_offset) {
|
|
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) {
|
|
// TODO(jart): disable cancelations after first iteration
|
|
if (!iov[i].iov_len) continue;
|
|
rc = sys_read_nt_impl(fd, iov[i].iov_base, iov[i].iov_len, opt_offset);
|
|
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 sys_read_nt_impl(fd, NULL, 0, opt_offset);
|
|
}
|
|
}
|
|
|
|
#endif /* __x86_64__ */
|