diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2022-04-26 22:42:43 -0700 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2022-04-26 22:42:43 -0700 |
commit | 66ba7fdd8752bc46c9cf14605226f044d259ab05 (patch) | |
tree | 8412216d0e33ac15239a9db9befc991741188988 /pw.c | |
parent | 9c38bc2efd72beb59ba686a5e323bc7cc4fedcf1 (diff) | |
download | pw-66ba7fdd8752bc46c9cf14605226f044d259ab05.tar.gz pw-66ba7fdd8752bc46c9cf14605226f044d259ab05.tar.bz2 pw-66ba7fdd8752bc46c9cf14605226f044d259ab05.zip |
New project, Pipe Watch.
Diffstat (limited to 'pw.c')
-rw-r--r-- | pw.c | 365 |
1 files changed, 365 insertions, 0 deletions
@@ -0,0 +1,365 @@ +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/poll.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/time.h> + +enum status_flags { + stat_dirty = 1, + stat_eof = 2, + stat_susp = 4 +}; + +static void panic(const char *fmt, ...) +{ + va_list vl; + va_start (vl, fmt); + vfprintf(stderr, fmt, vl); + abort(); +} + +static void error(const char *fmt, ...) +{ + va_list vl; + va_start (vl, fmt); + vfprintf(stderr, fmt, vl); + exit(EXIT_FAILURE); +} + +static char *addch(char *line, size_t len, int ch) +{ + if (len + 2 > len) { + char *nline = realloc(line, len + 2); + + if (nline == 0) + panic("out of memory"); + + nline[len] = ch; + nline[len + 1] = 0; + return nline; + } + + panic("line overflow"); + abort(); +} + +static char *getln(FILE *stream) +{ + char *line = 0; + size_t len = 0; + + for (;;) { + int ch = getc(stream); + + if (ch == EOF) + return line; + + if (ch == '\n') { + if (line) + return line; + return addch(line, len, 0); + } + + if (ch == 127) { + line = addch(line, len++, '^'); + line = addch(line, len++, '?'); + } else if (ch < 32) { + line = addch(line, len++, '^'); + line = addch(line, len++, ch + 64); + } else { + line = addch(line, len++, ch); + } + } +} + +static void usage(const char *name) +{ + fprintf(stderr, + "\nUsage: %s [options]\n\n" + "-i realnum poll interval (s)\n" + "-l realnum long update interval (s)\n" + "-n integer display size (# of lines)\n" + "-d do not quit on end-of-input\n\n" + "For a full description, see the manual page.\n\n", + name); + exit(EXIT_FAILURE); +} + +static void drawline(const char *line, int hpos, int columns) +{ + size_t len = strlen(line); + + if ((size_t) hpos <= len) { + if (hpos) { + line += hpos; + len -= hpos; + putchar('>'); + } + if (len < (size_t) columns - 1) { + puts(line); + } else { + for (int i = 0; i < columns - 2; i++) + putchar(line[i]); + puts("<"); + } + } else { + puts(">"); + } +} + +static void drawstatus(unsigned stat) +{ + if ((stat & (stat_eof | stat_susp))) { + if ((stat & stat_eof)) + printf("EOF "); + if ((stat & stat_susp)) + printf("SUSPENDED "); + fflush(stdout); + } +} + +static void redraw(char **circbuf, int nlines, int hpos, + int columns, unsigned stat) +{ + printf("\r\033[%dA\033[J", nlines); + for (int i = 0; i < nlines; i++) + drawline(circbuf[i], hpos, columns); + drawstatus(stat); +} + +static void clear_cur_line() +{ + printf("\r\033[J"); +} + +int main(int argc, char **argv) +{ + char *line; + FILE *tty = fopen("/dev/tty", "r+"); + int maxlines = 15, nlines = 0; + int poll_interval = 1000; + int long_interval = 10000; + int opt; + int fd = fileno(stdin); + int ttyfd = tty ? fileno(tty) : -1; + char **circbuf; + struct termios tty_saved, tty_new; + struct winsize ws = { 0 }; + int columns = 80; + enum kbd_state { kbd_cmd, kbd_esc, kbd_bkt, kbd_exit }; + int auto_quit = 1; + int exit_status = EXIT_FAILURE; + + if (fd < 0) + panic("unable to obtain input file descriptor"); + + if (ttyfd < 0) + panic("unable to open /dev/tty"); + + while ((opt = getopt(argc, argv, "ni:l:d")) != -1) { + switch (opt) { + case 'n': + maxlines = atoi(optarg); + break; + case 'i': case 'l': + { + double interval = atof(optarg) * 1000; + if (interval > (double) INT_MAX) + error("maximum interval is %f\n", INT_MAX / 1000.0); + if (opt == 'i') + poll_interval = interval; + else + long_interval = interval; + } + break; + case 'd': + auto_quit = 0; + break; + default: + usage(argv[0]); + } + } + + if (maxlines <= 0 || maxlines > 1000) + error("%d is an unreasonable number of lines to display", maxlines); + + if ((circbuf = malloc(sizeof *circbuf * maxlines)) == 0) + panic("out of memory"); + + if (isatty(fd)) { + while ((line = getln(stdin))) { + puts(line); + free(line); + } + + return 0; + } + + if (ioctl(ttyfd, TIOCGWINSZ, &ws) == 0 && ws.ws_row != 0) { + if (maxlines >= ws.ws_row) + maxlines = ws.ws_row - 1; + columns = ws.ws_col; + } + + if (tcgetattr(ttyfd, &tty_saved) < 0) + panic("unable to get TTY parameters"); + + tty_new = tty_saved; + + tty_new.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | + INLCR | IGNCR | ICRNL); + tty_new.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + + tty_new.c_cc[VMIN] = 1; + tty_new.c_cc[VTIME] = 0; + + if (tcsetattr(ttyfd, TCSANOW, &tty_new) < 0) + panic("unable to set TTY parameters"); + + setvbuf(tty, NULL, _IONBF, 0); + + for (unsigned stat = stat_dirty, hpos = 0, kbd_state = kbd_cmd, lasttime = ~0U; + kbd_state != kbd_exit ;) + { + struct pollfd pe[2] = { + { .fd = ttyfd, .events = POLLIN | POLLHUP | POLLERR }, + { .fd = fd, .events = POLLIN | POLLHUP | POLLERR }, + }; + + { + struct timeval tv; + unsigned now; + + gettimeofday(&tv, NULL); + now = (((unsigned) tv.tv_sec)%1000000)*1000 + tv.tv_usec/1000; + if (lasttime == ~0U || now - lasttime > (unsigned) long_interval) { + if ((stat & stat_dirty) && nlines == maxlines) + redraw(circbuf, nlines, hpos, columns, stat); + lasttime = now; + stat &= ~stat_dirty; + } + } + + if (poll(pe, ((stat & stat_eof)) ? 1 : 2, poll_interval) <= 0) { + if ((stat & stat_dirty) && nlines == maxlines) { + redraw(circbuf, nlines, hpos, columns, stat); + stat &= ~stat_dirty; + } + kbd_state = kbd_cmd; + } else { + if ((stat & stat_eof) == 0 && pe[1].revents) { + if ((line = getln(stdin))) { + if (nlines == maxlines) { + if ((stat & stat_susp)) { + free(line); + } else { + free(circbuf[0]); + memmove(circbuf, circbuf + 1, (nlines - 1) * sizeof *circbuf); + circbuf[nlines - 1] = line; + stat |= stat_dirty; + } + } else { + circbuf[nlines++] = line; + clear_cur_line(); + drawline(line, hpos, columns); + drawstatus(stat); + } + } else { + if (auto_quit) + kbd_state = kbd_exit; + else + stat |= stat_eof; + redraw(circbuf, nlines, hpos, columns, stat); + stat |= stat_eof; + stat &= ~stat_dirty; + if (!ferror(stdin)) + exit_status = 0; + continue; + } + } + + if ((pe[0].revents)) { + int ch = getc(tty); + + fakecmd: + switch (kbd_state) { + case kbd_cmd: + switch (ch) { + case 'q': case 3: + kbd_state = kbd_exit; + if ((stat & (stat_eof | stat_susp))) { + clear_cur_line(); + fflush(stdout); + } + break; + case 'h': + if (hpos >= 8) { + hpos -= 8; + stat |= stat_dirty; + } + break; + case 'l': + if (hpos < 10000) { + hpos += 8; + stat |= stat_dirty; + } + break; + case '0': + hpos = 0; + stat |= stat_dirty; + break; + case ' ': + if ((stat & stat_eof) == 0) + stat |= (stat_dirty | stat_susp); + break; + case 13: + stat &= ~stat_susp; + stat |= stat_dirty; + break; + case 27: + kbd_state = kbd_esc; + break; + } + break; + case kbd_esc: + kbd_state = kbd_cmd; + if (ch == '[') + kbd_state = kbd_bkt; + break; + case kbd_bkt: + kbd_state = kbd_cmd; + switch (ch) { + case 'D': + ch = 'h'; + goto fakecmd; + case 'C': + ch = 'l'; + goto fakecmd; + case 'H': + ch = '0'; + goto fakecmd; + } + break; + case kbd_exit: + break; + } + + if ((stat & stat_dirty)) { + redraw(circbuf, nlines, hpos, columns, stat); + stat &= ~stat_dirty; + } + } + } + } + + if (tcsetattr(ttyfd, TCSANOW, &tty_saved) < 0) + panic("unable to restore TTY parameters"); + return exit_status; +} |