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

@ -28,8 +28,6 @@
#include "libc/sysv/consts/termios.h" #include "libc/sysv/consts/termios.h"
#include "libc/x/xsigaction.h" #include "libc/x/xsigaction.h"
__static_yoink("WinMainStdin");
#define CTRL(C) ((C) ^ 0b01000000) #define CTRL(C) ((C) ^ 0b01000000)
#define WRITE(FD, SLIT) write(FD, SLIT, strlen(SLIT)) #define WRITE(FD, SLIT) write(FD, SLIT, strlen(SLIT))
#define ENABLE_SAFE_PASTE "\e[?2004h" #define ENABLE_SAFE_PASTE "\e[?2004h"
@ -69,7 +67,7 @@ int rawmode(void) {
memcpy(&t, &oldterm, sizeof(t)); memcpy(&t, &oldterm, sizeof(t));
t.c_cc[VMIN] = 1; t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 1; t.c_cc[VTIME] = 0;
// emacs does the following to remap ctrl-c to ctrl-g in termios // emacs does the following to remap ctrl-c to ctrl-g in termios
// t.c_cc[VINTR] = CTRL('G'); // t.c_cc[VINTR] = CTRL('G');

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 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/console.internal.h"
#include "libc/intrin/atomic.h" #include "libc/calls/struct/fd.internal.h"
#include "libc/nt/synchronization.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) { int CountConsoleInputBytes(int64_t handle) {
if (handle == g_fds.stdin.handle) { char buf[32];
if (g_fds.stdin.inisem) { int rc, e = errno;
ReleaseSemaphore(g_fds.stdin.inisem, 1, 0); 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;
} }
handle = g_fds.stdin.reader; break;
} }
return handle; rc += count;
}
} else {
rc = __winerr();
}
return rc;
} }
#endif /* __x86_64__ */

View file

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

View file

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

View file

@ -17,32 +17,39 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/console.internal.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/sig.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/fd.internal.h"
#include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.h"
#include "libc/calls/struct/iovec.internal.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/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h" #include "libc/calls/wincrash.internal.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/kprintf.h" #include "libc/fmt/itoa.h"
#include "libc/intrin/nomultics.internal.h" #include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/nt/console.h" #include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h" #include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/vk.h"
#include "libc/nt/enum/wait.h" #include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h" #include "libc/nt/errors.h"
#include "libc/nt/events.h" #include "libc/nt/events.h"
#include "libc/nt/files.h" #include "libc/nt/files.h"
#include "libc/nt/ipc.h" #include "libc/nt/ipc.h"
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/nt/struct/inputrecord.h"
#include "libc/nt/struct/overlapped.h" #include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h" #include "libc/nt/synchronization.h"
#include "libc/nt/thread.h" #include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h" #include "libc/nt/thunk/msabi.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/str/utf16.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/termios.h" #include "libc/sysv/consts/termios.h"
@ -50,33 +57,385 @@
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
__static_yoink("WinMainStdin");
#ifdef __x86_64__ #ifdef __x86_64__
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; __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, textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
int64_t offset) { int64_t offset) {
// perform the read i/o operation
bool32 ok; bool32 ok;
struct Fd *f; struct Fd *f;
uint32_t got; uint32_t got;
int64_t handle; int64_t handle;
uint32_t remain;
char *targetdata;
uint32_t targetsize;
struct PosixThread *pt; struct PosixThread *pt;
f = g_fds.p + fd; f = g_fds.p + fd;
handle = __resolve_stdin_handle(f->handle); handle = f->handle;
pt = _pthread_self(); pt = _pthread_self();
pt->abort_errno = EAGAIN;
size = MIN(size, 0x7ffff000);
bool pwriting = offset != -1; bool pwriting = offset != -1;
bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk; bool seekable = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
bool nonblock = !!(f->flags & O_NONBLOCK); bool nonblock = !!(f->flags & O_NONBLOCK);
pt->abort_errno = EAGAIN;
if (pwriting && !seekable) { if (pwriting && !seekable) {
return espipe(); return espipe();
@ -85,33 +444,9 @@ textwindows ssize_t sys_read_nt_impl(int fd, void *data, size_t size,
offset = 0; offset = 0;
} }
uint32_t dwConsoleMode; uint32_t cm;
bool is_console_input = if (!seekable && (f->kind == kFdConsole || GetConsoleMode(handle, &cm))) {
g_fds.stdin.handle return ReadFromWindowsConsole(f, data, size);
? 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;
} }
if (!pwriting && seekable) { if (!pwriting && seekable) {
@ -123,7 +458,7 @@ StartOver:
.Pointer = offset}; .Pointer = offset};
// the win32 manual says it's important to *not* put &got here // the win32 manual says it's important to *not* put &got here
// since for overlapped i/o, we always use GetOverlappedResult // 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) { if (!ok && GetLastError() == kNtErrorIoPending) {
BlockingOperation: BlockingOperation:
if (!nonblock) { if (!nonblock) {
@ -172,27 +507,6 @@ StartOver:
} }
if (ok) { 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; return got;
} }

