diff --git a/examples/ttyinfo.c b/examples/ttyinfo.c index 8a405da59..50b7c77e6 100644 --- a/examples/ttyinfo.c +++ b/examples/ttyinfo.c @@ -28,8 +28,6 @@ #include "libc/sysv/consts/termios.h" #include "libc/x/xsigaction.h" -__static_yoink("WinMainStdin"); - #define CTRL(C) ((C) ^ 0b01000000) #define WRITE(FD, SLIT) write(FD, SLIT, strlen(SLIT)) #define ENABLE_SAFE_PASTE "\e[?2004h" @@ -69,7 +67,7 @@ int rawmode(void) { memcpy(&t, &oldterm, sizeof(t)); 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 // t.c_cc[VINTR] = CTRL('G'); diff --git a/libc/calls/console.internal.h b/libc/calls/console.internal.h new file mode 100644 index 000000000..f4344682f --- /dev/null +++ b/libc/calls/console.internal.h @@ -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_ */ diff --git a/libc/calls/winstdin2.c b/libc/calls/fionread-nt.c similarity index 67% rename from libc/calls/winstdin2.c rename to libc/calls/fionread-nt.c index 45d6e74b5..01bb33e62 100644 --- a/libc/calls/winstdin2.c +++ b/libc/calls/fionread-nt.c @@ -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__ */ diff --git a/libc/calls/ioctl.c b/libc/calls/ioctl.c index 1de96fe88..cb67b658a 100644 --- a/libc/calls/ioctl.c +++ b/libc/calls/ioctl.c @@ -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(); } diff --git a/libc/calls/poll-nt.c b/libc/calls/poll-nt.c index 00c0e15ba..2e70adc07 100644 --- a/libc/calls/poll-nt.c +++ b/libc/calls/poll-nt.c @@ -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 diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index f8e36f67b..622d1132c 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -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; } diff --git a/libc/calls/sig.c b/libc/calls/sig.c index c06db87f1..48ddf6efc 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -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; } diff --git a/libc/calls/sig.internal.h b/libc/calls/sig.internal.h index 083b4c20d..8b0d8685c 100644 --- a/libc/calls/sig.internal.h +++ b/libc/calls/sig.internal.h @@ -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); diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index 4f285e4e0..587087830 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -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); diff --git a/libc/calls/sigwinch-nt.c b/libc/calls/sigwinch-nt.c deleted file mode 100644 index d629bef74..000000000 --- a/libc/calls/sigwinch-nt.c +++ /dev/null @@ -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__ */ diff --git a/libc/calls/struct/fd.internal.h b/libc/calls/struct/fd.internal.h index 85dcd71c5..27c4ec293 100644 --- a/libc/calls/struct/fd.internal.h +++ b/libc/calls/struct/fd.internal.h @@ -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_ */ diff --git a/libc/calls/struct/sigaction.internal.h b/libc/calls/struct/sigaction.internal.h index c6bc2796d..30e461a88 100644 --- a/libc/calls/struct/sigaction.internal.h +++ b/libc/calls/struct/sigaction.internal.h @@ -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) */ diff --git a/libc/calls/tcgetattr-nt.c b/libc/calls/tcgetattr-nt.c index 966277efa..80234e1f7 100644 --- a/libc/calls/tcgetattr-nt.c +++ b/libc/calls/tcgetattr-nt.c @@ -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; diff --git a/libc/calls/tcsetattr-nt.c b/libc/calls/tcsetattr-nt.c index 75febe649..9b1590daf 100644 --- a/libc/calls/tcsetattr-nt.c +++ b/libc/calls/tcsetattr-nt.c @@ -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__ */ diff --git a/libc/calls/winstdin1.c b/libc/calls/winstdin1.c deleted file mode 100644 index 74011bd90..000000000 --- a/libc/calls/winstdin1.c +++ /dev/null @@ -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; -} diff --git a/libc/calls/write-nt.c b/libc/calls/write-nt.c index 26d38b8cc..033281a93 100644 --- a/libc/calls/write-nt.c +++ b/libc/calls/write-nt.c @@ -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 diff --git a/libc/intrin/describetermios.c b/libc/intrin/describetermios.c index 921f09ed2..099b2cae8 100644 --- a/libc/intrin/describetermios.c +++ b/libc/intrin/describetermios.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/termios.h" #include "libc/calls/struct/termios.internal.h" +#include "libc/calls/ttydefaults.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" @@ -119,8 +120,15 @@ const char *(DescribeTermios)(char buf[N], ssize_t rc, {IEXTEN, "IEXTEN"}, // {EXTPROC, "EXTPROC"}, // }; - append(", .c_lflag=%s", - DescribeFlags(b128, 128, kLocal, ARRAYLEN(kLocal), "", tio->c_lflag)); + append(", " + ".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("}"); diff --git a/libc/intrin/g_fds.c b/libc/intrin/g_fds.c index 6545aba8d..e6d09efbe 100644 --- a/libc/intrin/g_fds.c +++ b/libc/intrin/g_fds.c @@ -119,6 +119,7 @@ textstartup void __init_fds(int argc, char **argv, char **envp) { } fds->p[1].flags = O_WRONLY | O_APPEND; fds->p[2].flags = O_WRONLY | O_APPEND; + __veof = CTRL('D'); __vintr = CTRL('C'); __vquit = CTRL('\\'); } diff --git a/libc/intrin/nomultics.c b/libc/intrin/nomultics.c index ee6e3815c..b66eae797 100644 --- a/libc/intrin/nomultics.c +++ b/libc/intrin/nomultics.c @@ -18,15 +18,10 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/nomultics.internal.h" -/** - * Controls ANSI prefix for log emissions. - * - * This should be true in raw tty mode repls. - * - * @see kprintf(), vflogf(), linenoise() - */ -char __replmode; -char __replstderr; -char __ttymagic; -char __vintr; -char __vquit; +unsigned char __replmode; +unsigned char __replstderr; +unsigned char __ttymagic; +unsigned char __veof; +unsigned char __vintr; +unsigned char __vquit; +unsigned char __vtime; diff --git a/libc/intrin/nomultics.internal.h b/libc/intrin/nomultics.internal.h index bda620d57..7f3caec3f 100644 --- a/libc/intrin/nomultics.internal.h +++ b/libc/intrin/nomultics.internal.h @@ -1,13 +1,16 @@ #ifndef COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ #define COSMOPOLITAN_LIBC_INTRIN_NOMULTICS_INTERNAL_H_ +#include "libc/calls/struct/timespec.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -extern char __replmode; -extern char __replstderr; -extern char __ttymagic; -extern char __vintr; -extern char __vquit; +extern unsigned char __replmode; +extern unsigned char __replstderr; +extern unsigned char __ttymagic; +extern unsigned char __veof; +extern unsigned char __vintr; +extern unsigned char __vquit; +extern unsigned char __vtime; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/nt/console.h b/libc/nt/console.h index c11000188..764ec1f65 100644 --- a/libc/nt/console.h +++ b/libc/nt/console.h @@ -49,8 +49,8 @@ bool32 ReadConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer, uint32_t nLength, uint32_t *lpNumberOfEventsRead); bool32 PeekConsoleInput(int64_t hConsoleInput, struct NtInputRecord *lpBuffer, uint32_t nLength, uint32_t *lpNumberOfEventsRead); -bool32 GetNumberOfConsoleInputEvent(int64_t hConsoleInput, - uint32_t *lpNumberOfEvents); +bool32 GetNumberOfConsoleInputEvents(int64_t hConsoleInput, + uint32_t *lpNumberOfEvents); bool32 ReadConsoleOutput(int64_t hConsoleOutput, struct NtCharInfo *lpBuffer, struct NtCoord dwBufferSize, struct NtCoord dwBufferCoord, diff --git a/libc/nt/process.h b/libc/nt/process.h index 392dda030..8be687029 100644 --- a/libc/nt/process.h +++ b/libc/nt/process.h @@ -56,7 +56,8 @@ int64_t OpenProcess(uint32_t dwDesiredAccess, bool32 bInheritHandle, uint32_t GetCurrentProcessId(void); /* %gs:0x40 */ uint32_t GetEnvironmentVariable(const char16_t *lpName, char16_t *lpBuffer, 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); bool32 GetProcessAffinityMask(int64_t hProcess, uint64_t *lpProcessAffinityMask, uint64_t *lpSystemAffinityMask); diff --git a/libc/nt/struct/inputrecord.h b/libc/nt/struct/inputrecord.h index f2b02a7e3..6cf415d65 100644 --- a/libc/nt/struct/inputrecord.h +++ b/libc/nt/struct/inputrecord.h @@ -9,17 +9,35 @@ struct NtKeyEventRecord { uint16_t wVirtualKeyCode; uint16_t wVirtualScanCode; union { - int16_t UnicodeChar; + uint16_t UnicodeChar; char AsciiChar; } uChar; 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 NtCoord dwMousePosition; uint32_t dwButtonState; +#define kNtFromLeft1stButtonPressed 0x0001 +#define kNtRightmostButtonPressed 0x0002 +#define kNtFromLeft2ndButtonPressed 0x0004 +#define kNtFromLeft3rdButtonPressed 0x0008 +#define kNtFromLeft4thButtonPressed 0x0010 uint32_t dwControlKeyState; uint32_t dwEventFlags; +#define kNtMouseMoved 0x0001 +#define kNtDoubleClick 0x0002 +#define kNtMouseWheeled 0x0004 +#define kNtMouseHwheeled 0x0008 }; struct NtWindowBufferSizeRecord { @@ -36,6 +54,11 @@ struct NtFocusEventRecord { struct NtInputRecord { uint16_t EventType; +#define kNtKeyEvent 0x0001 +#define kNtMouseEvent 0x0002 +#define kNtWindowBufferSizeEvent 0x0004 +#define kNtMenuEvent 0x0008 +#define kNtFocusEvent 0x0010 union { struct NtKeyEventRecord KeyEvent; struct NtMouseEventRecord MouseEvent; diff --git a/libc/proc/execve-nt.greg.c b/libc/proc/execve-nt.greg.c index 8eaa45661..b5ca7b231 100644 --- a/libc/proc/execve-nt.greg.c +++ b/libc/proc/execve-nt.greg.c @@ -154,9 +154,6 @@ keywords int sys_execve_nt(const char *program, char *const argv[], PurgeThread(pt->tib->tib_syshand); PurgeHandle(pt->semaphore); } - if (_weaken(__sigwinch_thread)) { - PurgeThread(*_weaken(__sigwinch_thread)); - } if (_weaken(__itimer)) { PurgeThread(_weaken(__itimer)->thread); } diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 084d86777..c33199965 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -55,12 +55,14 @@ __msabi extern typeof(FreeEnvironmentStrings) *const __imp_FreeEnvironmentString __msabi extern typeof(GetConsoleMode) *const __imp_GetConsoleMode; __msabi extern typeof(GetCurrentProcessId) *const __imp_GetCurrentProcessId; __msabi extern typeof(GetEnvironmentStrings) *const __imp_GetEnvironmentStringsW; +__msabi extern typeof(GetEnvironmentVariable) *const __imp_GetEnvironmentVariableW; __msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW; __msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; __msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; __msabi extern typeof(SetConsoleCP) *const __imp_SetConsoleCP; __msabi extern typeof(SetConsoleMode) *const __imp_SetConsoleMode; __msabi extern typeof(SetConsoleOutputCP) *const __imp_SetConsoleOutputCP; +__msabi extern typeof(SetEnvironmentVariable) *const __imp_SetEnvironmentVariableW; __msabi extern typeof(SetStdHandle) *const __imp_SetStdHandle; __msabi extern typeof(VirtualProtect) *const __imp_VirtualProtect; // clang-format on @@ -92,8 +94,7 @@ __funline char16_t *MyCommandLine(void) { // implements all win32 apis on non-windows hosts static abi long __oops_win32(void) { - assert(!"win32 api called on non-windows host"); - return 0; + notpossible; } // 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) { __imp_SetConsoleCP(kNtCpUtf8); __imp_SetConsoleOutputCP(kNtCpUtf8); - for (int i = 1; i <= 2; ++i) { + for (int i = 0; i <= 2; ++i) { uint32_t m; intptr_t h = __imp_GetStdHandle(kNtStdio[i]); __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)(); } DeduplicateStdioHandles(); - if (_weaken(WinMainStdin)) { - _weaken(WinMainStdin)(); - } if (_weaken(WinMainForked)) { _weaken(WinMainForked)(); } diff --git a/libc/thread/pthread_cancel.c b/libc/thread/pthread_cancel.c index 411380e79..14c54d8a0 100644 --- a/libc/thread/pthread_cancel.c +++ b/libc/thread/pthread_cancel.c @@ -111,23 +111,27 @@ static void _pthread_cancel_listen(void) { static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) { uint32_t old_suspend_count; - if ((pt->pt_flags & PT_ASYNC) && !(pt->pt_flags & PT_NOCANCEL)) { - if ((old_suspend_count = SuspendThread(hThread)) != -1u) { + if (!(pt->pt_flags & PT_NOCANCEL) && + (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) { struct NtContext cpu; cpu.ContextFlags = kNtContextControl | kNtContextInteger; if (GetThreadContext(hThread, &cpu)) { - pt->pt_flags |= PT_NOCANCEL; cpu.Rip = (uintptr_t)pthread_exit; cpu.Rdi = (uintptr_t)PTHREAD_CANCELED; cpu.Rsp &= -16; *(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip; + pt->abort_errno = ECANCELED; unassert(SetThreadContext(hThread, &cpu)); - __sig_cancel(pt, 0); } } ResumeThread(hThread); } + __sig_cancel(pt); } } diff --git a/test/libc/calls/ioctl_test.c b/test/libc/calls/ioctl_test.c index bd2f35749..a5912bf61 100644 --- a/test/libc/calls/ioctl_test.c +++ b/test/libc/calls/ioctl_test.c @@ -95,3 +95,15 @@ TEST(fionread, pipe) { ASSERT_SYS(0, 0, close(pfds[1])); 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])); +}