#if 0 /*─────────────────────────────────────────────────────────────────╗ │ To the extent possible under law, Justine Tunney has waived │ │ all copyright and related or neighboring rights to this file, │ │ as it is written in the following disclaimers: │ │ • http://unlicense.org/ │ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "dsp/tty/tty.h" #include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/termios.h" #include "libc/errno.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/runtime/runtime.h" #include "libc/sock/select.h" #include "libc/stdio/dprintf.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/termios.h" #include "libc/x/xsigaction.h" #define CTRL(C) ((C) ^ 0b01000000) #define WRITE(FD, SLIT) write(FD, SLIT, strlen(SLIT)) #define ENABLE_SAFE_PASTE "\e[?2004h" #define ENABLE_MOUSE_TRACKING "\e[?1000;1002;1015;1006h" #define DISABLE_MOUSE_TRACKING "\e[?1000;1002;1015;1006l" #define PROBE_DISPLAY_SIZE "\e7\e[9979;9979H\e[6n\e8" char code[512]; int infd, outfd; struct winsize wsize; struct termios oldterm; volatile bool killed, resized, resurrected; void OnKilled(int sig) { killed = true; } void OnResize(int sig) { resized = true; } void OnResurrect(int sig) { resized = true; resurrected = true; } void RestoreTty(void) { WRITE(outfd, DISABLE_MOUSE_TRACKING); tcsetattr(outfd, TCSANOW, &oldterm); } int EnableRawMode(void) { static bool once; struct termios t; if (!once) { if (!tcgetattr(outfd, &oldterm)) { atexit(RestoreTty); } else { perror("tcgetattr"); } once = true; } memcpy(&t, &oldterm, sizeof(t)); t.c_cc[VMIN] = 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'); // it can be restored using // (set-quit-char (logxor ?C 0100)) // but we are able to polyfill the remapping on windows // please note this is a moot point b/c ISIG is cleared t.c_iflag &= ~(INPCK | ISTRIP | PARMRK | INLCR | IGNCR | ICRNL | IXON | IGNBRK | BRKINT); t.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHONL); t.c_cflag &= ~(CSIZE | PARENB); t.c_oflag |= OPOST | ONLCR; t.c_cflag |= CS8; t.c_iflag |= IUTF8; if (tcsetattr(outfd, TCSANOW, &t)) { perror("tcsetattr"); } /* WRITE(outfd, ENABLE_MOUSE_TRACKING); */ /* WRITE(outfd, ENABLE_SAFE_PASTE); */ /* WRITE(outfd, PROBE_DISPLAY_SIZE); */ return 0; } void GetTtySize(void) { if (tcgetwinsize(outfd, &wsize) != -1) { dprintf(outfd, "termios says terminal size is %hu×%hu\r\n", wsize.ws_col, wsize.ws_row); } else { perror("tcgetwinsize"); } } const char *describemouseevent(int e) { static char buf[64]; buf[0] = 0; if (e & 0x10) { strcat(buf, " ctrl"); } if (e & 0x40) { strcat(buf, " wheel"); if (e & 0x01) { strcat(buf, " down"); } else { strcat(buf, " up"); } } else { switch (e & 3) { case 0: strcat(buf, " left"); break; case 1: strcat(buf, " middle"); break; case 2: strcat(buf, " right"); break; default: __builtin_unreachable(); } if (e & 0x20) { strcat(buf, " drag"); } else if (e & 0x04) { strcat(buf, " up"); } else { strcat(buf, " down"); } } return buf + 1; } // change the code above to enable ISIG if you want to trigger this // then press ctrl-c or ctrl-\ in your pseudoteletypewriter console void OnSignalThatWontEintrRead(int sig) { dprintf(outfd, "got %s (read()ing will SA_RESTART; try CTRL-D to exit)\n", strsignal(sig)); } void OnSignalThatWillEintrRead(int sig) { dprintf(outfd, "got %s (read() operation will be aborted)\n", strsignal(sig)); } int main(int argc, char *argv[]) { int e, c, y, x, n, yn, xn; infd = 0; outfd = 1; infd = outfd = open("/dev/tty", O_RDWR); signal(SIGINT, OnSignalThatWontEintrRead); sigaction(SIGQUIT, &(struct sigaction){.sa_handler = OnSignalThatWillEintrRead}, 0); sigaction(SIGTERM, &(struct sigaction){.sa_handler = OnKilled}, 0); sigaction(SIGWINCH, &(struct sigaction){.sa_handler = OnResize}, 0); sigaction(SIGCONT, &(struct sigaction){.sa_handler = OnResurrect}, 0); EnableRawMode(); GetTtySize(); while (!killed) { if (resurrected) { dprintf(outfd, "WE LIVE AGAIN "); resurrected = false; } if (resized) { dprintf(outfd, "SIGWINCH "); GetTtySize(); resized = false; } bzero(code, sizeof(code)); if ((n = read(infd, code, sizeof(code))) == -1) { if (errno == EINTR) { dprintf(outfd, "read() was interrupted\n"); continue; } perror("read"); exit(1); } if (!n) { dprintf(outfd, "got stdin eof\n"); exit(0); } dprintf(outfd, "%`'.*s (got %d) ", n, code, n); if (iscntrl(code[0]) && !code[1]) { dprintf(outfd, "is CTRL-%c a.k.a. ^%c\r\n", CTRL(code[0]), CTRL(code[0])); if (code[0] == CTRL('C') || code[0] == CTRL('D')) break; } else if (startswith(code, "\e[") && endswith(code, "R")) { yn = 1, xn = 1; sscanf(code, "\e[%d;%dR", &yn, &xn); dprintf(outfd, "inband signalling says terminal size is %d×%d\r\n", xn, yn); } else if (startswith(code, "\e[<") && (endswith(code, "m") || endswith(code, "M"))) { e = 0, y = 1, x = 1; sscanf(code, "\e[<%d;%d;%d%c", &e, &y, &x, &c); dprintf(outfd, "mouse %s at %d×%d\r\n", describemouseevent(e | (c == 'm') << 2), x, y); } else { dprintf(outfd, "\r\n"); } } return 0; }