aboutsummaryrefslogtreecommitdiffstats
path: root/pw.c
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2022-04-26 22:42:43 -0700
committerKaz Kylheku <kaz@kylheku.com>2022-04-26 22:42:43 -0700
commit66ba7fdd8752bc46c9cf14605226f044d259ab05 (patch)
tree8412216d0e33ac15239a9db9befc991741188988 /pw.c
parent9c38bc2efd72beb59ba686a5e323bc7cc4fedcf1 (diff)
downloadpw-66ba7fdd8752bc46c9cf14605226f044d259ab05.tar.gz
pw-66ba7fdd8752bc46c9cf14605226f044d259ab05.tar.bz2
pw-66ba7fdd8752bc46c9cf14605226f044d259ab05.zip
New project, Pipe Watch.
Diffstat (limited to 'pw.c')
-rw-r--r--pw.c365
1 files changed, 365 insertions, 0 deletions
diff --git a/pw.c b/pw.c
new file mode 100644
index 0000000..8d79183
--- /dev/null
+++ b/pw.c
@@ -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;
+}