Rewrite Windows console input handling

This change removes our use of ENABLE_VIRTUAL_TERMINAL_INPUT (which
isn't very good) in favor of having read() translate Windows Console
input events to ANSI/XTERM sequences by hand. This makes it possible to
capture important keystrokes (e.g. ctrl-space) that weren't possible
before. Most importantly this change also removes the stdin/sigwinch
worker threads, which never really worked that well. Interactive TTY
sessions will now work reliably when a Cosmo process spawns or forks
another Cosmo process, e.g. unbourne.com launching emacs.com.
This commit is contained in:
Justine Tunney 2023-09-19 11:42:38 -07:00
parent ececec4c94
commit d6c2830850
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
27 changed files with 635 additions and 464 deletions

View file

@ -0,0 +1,14 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_
#include "libc/calls/struct/fd.internal.h"
#include "libc/nt/struct/inputrecord.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
int CountConsoleInputBytes(int64_t);
int ConvertConsoleInputToAnsi(const struct NtInputRecord *, char[hasatleast 32],
uint16_t *, struct Fd *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_CONSOLE_INTERNAL_H_ */

View file

@ -16,17 +16,41 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/intrin/atomic.h"
#include "libc/nt/synchronization.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/nt/console.h"
#include "libc/nt/struct/inputrecord.h"
#ifdef __x86_64__
textwindows int64_t __resolve_stdin_handle(int64_t handle) {
if (handle == g_fds.stdin.handle) {
if (g_fds.stdin.inisem) {
ReleaseSemaphore(g_fds.stdin.inisem, 1, 0);
int CountConsoleInputBytes(int64_t handle) {
char buf[32];
int rc, e = errno;
uint16_t utf16hs = 0;
uint32_t i, n, count;
struct NtInputRecord records[64];
if (PeekConsoleInput(handle, records, ARRAYLEN(records), &n)) {
for (rc = i = 0; i < n; ++i) {
count = ConvertConsoleInputToAnsi(records + i, buf, &utf16hs, 0);
if (count == -1) {
unassert(errno == ENODATA);
if (!rc) {
rc = -1;
} else {
errno = e;
}
break;
}
rc += count;
}
handle = g_fds.stdin.reader;
} else {
rc = __winerr();
}
return handle;
return rc;
}
#endif /* __x86_64__ */

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
@ -31,6 +32,7 @@
#include "libc/macros.internal.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
@ -54,10 +56,6 @@
#include "libc/sysv/consts/termios.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
__static_yoink("WinMainStdin");
#endif
/* Maximum number of unicast addresses handled for each interface */
#define MAX_UNICAST_ADDR 32
#define MAX_NAME_CLASH ((int)('z' - 'a')) /* Allow a..z */
@ -93,13 +91,14 @@ static int ioctl_default(int fd, unsigned long request, void *arg) {
}
static int ioctl_fionread(int fd, uint32_t *arg) {
int rc;
uint32_t cm;
int64_t handle;
uint32_t avail;
int rc, e = errno;
if (!IsWindows()) {
return sys_ioctl(fd, FIONREAD, arg);
} else if (__isfdopen(fd)) {
handle = __resolve_stdin_handle(g_fds.p[fd].handle);
handle = g_fds.p[fd].handle;
if (g_fds.p[fd].kind == kFdSocket) {
if ((rc = _weaken(__sys_ioctlsocket_nt)(handle, FIONREAD, arg)) != -1) {
return rc;
@ -113,6 +112,13 @@ static int ioctl_fionread(int fd, uint32_t *arg) {
} else {
return __winerr();
}
} else if (GetConsoleMode(handle, &cm)) {
avail = CountConsoleInputBytes(handle);
if (avail == -1u && errno == ENODATA) {
errno = e;
avail = 0;
}
return avail;
} else {
return eopnotsupp();
}

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
@ -26,6 +27,7 @@
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
@ -47,8 +49,6 @@
#ifdef __x86_64__
__static_yoink("WinMainStdin");
/*
* Polls on the New Technology.
*
@ -59,8 +59,8 @@ __static_yoink("WinMainStdin");
textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
const sigset_t *sigmask) {
bool ok;
uint32_t avail;
sigset_t oldmask;
uint32_t avail, cm;
struct sys_pollfd_nt pipefds[8];
struct sys_pollfd_nt sockfds[64];
int pipeindices[ARRAYLEN(pipefds)];
@ -98,7 +98,7 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
}
} else if (pn < ARRAYLEN(pipefds)) {
pipeindices[pn] = i;
pipefds[pn].handle = __resolve_stdin_handle(g_fds.p[fds[i].fd].handle);
pipefds[pn].handle = g_fds.p[fds[i].fd].handle;
pipefds[pn].events = 0;
pipefds[pn].revents = 0;
switch (g_fds.p[fds[i].fd].flags & O_ACCMODE) {
@ -151,6 +151,19 @@ textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint64_t *ms,
} else {
pipefds[i].revents |= POLLERR;
}
} else if (GetConsoleMode(pipefds[i].handle, &cm)) {
int e = errno;
avail = CountConsoleInputBytes(pipefds[i].handle);
if (avail > 0) {
pipefds[i].revents |= POLLIN;
} else if (avail == -1u) {
if (errno == ENODATA) {
pipefds[i].revents |= POLLIN;
} else {
pipefds[i].revents |= POLLERR;
}
errno = e;
}
} else {
// we have no way of polling if a non-socket is readable yet
// therefore we assume that if it can happen it shall happen

View file

@ -17,32 +17,39 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nt/console.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/ipc.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/inputrecord.h"
#include "libc/nt/struct/overlapped.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/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/termios.h"
@ -50,33 +57,385 @@
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
__static_yoink("WinMainStdin");
#ifdef __x86_64__
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
static const struct {
uint16_t vk;
uint32_t normal_str;
uint32_t shift_str;
uint32_t ctrl_str;
uint32_t shift_ctrl_str;
} kVirtualKey[] = {
#define SW(s) W4(s "\0\0")
#define W4(s) (s[3] + 0u) << 24 | s[2] << 16 | s[1] << 8 | s[0]
#define VK(vk, normal_str, shift_str, ctrl_str, shift_ctrl_str) \
{ vk, SW(normal_str), SW(shift_str), SW(ctrl_str), SW(shift_ctrl_str) }
VK(kNtVkInsert, "2~", "2;2~", "2;5~", "2;6~"),
VK(kNtVkEnd, "4~", "4;2~", "4;5~", "4;6~"),
VK(kNtVkDown, "B", "1;2B", "1;5B", "1;6B"),
VK(kNtVkNext, "6~", "6;2~", "6;5~", "6;6~"),
VK(kNtVkLeft, "D", "1;2D", "1;5D", "1;6D"),
VK(kNtVkClear, "G", "1;2G", "1;5G", "1;6G"),
VK(kNtVkRight, "C", "1;2C", "1;5C", "1;6C"),
VK(kNtVkUp, "A", "1;2A", "1;5A", "1;6A"),
VK(kNtVkHome, "1~", "1;2~", "1;5~", "1;6~"),
VK(kNtVkPrior, "5~", "5;2~", "5;5~", "5;6~"),
VK(kNtVkDelete, "3~", "3;2~", "3;5~", "3;6~"),
VK(kNtVkNumpad0, "2~", "2;2~", "2;5~", "2;6~"),
VK(kNtVkNumpad1, "4~", "4;2~", "4;5~", "4;6~"),
VK(kNtVkNumpad2, "B", "1;2B", "1;5B", "1;6B"),
VK(kNtVkNumpad3, "6~", "6;2~", "6;5~", "6;6~"),
VK(kNtVkNumpad4, "D", "1;2D", "1;5D", "1;6D"),
VK(kNtVkNumpad5, "G", "1;2G", "1;5G", "1;6G"),
VK(kNtVkNumpad6, "C", "1;2C", "1;5C", "1;6C"),
VK(kNtVkNumpad7, "A", "1;2A", "1;5A", "1;6A"),
VK(kNtVkNumpad8, "1~", "1;2~", "1;5~", "1;6~"),
VK(kNtVkNumpad9, "5~", "5;2~", "5;5~", "5;6~"),
VK(kNtVkDecimal, "3~", "3;2~", "3;5~", "3;6~"),
VK(kNtVkF1, "[A", "23~", "11^", "23^"),
VK(kNtVkF2, "[B", "24~", "12^", "24^"),
VK(kNtVkF3, "[C", "25~", "13^", "25^"),
VK(kNtVkF4, "[D", "26~", "14^", "26^"),
VK(kNtVkF5, "[E", "28~", "15^", "28^"),
VK(kNtVkF6, "17~", "29~", "17^", "29^"),
VK(kNtVkF7, "18~", "31~", "18^", "31^"),
VK(kNtVkF8, "19~", "32~", "19^", "32^"),
VK(kNtVkF9, "20~", "33~", "20^", "33^"),
VK(kNtVkF10, "21~", "34~", "21^", "34^"),
VK(kNtVkF11, "23~", "23$", "23^", "23@"),
VK(kNtVkF12, "24~", "24$", "24^", "24@"),
#undef VK
#undef W4
#undef SW
};
static textwindows int ProcessSignal(int sig, struct Fd *f) {
if (f) {
if (_weaken(__sig_raise)) {
pthread_mutex_unlock(&f->lock);
_weaken(__sig_raise)(sig, SI_KERNEL);
pthread_mutex_lock(&f->lock);
if (!(__sighandflags[sig] & SA_RESTART)) {
return eintr();
}
} else if (sig != SIGWINCH) {
TerminateThisProcess(sig);
}
}
return 0;
}
static textwindows uint32_t 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;
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
static textwindows void EchoTerminalInput(struct Fd *f, char *p, size_t n) {
int64_t hOutput;
if (f->kind == kFdConsole) {
hOutput = f->extra;
} else {
hOutput = g_fds.p[1].handle;
}
if (__ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
}
}
static textwindows int ProcessKeyEvent(const struct NtInputRecord *r, char *p,
uint16_t *utf16hs, struct Fd *f) {
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;
}
// ignore numpad being used to compose a character
if ((cks & kNtLeftAltPressed) && !(cks & kNtEnhancedKey) &&
(vk == kNtVkInsert || vk == kNtVkEnd || vk == kNtVkDown ||
vk == kNtVkNext || vk == kNtVkLeft || vk == kNtVkClear ||
vk == kNtVkRight || vk == kNtVkHome || vk == kNtVkUp ||
vk == kNtVkPrior || vk == kNtVkNumpad0 || vk == kNtVkNumpad1 ||
vk == kNtVkNumpad2 || vk == kNtVkNumpad3 || vk == kNtVkNumpad4 ||
vk == kNtVkNumpad5 || vk == kNtVkNumpad6 || vk == kNtVkNumpad7 ||
vk == kNtVkNumpad8 || vk == kNtVkNumpad9)) {
return 0;
}
int n = 0;
// process virtual keys
if (!c) {
uint32_t w;
w = GetVirtualKey(vk, !!(cks & kNtShiftPressed),
!!(cks & (kNtLeftCtrlPressed | kNtRightCtrlPressed)));
if (!w) return 0;
p[n++] = 033;
if (cks & (kNtLeftAltPressed | kNtRightAltPressed)) {
p[n++] = 033;
}
p[n++] = '[';
do p[n++] = w;
while ((w >>= 8));
return n;
}
// translate utf-16 into utf-32
if (IsHighSurrogate(c)) {
*utf16hs = c;
return 0;
}
if (IsLowSurrogate(c)) {
c = MergeUtf16(*utf16hs, c);
}
// enter sends \r in a raw terminals
// make it a multics newline instead
if (c == '\r' && !(__ttymagic & kFdTtyNoCr2Nl)) {
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) {
c = 0177;
}
// handle ctrl-c and ctrl-\, which tcsetattr() is able to remap
if (!(__ttymagic & kFdTtyNoIsigs)) {
if (c == __vintr && __vintr != _POSIX_VDISABLE) {
return ProcessSignal(SIGINT, f);
} else if (c == __vquit && __vquit != _POSIX_VDISABLE) {
return ProcessSignal(SIGQUIT, f);
}
}
// handle ctrl-d the end of file keystroke
if (c == __veof && __veof != _POSIX_VDISABLE) {
return enodata();
}
// 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,
struct Fd *f) {
int e = 0;
char *p = b;
uint32_t currentbs = f ? f->mousebuttons : 0;
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 (__ttymagic & kFdTtyXtMouse) {
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) && (__ttymagic & kFdTtyXtMouse)) {
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);
*p++ = ';';
p = FormatInt32(p, r->Event.MouseEvent.dwMousePosition.Y + 1);
if (!bs && currentbs) {
*p++ = 'm'; // up
} else {
*p++ = 'M'; // down
}
if (f) {
f->mousebuttons = bs;
}
}
return p - b;
}
textwindows int ConvertConsoleInputToAnsi(const struct NtInputRecord *r,
char p[hasatleast 32],
uint16_t *utf16hs, struct Fd *f) {
switch (r->EventType) {
case kNtKeyEvent:
return ProcessKeyEvent(r, p, utf16hs, f);
case kNtMouseEvent:
return ProcessMouseEvent(r, p, f);
case kNtWindowBufferSizeEvent:
return ProcessSignal(SIGWINCH, f);
default:
return 0;
}
}
static textwindows ssize_t ReadFromWindowsConsole(struct Fd *f, void *data,
size_t size) {
ssize_t rc;
int e = errno;
uint16_t utf16hs = 0;
pthread_mutex_lock(&f->lock);
for (;;) {
if (f->eoftty) {
rc = 0;
break;
}
uint32_t got = MIN(size, f->buflen);
uint32_t remain = f->buflen - got;
if (got) memcpy(data, f->buf, got);
if (remain) memmove(f->buf, f->buf + got, remain);
f->buflen = remain;
if (got) {
rc = got;
break;
}
uint32_t events_available;
if (!GetNumberOfConsoleInputEvents(f->handle, &events_available)) {
rc = __winerr();
break;
}
if (events_available) {
uint32_t n;
struct NtInputRecord r;
if (!ReadConsoleInput(f->handle, &r, 1, &n)) {
rc = __winerr();
break;
}
rc = ConvertConsoleInputToAnsi(&r, f->buf, &utf16hs, f);
if (rc == -1) {
if (errno == ENODATA) {
f->eoftty = true;
errno = e;
rc = 0;
}
break;
}
f->buflen = rc;
} else {
if (f->flags & O_NONBLOCK) {
rc = 0;
break;
}
uint32_t ms = __SIG_POLL_INTERVAL_MS;
if (__ttymagic & kFdTtyNoBlock) {
if (!__vtime) {
rc = 0;
break;
} else {
ms = __vtime * 100;
}
}
if ((rc = _check_interrupts(kSigOpRestartable))) {
break;
}
struct PosixThread *pt = _pthread_self();
pt->pt_flags |= PT_INSEMAPHORE;
WaitForSingleObject(pt->semaphore, ms);
pt->pt_flags &= ~PT_INSEMAPHORE;
if (pt->abort_errno == ECANCELED) {
rc = ecanceled();
break;
}
}
}
pthread_mutex_unlock(&f->lock);
if (rc > 0 && (__ttymagic & kFdTtyEchoing)) {
EchoTerminalInput(f, data, size);
}
return rc;
}
textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
int64_t offset) {
// perform the read i/o operation
bool32 ok;
struct Fd *f;
uint32_t got;
int64_t handle;
uint32_t remain;
char *targetdata;
uint32_t targetsize;
struct PosixThread *pt;
f = g_fds.p + fd;
handle = __resolve_stdin_handle(f->handle);
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);
pt->abort_errno = EAGAIN;
if (pwriting && !seekable) {
return espipe();
@ -85,33 +444,9 @@ textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
offset = 0;
}
uint32_t dwConsoleMode;
bool is_console_input =
g_fds.stdin.handle
? f->handle == g_fds.stdin.handle
: !seekable && (f->kind == kFdConsole ||
GetConsoleMode(handle, &dwConsoleMode));
StartOver:
size = MIN(size, 0x7ffff000);
// the caller might be reading a single byte at a time. but we need to
// be able to munge three bytes into just 1 e.g. "\342\220\200" → "\0"
if (size && f->buflen) {
ReturnDataFromBuffer:
got = MIN(size, f->buflen);
remain = f->buflen - got;
if (got) memcpy(data, f->buf, got);
if (remain) memmove(f->buf, f->buf + got, remain);
f->buflen = remain;
return got;
}
if (is_console_input && size && size < 3 && (__ttymagic & kFdTtyMunging)) {
targetdata = f->buf;
targetsize = sizeof(f->buf);
} else {
targetdata = data;
targetsize = size;
uint32_t cm;
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
return ReadFromWindowsConsole(f, data, size);
}
if (!pwriting && seekable) {
@ -123,7 +458,7 @@ StartOver:
.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, targetdata, targetsize, 0, &overlap);
ok = ReadFile(handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
BlockingOperation:
if (!nonblock) {
@ -172,27 +507,6 @@ StartOver:
}
if (ok) {
if (is_console_input) {
if (__ttymagic & kFdTtyMunging) {
switch (_weaken(__munge_terminal_input)(targetdata, &got)) {
case DO_NOTHING:
break;
case DO_RESTART:
goto StartOver;
case DO_EINTR:
return eintr();
default:
__builtin_unreachable();
}
}
if (__ttymagic & kFdTtyEchoing) {
_weaken(__echo_terminal_input)(f, targetdata, got);
}
}
if (targetdata != data) {
f->buflen = got;
goto ReturnDataFromBuffer;
}
return got;
}

View file

@ -160,13 +160,12 @@ textwindows int __sig_raise(int sig, int sic) {
return (flags & SA_RESTART) ? 2 : 1;
}
textwindows void __sig_cancel(struct PosixThread *pt, unsigned flags) {
textwindows void __sig_cancel(struct PosixThread *pt) {
atomic_int *futex;
if (_weaken(WakeByAddressSingle) &&
(futex = atomic_load_explicit(&pt->pt_futex, memory_order_acquire))) {
_weaken(WakeByAddressSingle)(futex);
} else if (!(flags & SA_RESTART) && pt->iohandle > 0) {
pt->abort_errno = EINTR;
} else if (pt->iohandle > 0) {
if (!CancelIoEx(pt->iohandle, pt->ioverlap)) {
int err = GetLastError();
if (err != kNtErrorNotFound) {
@ -174,7 +173,6 @@ textwindows void __sig_cancel(struct PosixThread *pt, unsigned flags) {
}
}
} else if (pt->pt_flags & PT_INSEMAPHORE) {
pt->abort_errno = EINTR;
if (!ReleaseSemaphore(pt->semaphore, 1, 0)) {
STRACE("ReleaseSemaphore() failed w/ %d", GetLastError());
}
@ -259,8 +257,9 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
return ESRCH;
}
ResumeThread(th); // doesn't actually resume if doing blocking i/o
__sig_cancel(pt, flags); // we can wake it up immediately by canceling it
ResumeThread(th); // doesn't actually resume if doing blocking i/o
pt->abort_errno = EINTR;
__sig_cancel(pt); // we can wake it up immediately by canceling it
return 0;
}

View file

@ -26,7 +26,7 @@ int __sig_check(void);
int __sig_kill(struct PosixThread *, int, int);
int __sig_mask(int, const sigset_t *, sigset_t *);
int __sig_raise(int, int);
void __sig_cancel(struct PosixThread *, unsigned);
void __sig_cancel(struct PosixThread *);
void __sig_generate(int, int);
void __sig_init(void);

View file

@ -507,9 +507,6 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) {
once = true;
}
}
if (IsWindows() && !rc && sig == SIGWINCH) {
_init_sigwinch(); // lazy b/c sigwinch is otherwise ignored
}
}
STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act),
DescribeSigaction(rc, oldact), rc);

View file

@ -1,90 +0,0 @@
/*-*- 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 2021 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/atomic.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/cosmo.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/struct/consolescreenbufferinfoex.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/tls.h"
#include "libc/thread/tls2.internal.h"
#ifdef __x86_64__
intptr_t __sigwinch_thread;
static unsigned __sigwinch_size;
static atomic_uint __sigwinch_once;
static struct CosmoTib __sigwinch_tls;
static textwindows unsigned __get_console_size(void) {
unsigned res = -1u;
__fds_lock();
for (int fd = 1; fd < 10; ++fd) {
intptr_t hConsoleOutput;
if (g_fds.p[fd].kind == kFdConsole) {
hConsoleOutput = g_fds.p[fd].extra;
} else {
hConsoleOutput = g_fds.p[fd].handle;
}
struct NtConsoleScreenBufferInfoEx sr = {.cbSize = sizeof(sr)};
if (GetConsoleScreenBufferInfoEx(hConsoleOutput, &sr)) {
unsigned short yn = sr.srWindow.Bottom - sr.srWindow.Top + 1;
unsigned short xn = sr.srWindow.Right - sr.srWindow.Left + 1;
res = (unsigned)yn << 16 | xn;
break;
}
}
__fds_unlock();
return res;
}
static textwindows dontinstrument uint32_t __sigwinch_worker(void *arg) {
__bootstrap_tls(&__sigwinch_tls, __builtin_frame_address(0));
for (;;) {
unsigned old = __sigwinch_size;
unsigned neu = __get_console_size();
if (neu != old) {
__sigwinch_size = neu;
__sig_generate(SIGWINCH, SI_KERNEL);
}
SleepEx(25, false);
}
return 0;
}
static textwindows void __sigwinch_init(void) {
__enable_threads();
__sigwinch_size = __get_console_size();
__sigwinch_thread = CreateThread(0, 65536, __sigwinch_worker, 0,
kNtStackSizeParamIsAReservation, 0);
}
textwindows void _init_sigwinch(void) {
cosmo_once(&__sigwinch_once, __sigwinch_init);
}
#endif /* __x86_64__ */

View file

@ -24,18 +24,22 @@ COSMOPOLITAN_C_START_
#define kFdTtyMunging 4 /* enable input / output remappings */
#define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */
#define kFdTtyNoIsigs 16
#define kFdTtyNoBlock 32
#define kFdTtyXtMouse 64
struct Fd {
char kind;
bool eoftty;
bool dontclose;
char buflen;
char buf[4];
unsigned char buflen;
unsigned flags;
unsigned mode;
int64_t handle;
int64_t extra;
int64_t pointer;
pthread_mutex_t lock;
unsigned char mousebuttons;
char buf[32];
};
struct StdinRelay {
@ -55,11 +59,6 @@ struct Fds {
struct StdinRelay stdin;
};
void WinMainStdin(void);
int64_t __resolve_stdin_handle(int64_t);
int __munge_terminal_input(char *, uint32_t *);
void __echo_terminal_input(struct Fd *, char *, size_t);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ */

View file

@ -64,9 +64,6 @@ const char *DescribeSigaction(char[256], int, const struct sigaction *);
#define DescribeSigaction(rc, sa) DescribeSigaction(alloca(256), rc, sa)
void _init_onntconsoleevent(void);
void _init_sigwinch(void);
extern intptr_t __sigwinch_thread;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -55,13 +55,14 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) {
bzero(tio, sizeof(*tio));
tio->c_cc[VMIN] = 1;
tio->c_cc[VMIN] = !(__ttymagic & kFdTtyNoBlock);
tio->c_cc[VEOF] = __veof;
tio->c_cc[VTIME] = __vtime;
tio->c_cc[VINTR] = __vintr;
tio->c_cc[VQUIT] = __vquit;
tio->c_cc[VERASE] = CTRL('?');
tio->c_cc[VWERASE] = CTRL('W');
tio->c_cc[VKILL] = CTRL('U');
tio->c_cc[VEOF] = CTRL('D');
tio->c_cc[VMIN] = CTRL('A');
tio->c_cc[VSTART] = _POSIX_VDISABLE;
tio->c_cc[VSTOP] = _POSIX_VDISABLE;

View file

@ -45,130 +45,6 @@
#ifdef __x86_64__
textwindows int __munge_terminal_input(char *p, uint32_t *n) {
size_t i, j;
if (!(__ttymagic & kFdTtyNoCr2Nl)) {
for (i = 0; i < *n; ++i) {
if (p[i] == '\r') {
p[i] = '\n';
}
}
}
bool delivered = false;
bool got_vintr = false;
bool got_vquit = false;
for (j = i = 0; i < *n; ++i) {
/*
It's not possible to type Ctrl+Space (aka ^@) into the CMD.EXE
console. Ctrl+Z is also problematic, since the Windows Console
processes that keystroke into an EOF event, even when we don't
enable input processing. These control codes are important for
Emacs users. One solution is to install the "Windows Terminal"
application from Microsoft Store, type Ctrl+Shift+, to get its
settings.json file opened, and add this code to its "actions":
"actions": [
{
"command": {
"action": "sendInput",
"input": ""
},
"keys": "ctrl+space"
},
{
"command": {
"action": "sendInput",
"input": ""
},
"keys": "ctrl+z"
},
{
"command": {
"action": "sendInput",
"input": "\u001f"
},
"keys": "ctrl+-"
}
],
Its not possible to configure Windows Terminal to output our
control codes. The workaround is to encode control sequences
using the "Control Pictures" UNICODE plane, which we'll then
translate back from UTF-8 glyphs, into actual control codes.
Another option Windows users can consider, particularly when
using CMD.EXE is installing Microsoft PowerTools whech makes
it possible to remap keys then configure ncurses to use them
https://github.com/microsoft/terminal/issues/2865
*/
int c;
if (i + 3 <= *n && // control pictures ascii nul ␀
(p[i + 0] & 255) == 0xe2 && // becomes ^@ a.k.a. ctrl-space
(p[i + 1] & 255) == 0x90 && // map the entire unicode plane
((p[i + 2] & 255) >= 0x80 && //
(p[i + 2] & 255) <= 0x9F)) {
c = (p[i + 2] & 255) - 0x80;
i += 2;
} else {
c = p[i] & 255;
}
if (!(__ttymagic & kFdTtyNoIsigs) && //
__vintr != _POSIX_VDISABLE && //
c == __vintr) {
got_vintr = true;
} else if (!(__ttymagic & kFdTtyNoIsigs) && //
__vquit != _POSIX_VDISABLE && //
c == __vquit) {
got_vquit = true;
} else {
p[j++] = c;
}
}
if (got_vintr) {
__sig_raise(SIGINT, SI_KERNEL);
delivered |= true;
}
if (got_vquit) {
__sig_raise(SIGQUIT, SI_KERNEL);
delivered |= true;
}
if (*n && !j) {
if (delivered) {
return DO_EINTR;
} else {
return DO_RESTART;
}
}
*n = j;
return DO_NOTHING;
}
// Manual CMD.EXE echoing for when !ICANON && ECHO is the case.
textwindows void __echo_terminal_input(struct Fd *fd, char *p, size_t n) {
int64_t hOutput;
if (fd->kind == kFdConsole) {
hOutput = fd->extra;
} else {
hOutput = g_fds.p[1].handle;
}
if (__ttymagic & kFdTtyEchoRaw) {
WriteFile(hOutput, p, n, 0, 0);
} else {
size_t i;
for (i = 0; i < n; ++i) {
if (isascii(p[i]) && iscntrl(p[i]) && p[i] != '\n' && p[i] != '\t') {
char ctl[2];
ctl[0] = '^';
ctl[1] = p[i] ^ 0100;
WriteFile(hOutput, ctl, 2, 0, 0);
} else {
WriteFile(hOutput, p + i, 1, 0, 0);
}
}
}
}
textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
bool32 ok;
int64_t hInput, hOutput;
@ -198,18 +74,15 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
inmode |= kNtEnableWindowInput;
__ttymagic = 0;
if (tio->c_lflag & ICANON) {
inmode |= kNtEnableLineInput | kNtEnableProcessedInput;
inmode |=
kNtEnableLineInput | kNtEnableProcessedInput | kNtEnableQuickEditMode;
} else {
inmode &= ~kNtEnableQuickEditMode;
__ttymagic |= kFdTtyMunging;
if (tio->c_cc[VMIN] != 1) {
STRACE("tcsetattr c_cc[VMIN] must be 1 on Windows");
return einval();
}
if (IsAtLeastWindows10()) {
// - keys like f1, up, etc. get turned into \e ansi codes
// - totally destroys default console gui (e.g. up arrow)
inmode |= kNtEnableVirtualTerminalInput;
if (!tio->c_cc[VMIN]) {
__ttymagic |= kFdTtyNoBlock;
}
__vtime = tio->c_cc[VTIME];
}
if (!(tio->c_iflag & ICRNL)) {
__ttymagic |= kFdTtyNoCr2Nl;
@ -233,6 +106,7 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
if (!(tio->c_lflag & ISIG)) {
__ttymagic |= kFdTtyNoIsigs;
}
__veof = tio->c_cc[VEOF];
__vintr = tio->c_cc[VINTR];
__vquit = tio->c_cc[VQUIT];
if ((tio->c_lflag & ISIG) && //
@ -261,11 +135,4 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
return 0;
}
__attribute__((__constructor__)) static void tcsetattr_nt_init(void) {
if (!getenv("TERM")) {
setenv("TERM", "xterm-256color", true);
errno = 0; // ignore malloc not linked
}
}
#endif /* __x86_64__ */

View file

@ -1,94 +0,0 @@
/*-*- 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 2023 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/dce.h"
#include "libc/nt/console.h"
#include "libc/nt/createfile.h"
#include "libc/nt/enum/accessmask.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/fileflagandattributes.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/ipc.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/thread/tls.h"
static textwindows dontinstrument wontreturn void WinStdinThread(void) {
char buf[4];
struct CosmoTib tls;
uint32_t i, got, wrote;
__bootstrap_tls(&tls, __builtin_frame_address(0));
for (;;) {
WaitForSingleObject(g_fds.stdin.inisem, -1u);
if (!ReadFile(g_fds.stdin.handle, buf, sizeof(buf), &got, 0)) goto Finish;
if (!got) goto Finish;
for (i = 0; i < got; i += wrote) {
if (!WriteFile(g_fds.stdin.writer, buf + i, got - i, &wrote, 0)) {
goto Finish;
}
}
}
Finish:
CloseHandle(g_fds.stdin.writer);
ExitThread(0);
}
textwindows static char16_t *FixCpy(char16_t p[17], uint64_t x, uint8_t k) {
while (k > 0) *p++ = "0123456789abcdef"[(x >> (k -= 4)) & 15];
*p = '\0';
return p;
}
textwindows static char16_t *CreateStdinPipeName(char16_t *a, int64_t h) {
char16_t *p = a;
const char *q = "\\\\?\\pipe\\cosmo\\stdin\\";
while (*q) *p++ = *q++;
p = FixCpy(p, h, 64);
*p = 0;
return a;
}
textwindows void WinMainStdin(void) {
uint32_t conmode;
char16_t pipename[64];
int64_t hStdin, hWriter, hReader, hThread, hSemaphore;
if (!SupportsWindows()) return;
hStdin = GetStdHandle(kNtStdInputHandle);
if (!GetConsoleMode(hStdin, &conmode)) return;
CreateStdinPipeName(pipename, hStdin);
hWriter = CreateNamedPipe(
pipename, kNtPipeAccessOutbound | kNtFileFlagOverlapped,
kNtPipeTypeMessage | kNtPipeReadmodeMessage | kNtPipeNowait,
kNtPipeUnlimitedInstances, 4096, 4096, 0, 0);
if (hWriter == kNtInvalidHandleValue) return;
hReader = CreateFile(pipename, kNtGenericRead, 0, 0, kNtOpenExisting,
kNtFileFlagOverlapped, 0);
if (hReader == kNtInvalidHandleValue) return;
hSemaphore = CreateSemaphore(0, 0, 1, 0);
if (!hSemaphore) return;
hThread = CreateThread(0, 65536, (void *)WinStdinThread, 0,
kNtStackSizeParamIsAReservation, 0);
if (!hThread) return;
g_fds.stdin.handle = hStdin;
g_fds.stdin.thread = hThread;
g_fds.stdin.reader = hReader;
g_fds.stdin.writer = hWriter;
g_fds.stdin.inisem = hSemaphore;
}

View file

@ -23,9 +23,12 @@
#include "libc/calls/struct/iovec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.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/nt/console.h"
#include "libc/nt/enum/consolemodeflags.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h"
@ -46,6 +49,13 @@
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle;
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
}
static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
ssize_t offset) {
@ -81,6 +91,76 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
offset = f->pointer;
}
// 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
uint32_t cm;
if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
int64_t hin;
if (f->kind == kFdConsole) {
hin = f->handle;
} else {
hin = GetStdHandle(kNtStdInputHandle);
}
if (GetConsoleMode(hin, &cm)) {
int t = 0;
unsigned x;
bool m = false;
char *p = data;
uint32_t cm2 = cm;
for (int i = 0; i < size; ++i) {
switch (t) {
case 0:
if (p[i] == 033) {
t = 1;
}
break;
case 1:
if (p[i] == '[') {
t = 2;
} else {
t = 0;
}
break;
case 2:
if (p[i] == '?') {
t = 3;
x = 0;
} else {
t = 0;
}
break;
case 3:
if ('0' <= p[i] && p[i] <= '9') {
x *= 10;
x += p[i] - '0';
} else if (p[i] == ';') {
m |= IsMouseModeCommand(x);
x = 0;
} else {
m |= IsMouseModeCommand(x);
if (p[i] == 'h') {
__ttymagic |= kFdTtyXtMouse;
cm2 |= kNtEnableMouseInput;
cm2 &= kNtEnableQuickEditMode; // precludes mouse events
} else if (p[i] == 'l') {
__ttymagic &= ~kFdTtyXtMouse;
cm2 |= kNtEnableQuickEditMode; // disables mouse too
}
t = 0;
}
break;
default:
__builtin_unreachable();
}
}
if (cm2 != cm) {
SetConsoleMode(hin, cm2);
}
}
}
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
.Pointer = offset};
ok = WriteFile(handle, data, size, 0, &overlap);
@ -143,7 +223,6 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
_weaken(__sig_raise)(SIGPIPE, SI_KERNEL);
return epipe();
} else {
STRACE("broken pipe");
TerminateThisProcess(SIGPIPE);
}
case kNtErrorAccessDenied: // write doesn't return EACCESS