View file

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

View file

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

View file

@ -507,9 +507,6 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) {
once = true; 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), STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act),
DescribeSigaction(rc, oldact), rc); 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 kFdTtyMunging 4 /* enable input / output remappings */
#define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */ #define kFdTtyNoCr2Nl 8 /* don't map \r → \n (a.k.a !ICRNL) */
#define kFdTtyNoIsigs 16 #define kFdTtyNoIsigs 16
#define kFdTtyNoBlock 32
#define kFdTtyXtMouse 64
struct Fd { struct Fd {
char kind; char kind;
bool eoftty;
bool dontclose; bool dontclose;
char buflen; unsigned char buflen;
char buf[4];
unsigned flags; unsigned flags;
unsigned mode; unsigned mode;
int64_t handle; int64_t handle;
int64_t extra; int64_t extra;
int64_t pointer; int64_t pointer;
pthread_mutex_t lock; pthread_mutex_t lock;
unsigned char mousebuttons;
char buf[32];
}; };
struct StdinRelay { struct StdinRelay {
@ -55,11 +59,6 @@ struct Fds {
struct StdinRelay stdin; 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_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ */ #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) #define DescribeSigaction(rc, sa) DescribeSigaction(alloca(256), rc, sa)
void _init_onntconsoleevent(void); void _init_onntconsoleevent(void);
void _init_sigwinch(void);
extern intptr_t __sigwinch_thread;
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -55,13 +55,14 @@ textwindows int tcgetattr_nt(int fd, struct termios *tio) {
bzero(tio, sizeof(*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[VINTR] = __vintr;
tio->c_cc[VQUIT] = __vquit; tio->c_cc[VQUIT] = __vquit;
tio->c_cc[VERASE] = CTRL('?'); tio->c_cc[VERASE] = CTRL('?');
tio->c_cc[VWERASE] = CTRL('W'); tio->c_cc[VWERASE] = CTRL('W');
tio->c_cc[VKILL] = CTRL('U'); tio->c_cc[VKILL] = CTRL('U');
tio->c_cc[VEOF] = CTRL('D');
tio->c_cc[VMIN] = CTRL('A'); tio->c_cc[VMIN] = CTRL('A');
tio->c_cc[VSTART] = _POSIX_VDISABLE; tio->c_cc[VSTART] = _POSIX_VDISABLE;
tio->c_cc[VSTOP] = _POSIX_VDISABLE; tio->c_cc[VSTOP] = _POSIX_VDISABLE;

View file

@ -45,130 +45,6 @@
#ifdef __x86_64__ #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) { textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
bool32 ok; bool32 ok;
int64_t hInput, hOutput; int64_t hInput, hOutput;
@ -198,18 +74,15 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
inmode |= kNtEnableWindowInput; inmode |= kNtEnableWindowInput;
__ttymagic = 0; __ttymagic = 0;
if (tio->c_lflag & ICANON) { if (tio->c_lflag & ICANON) {
inmode |= kNtEnableLineInput | kNtEnableProcessedInput; inmode |=
kNtEnableLineInput | kNtEnableProcessedInput | kNtEnableQuickEditMode;
} else { } else {
inmode &= ~kNtEnableQuickEditMode;
__ttymagic |= kFdTtyMunging; __ttymagic |= kFdTtyMunging;
if (tio->c_cc[VMIN] != 1) { if (!tio->c_cc[VMIN]) {
STRACE("tcsetattr c_cc[VMIN] must be 1 on Windows"); __ttymagic |= kFdTtyNoBlock;
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;
} }
__vtime = tio->c_cc[VTIME];
} }
if (!(tio->c_iflag & ICRNL)) { if (!(tio->c_iflag & ICRNL)) {
__ttymagic |= kFdTtyNoCr2Nl; __ttymagic |= kFdTtyNoCr2Nl;
@ -233,6 +106,7 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
if (!(tio->c_lflag & ISIG)) { if (!(tio->c_lflag & ISIG)) {
__ttymagic |= kFdTtyNoIsigs; __ttymagic |= kFdTtyNoIsigs;
} }
__veof = tio->c_cc[VEOF];
__vintr = tio->c_cc[VINTR]; __vintr = tio->c_cc[VINTR];
__vquit = tio->c_cc[VQUIT]; __vquit = tio->c_cc[VQUIT];
if ((tio->c_lflag & ISIG) && // if ((tio->c_lflag & ISIG) && //
@ -261,11 +135,4 @@ textwindows int tcsetattr_nt(int fd, int opt, const struct termios *tio) {
return 0; 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__ */ #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/struct/iovec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/intrin/strace.internal.h" #include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
#include "libc/macros.internal.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/filetype.h"
#include "libc/nt/enum/wait.h" #include "libc/nt/enum/wait.h"
#include "libc/nt/errors.h" #include "libc/nt/errors.h"
@ -46,6 +49,13 @@
__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; __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, static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
ssize_t offset) { 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; 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), struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0),
.Pointer = offset}; .Pointer = offset};
ok = WriteFile(handle, data, size, 0, &overlap); 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); _weaken(__sig_raise)(SIGPIPE, SI_KERNEL);
return epipe(); return epipe();
} else { } else {
STRACE("broken pipe");
TerminateThisProcess(SIGPIPE); TerminateThisProcess(SIGPIPE);
} }
case kNtErrorAccessDenied: // write doesn't return EACCESS case kNtErrorAccessDenied: // write doesn't return EACCESS

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/struct/termios.h" #include "libc/calls/struct/termios.h"
#include "libc/calls/struct/termios.internal.h" #include "libc/calls/struct/termios.internal.h"
#include "libc/calls/ttydefaults.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/asan.internal.h" #include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h" #include "libc/intrin/describeflags.internal.h"
@ -119,8 +120,15 @@ const char *(DescribeTermios)(char buf[N], ssize_t rc,
{IEXTEN, "IEXTEN"}, // {IEXTEN, "IEXTEN"}, //
{EXTPROC, "EXTPROC"}, // {EXTPROC, "EXTPROC"}, //
}; };
append(", .c_lflag=%s", append(", "
DescribeFlags(b128, 128, kLocal, ARRAYLEN(kLocal), "", tio->c_lflag)); ".c_lflag=%s, "
".c_cc[VMIN]=%d, "
".c_cc[VTIME]=%d, "
".c_cc[VINTR]=CTRL(%#c), "
".c_cc[VQUIT]=CTRL(%#c)",
DescribeFlags(b128, 128, kLocal, ARRAYLEN(kLocal), "", tio->c_lflag),
tio->c_cc[VMIN], tio->c_cc[VTIME], CTRL(tio->c_cc[VINTR]),
CTRL(tio->c_cc[VQUIT]));
append("}"); append("}");

View file

@ -119,6 +119,7 @@ textstartup void __init_fds(int argc, char **argv, char **envp) {
} }
fds->p[1].flags = O_WRONLY | O_APPEND; fds->p[1].flags = O_WRONLY | O_APPEND;
fds->p[2].flags = O_WRONLY | O_APPEND; fds->p[2].flags = O_WRONLY | O_APPEND;
__veof = CTRL('D');
__vintr = CTRL('C'); __vintr = CTRL('C');
__vquit = CTRL('\\'); __vquit = CTRL('\\');
} }

View file

@ -18,15 +18,10 @@
*/ */
#include "libc/intrin/nomultics.internal.h" #include "libc/intrin/nomultics.internal.h"
/** unsigned char __replmode;
* Controls ANSI prefix for log emissions. unsigned char __replstderr;
* unsigned char __ttymagic;
* This should be true in raw tty mode repls. unsigned char __veof;
* unsigned char __vintr;
* @see kprintf(), vflogf(), linenoise() unsigned char __vquit;
*/ unsigned char __vtime;
char __replmode;
char __replstderr;
char __ttymagic;
char __vintr;
char __vquit;

View file

@ -1,13 +1,16 @@
#ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ #ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_
#include "libc/calls/struct/timespec.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
extern char __replmode; extern unsigned char __replmode;
extern char __replstderr; extern unsigned char __replstderr;
extern char __ttymagic; extern unsigned char __ttymagic;
extern char __vintr; extern unsigned char __veof;
extern char __vquit; extern unsigned char __vintr;
extern unsigned char __vquit;
extern unsigned char __vtime;
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -49,7 +49,7 @@ bool32 ReadConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer,
uint32_t nLength, uint32_t *lpNumberOfEventsRead); uint32_t nLength, uint32_t *lpNumberOfEventsRead);
bool32 PeekConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer, bool32 PeekConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer,
uint32_t nLength, uint32_t *lpNumberOfEventsRead); uint32_t nLength, uint32_t *lpNumberOfEventsRead);
bool32 GetNumberOfConsoleInputEvent(int64_t hConsoleInput, bool32 GetNumberOfConsoleInputEvents(int64_t hConsoleInput,
uint32_t *lpNumberOfEvents); uint32_t *lpNumberOfEvents);
bool32 ReadConsoleOutput(int64_t hConsoleOutput, struct NtCharInfo *lpBuffer, bool32 ReadConsoleOutput(int64_t hConsoleOutput, struct NtCharInfo *lpBuffer,
struct NtCoord dwBufferSize, struct NtCoord dwBufferSize,

View file

@ -56,7 +56,8 @@ int64_t OpenProcess(uint32_t dwDesiredAccess, bool32 bInheritHandle,
uint32_t GetCurrentProcessId(void); /* %gs:0x40 */ uint32_t GetCurrentProcessId(void); /* %gs:0x40 */
uint32_t GetEnvironmentVariable(const char16_t *lpName, char16_t *lpBuffer, uint32_t GetEnvironmentVariable(const char16_t *lpName, char16_t *lpBuffer,
uint32_t nSize); uint32_t nSize);
uint32_t SetEnvironmentVariable(const char16_t *lpName, char16_t *lpValue); uint32_t SetEnvironmentVariable(const char16_t *lpName,
const char16_t *lpValue);
int32_t SetEnvironmentStrings(char16_t *NewEnvironment); int32_t SetEnvironmentStrings(char16_t *NewEnvironment);
bool32 GetProcessAffinityMask(int64_t hProcess, uint64_t *lpProcessAffinityMask, bool32 GetProcessAffinityMask(int64_t hProcess, uint64_t *lpProcessAffinityMask,
uint64_t *lpSystemAffinityMask); uint64_t *lpSystemAffinityMask);

View file

@ -9,17 +9,35 @@ struct NtKeyEventRecord {
uint16_t wVirtualKeyCode; uint16_t wVirtualKeyCode;
uint16_t wVirtualScanCode; uint16_t wVirtualScanCode;
union { union {
int16_t UnicodeChar; uint16_t UnicodeChar;
char AsciiChar; char AsciiChar;
} uChar; } uChar;
unsigned int dwControlKeyState; unsigned int dwControlKeyState;
#define kNtRightAltPressed 0x0001
#define kNtLeftAltPressed 0x0002
#define kNtRightCtrlPressed 0x0004
#define kNtLeftCtrlPressed 0x0008
#define kNtShiftPressed 0x0010
#define kNtNumlockOn 0x0020
#define kNtScrolllockOn 0x0040
#define kNtCapslockOn 0x0080
#define kNtEnhancedKey 0x0100
}; };
struct NtMouseEventRecord { struct NtMouseEventRecord {
struct NtCoord dwMousePosition; struct NtCoord dwMousePosition;
uint32_t dwButtonState; uint32_t dwButtonState;
#define kNtFromLeft1stButtonPressed 0x0001
#define kNtRightmostButtonPressed 0x0002
#define kNtFromLeft2ndButtonPressed 0x0004
#define kNtFromLeft3rdButtonPressed 0x0008
#define kNtFromLeft4thButtonPressed 0x0010
uint32_t dwControlKeyState; uint32_t dwControlKeyState;
uint32_t dwEventFlags; uint32_t dwEventFlags;
#define kNtMouseMoved 0x0001
#define kNtDoubleClick 0x0002
#define kNtMouseWheeled 0x0004
#define kNtMouseHwheeled 0x0008
}; };
struct NtWindowBufferSizeRecord { struct NtWindowBufferSizeRecord {
@ -36,6 +54,11 @@ struct NtFocusEventRecord {
struct NtInputRecord { struct NtInputRecord {
uint16_t EventType; uint16_t EventType;
#define kNtKeyEvent 0x0001
#define kNtMouseEvent 0x0002
#define kNtWindowBufferSizeEvent 0x0004
#define kNtMenuEvent 0x0008
#define kNtFocusEvent 0x0010
union { union {
struct NtKeyEventRecord KeyEvent; struct NtKeyEventRecord KeyEvent;
struct NtMouseEventRecord MouseEvent; struct NtMouseEventRecord MouseEvent;

View file

@ -154,9 +154,6 @@ keywords int sys_execve_nt(const char *program, char *const argv[],
PurgeThread(pt->tib->tib_syshand); PurgeThread(pt->tib->tib_syshand);
PurgeHandle(pt->semaphore); PurgeHandle(pt->semaphore);
} }
if (_weaken(__sigwinch_thread)) {
PurgeThread(*_weaken(__sigwinch_thread));
}
if (_weaken(__itimer)) { if (_weaken(__itimer)) {
PurgeThread(_weaken(__itimer)->thread); PurgeThread(_weaken(__itimer)->thread);
} }

View file

@ -55,12 +55,14 @@ __msabi extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentString
__msabi extern typeof(GetConsoleMode) *const __imp_GetConsoleMode; __msabi extern typeof(GetConsoleMode) *const __imp_GetConsoleMode;
__msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId; __msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId;
__msabi extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW; __msabi extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW;
__msabi extern typeof(GetEnvironmentVariable) *const __imp_GetEnvironmentVariableW;
__msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW; __msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW;
__msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; __msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; __msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(SetConsoleCP) *const __imp_SetConsoleCP; __msabi extern typeof(SetConsoleCP) *const __imp_SetConsoleCP;
__msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode; __msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode;
__msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP; __msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP;
__msabi extern typeof(SetEnvironmentVariable) *const __imp_SetEnvironmentVariableW;
__msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle; __msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle;
__msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; __msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect;
// clang-format on // clang-format on
@ -92,8 +94,7 @@ __funline char16_t *MyCommandLine(void) {
// implements all win32 apis on non-windows hosts // implements all win32 apis on non-windows hosts
static abi long __oops_win32(void) { static abi long __oops_win32(void) {
assert(!"win32 api called on non-windows host"); notpossible;
return 0;
} }
// returns true if utf-8 path is a win32-style path that exists // returns true if utf-8 path is a win32-style path that exists
@ -131,11 +132,16 @@ static abi wontreturn void WinInit(const char16_t *cmdline) {
(intptr_t)v_ntsubsystem == kNtImageSubsystemWindowsCui) { (intptr_t)v_ntsubsystem == kNtImageSubsystemWindowsCui) {
__imp_SetConsoleCP(kNtCpUtf8); __imp_SetConsoleCP(kNtCpUtf8);
__imp_SetConsoleOutputCP(kNtCpUtf8); __imp_SetConsoleOutputCP(kNtCpUtf8);
for (int i = 1; i <= 2; ++i) { for (int i = 0; i <= 2; ++i) {
uint32_t m; uint32_t m;
intptr_t h = __imp_GetStdHandle(kNtStdio[i]); intptr_t h = __imp_GetStdHandle(kNtStdio[i]);
__imp_GetConsoleMode(h, &m); __imp_GetConsoleMode(h, &m);
__imp_SetConsoleMode(h, m | kNtEnableVirtualTerminalProcessing); if (!i) {
m |= kNtEnableMouseInput | kNtEnableWindowInput;
} else {
m |= kNtEnableVirtualTerminalProcessing;
}
__imp_SetConsoleMode(h, m);
} }
} }
@ -222,9 +228,6 @@ abi int64_t WinMain(int64_t hInstance, int64_t hPrevInstance,
_weaken(WinSockInit)(); _weaken(WinSockInit)();
} }
DeduplicateStdioHandles(); DeduplicateStdioHandles();
if (_weaken(WinMainStdin)) {
_weaken(WinMainStdin)();
}
if (_weaken(WinMainForked)) { if (_weaken(WinMainForked)) {
_weaken(WinMainForked)(); _weaken(WinMainForked)();
} }

View file

@ -111,23 +111,27 @@ static void _pthread_cancel_listen(void) {
static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) { static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) {
uint32_t old_suspend_count; uint32_t old_suspend_count;
if ((pt->pt_flags & PT_ASYNC) && !(pt->pt_flags & PT_NOCANCEL)) { if (!(pt->pt_flags & PT_NOCANCEL) &&
if ((old_suspend_count = SuspendThread(hThread)) != -1u) { (pt->pt_flags & (PT_ASYNC | PT_MASKED))) {
pt->pt_flags |= PT_NOCANCEL;
pt->abort_errno = ECANCELED;
if ((pt->pt_flags & PT_ASYNC) &&
(old_suspend_count = SuspendThread(hThread)) != -1u) {
if (!old_suspend_count) { if (!old_suspend_count) {
struct NtContext cpu; struct NtContext cpu;
cpu.ContextFlags = kNtContextControl | kNtContextInteger; cpu.ContextFlags = kNtContextControl | kNtContextInteger;
if (GetThreadContext(hThread, &cpu)) { if (GetThreadContext(hThread, &cpu)) {
pt->pt_flags |= PT_NOCANCEL;
cpu.Rip = (uintptr_t)pthread_exit; cpu.Rip = (uintptr_t)pthread_exit;
cpu.Rdi = (uintptr_t)PTHREAD_CANCELED; cpu.Rdi = (uintptr_t)PTHREAD_CANCELED;
cpu.Rsp &= -16; cpu.Rsp &= -16;
*(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip; *(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip;
pt->abort_errno = ECANCELED;
unassert(SetThreadContext(hThread, &cpu)); unassert(SetThreadContext(hThread, &cpu));
__sig_cancel(pt, 0);
} }
} }
ResumeThread(hThread); ResumeThread(hThread);
} }
__sig_cancel(pt);
} }
} }

View file

@ -95,3 +95,15 @@ TEST(fionread, pipe) {
ASSERT_SYS(0, 0, close(pfds[1])); ASSERT_SYS(0, 0, close(pfds[1]));
ASSERT_SYS(0, 0, close(pfds[0])); ASSERT_SYS(0, 0, close(pfds[0]));
} }
TEST(fionread, eof_returnsZeroWithoutError) {
char buf[8];
int pfds[2];
int pending;
ASSERT_SYS(0, 0, pipe(pfds));
ASSERT_SYS(0, 0, close(pfds[1]));
ASSERT_SYS(0, 0, ioctl(pfds[0], FIONREAD, &pending));
ASSERT_EQ(0, pending);
ASSERT_SYS(0, 0, read(pfds[0], buf, 8));
ASSERT_SYS(0, 0, close(pfds[0]));
}