From 250376f60aeebbc215359e51ce003f0e104e7986 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sun, 21 Feb 2021 12:01:39 +0100 Subject: Rename to read-quickly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 2 +- Makefile | 16 +- README | 9 +- config.mk | 4 +- read-quickly.1 | 92 +++++++++++ read-quickly.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rq.1 | 92 ----------- rq.c | 482 --------------------------------------------------------- 8 files changed, 589 insertions(+), 590 deletions(-) create mode 100644 read-quickly.1 create mode 100644 read-quickly.c delete mode 100644 rq.1 delete mode 100644 rq.c diff --git a/.gitignore b/.gitignore index 5347c08..138741c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ *~ *.o *.su -/rq +/read-quickly diff --git a/Makefile b/Makefile index eedff0a..1121ff9 100644 --- a/Makefile +++ b/Makefile @@ -3,26 +3,26 @@ CONFIGFILE = config.mk include $(CONFIGFILE) -all: rq +all: read-quickly -rq: rq.o +read-quickly: read-quickly.o $(CC) -o $@ $@.o $(LDFLAGS) .c.o: $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -install: rq +install: read-quickly mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1" - cp -- rq "$(DESTDIR)$(PREFIX)/bin/$(COMMAND)" - cp -- rq.1 "$(DESTDIR)$(MANPREFIX)/man1/$(COMMAND).1" + cp -- read-quickly "$(DESTDIR)$(PREFIX)/bin/" + cp -- read-quickly.1 "$(DESTDIR)$(MANPREFIX)/man1/" uninstall: - -rm -f -- "$(DESTDIR)$(PREFIX)/$(COMMAND)" - -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/$(COMMAND).1" + -rm -f -- "$(DESTDIR)$(PREFIX)/read-quickly" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/read-quickly.1" clean: - -rm -rf -- rq *.o + -rm -rf -- read-quickly *.o .SUFFIXES: .SUFFIXES: .o .c diff --git a/README b/README index 0208765..f164712 100644 --- a/README +++ b/README @@ -1,8 +1,8 @@ NAME - rq - read quickly + read-quickly - read quickly SYNOPSIS - rq [FILE] + read-quickly [FILE] DESCRIPTION Displays a plain-text file word-by-word in the middle @@ -18,13 +18,14 @@ DESCRIPTION If no file is specified, or if '-' i specified, stdin will be paged. - rq uses a method called rapid serial visual presentation. + read-quickly uses a method called rapid serial visual + presentation. OPTIONS None. ENVIRONMENT - RQ_RATE + READ_QUICKLY_RATE The rate at which the words will be printed. Defaults to 120 words per minute. diff --git a/config.mk b/config.mk index 7997417..bc56b5c 100644 --- a/config.mk +++ b/config.mk @@ -2,7 +2,5 @@ PREFIX = /usr MANPREFIX = $(PREFIX)/share/man CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -CFLAGS = -std=c99 -O2 -Wall -pedantic +CFLAGS = -std=c99 -O2 -Wall LDFLAGS = -s - -COMMAND = rq diff --git a/read-quickly.1 b/read-quickly.1 new file mode 100644 index 0000000..50ccb7d --- /dev/null +++ b/read-quickly.1 @@ -0,0 +1,92 @@ +.TH READ-QUICKLY 1 READ-QUICKLY +.SH NAME +read-quickly \- read quickly +.SH SYNOPSIS +.B read-quickly +.RI [ file ] +.SH DESCRIPTION +Displays a plain-text file word-by-word in the middle +of the terminal. Words are automatically paged in a +specifiable rate, by default 2 words per second. +This is a bit slow, you should turn it up when +gradually until your performance has been optimised. +Repeated words will be indicated by alternating reverse +video highlighting. +.PP +Escape sequences are printed as-is. +.PP +If no file is specified, or if \- i specified, +stdin will be paged. +.PP +.B read-quickly +uses a method called rapid serial visual presentation. +.SH OPTIONS +None. +.SH ENVIRONMENT +.TP +.B READ_QUICKLY_RATE +The rate at which the words will be printed. +Defaults to 120 words per minute. +.br +.br +This should a positive integer, optionally +followed by a unit. If no unit is specified, +words per minute will be used. Valid units +are (case-insensitive): +.RS 14 +.TP +.B wpm +.TQ +.B w/m +.TQ +.B m +.TQ +.B wpmin +.TQ +.B w/min +.TQ +.B /min +Words per minute. +.PP +.TP +.B wpsec +.TQ +.B w/sec +.TQ +.B sec +.TQ +.B wps +.TQ +.B w/s +.TQ +.B /s +.TQ +.B Hz +Words per second. +.Re +.fi +.SH COMMANDS +.TP +.B \+ +Increase word rate. +.TP +.B \- +Decrease word rate. +.TP +.B p +Pause/resume. +.TP +.B q +Exit. +.TP +.BR left ,\ up +Go to the previous word. +.TP +.BR right ,\ down +Go to next previous word. +.SH RATIONALE +This should be obvious. +.SH SEE ALSO +No similar or otherwise related work known. +Please inform me if you know any. There probably +is a bunch. diff --git a/read-quickly.c b/read-quickly.c new file mode 100644 index 0000000..acd52a6 --- /dev/null +++ b/read-quickly.c @@ -0,0 +1,482 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/** + * The default word rate. + */ +#ifndef DEFAULT_RATE +# define DEFAULT_RATE 120 /* 2 hz */ +#endif + +/** + * Delta-value of rate increment and rate decrement. + */ +#ifndef RATE_DELTA +# define RATE_DELTA 10 /* 10/min */ +#endif + + + +/** + * The a word. + */ +struct word { + /** + * The word. + */ + const char *word; + + /** + * Should reverse video be applied? + */ + int reverse_video; +}; + +/** + * The name of the process. + */ +static const char *argv0; + +/** + * Have the terminal been resized? + */ +static volatile sig_atomic_t caught_sigwinch = 1; + +/** + * Has the timer expired? + */ +static volatile sig_atomic_t caught_sigalrm = 0; + +/** + * The width of the terminal. + */ +static size_t width = 80; + +/** + * The height of the terminal. + */ +static size_t height = 30; + +/** + * The number of words. + */ +static size_t word_count = 0; + +/** + * All loaded words. Refer to `word_count` + * for the number of contained words. + */ +static struct word *words; + + + +/** + * Signal handler for SIGWINCH. + * Invoked when the terminal resizes. + */ +static void +sigwinch(int signo) +{ + caught_sigwinch = 1; + (void) signo; +} + + +/** + * Signal handler for SIGALRM. + * Invoked when the timer expires. + */ +static void +sigalrm(int signo) +{ + caught_sigalrm = 1; + (void) signo; +} + + +/** + * Get the size of the terminal. + */ +static void +get_terminal_size(void) +{ + struct winsize winsize; + if (caught_sigwinch) { + caught_sigwinch = 0; + while (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) + if (errno != EINTR) + return; + height = winsize.ws_row; + width = winsize.ws_col; + } +} + + +/** + * Get the selected word rate by reading + * the environment variable READ_QUICKLY_RATE. + * + * @return The rate in words per minute. + */ +static long +get_word_rate(void) +{ + char *s; + long r; + + errno = 0; + s = getenv("READ_QUICKLY_RATE"); + if (!s || !*s || !isdigit(*s)) + return DEFAULT_RATE; + + r = strtol(s, &s, 10); + if (r <= 0) + return DEFAULT_RATE; + while (*s == ' ') + s++; + + if (!*s) r *= 1; + else if (!strcasecmp(s, "wpm")) r *= 1; + else if (!strcasecmp(s, "w/m")) r *= 1; + else if (!strcasecmp(s, "/m")) r *= 1; + else if (!strcasecmp(s, "wpmin")) r *= 1; + else if (!strcasecmp(s, "w/min")) r *= 1; + else if (!strcasecmp(s, "/min")) r *= 1; + else if (!strcasecmp(s, "wps")) r *= 60; + else if (!strcasecmp(s, "w/s")) r *= 60; + else if (!strcasecmp(s, "/s")) r *= 60; + else if (!strcasecmp(s, "wpsec")) r *= 60; + else if (!strcasecmp(s, "w/sec")) r *= 60; + else if (!strcasecmp(s, "/sec")) r *= 60; + else if (!strcasecmp(s, "hz")) r *= 60; + else + return DEFAULT_RATE; + + return r; +} + + +/** + * Count the number of character in a string. + * + * @param s The string. + * @return The number of characters in `s`. + */ +static size_t +display_len(const char *s) +{ + size_t r = 0; + wchar_t wc; + int len, w; + for (; *s; s += len) { + len = mbtowc(&wc, s, SIZE_MAX); + if (len <= 0) + break; + w = wcwidth(wc); + if (w < 0) + break; + r += (size_t)w; + } + return r; +} + + +/** + * Load the file and do some preparsing. + * + * @param fd The file descriptor to the file, -1 to clean up instead. + * @return 0 on success, -1 on error. + */ +static int +load_file(int fd) +{ + static char *buffer = NULL; + size_t ptr = 0; + size_t size = 0; + void *new; + int saved_errno; + char *s; + char *end; + size_t i; + ssize_t n; + + if (fd == -1) + return free(buffer), 0; + + /* Load file. */ + for (;;) { + if (ptr == size) { + size = size ? size << 1 : 8 << 10; + new = realloc(buffer, size); + if (!new) + goto fail; + buffer = new; + } + n = read(fd, buffer + ptr, size - ptr); + if (n <= 0) { + if (!n) + break; + if (errno == EINTR) + continue; + goto fail; + } + ptr += (size_t)n; + } + if (!buffer) + return 0; + new = realloc(buffer, ptr + 2); + if (!new) + goto fail; + buffer = new; + buffer[ptr++] = '\0'; + buffer[ptr++] = '\0'; + + /* Split words. */ + size = 0; + for (s = buffer; *s; s = &end[1]) { + if (word_count == size) { + size = size ? size << 1 : 512; + new = realloc(words, size * sizeof(*words)); + if (!new) + goto fail; + words = new; + } + while (isspace(*s)) + s++; + end = strpbrk(s, " \f\n\r\t\v"); + if (!end) + end = strchr(s, '\0'); + *end = '\0'; + words[word_count].word = s; + words[word_count].reverse_video = 0; + word_count++; + } + + /* Figure out which words should have reverse video. */ + for (i = 1; i < word_count; i++) + if (!strcmp(words[i].word, words[i - 1].word)) + words[i].reverse_video = words[i - 1].reverse_video ^ 1; + + return 0; + +fail: + saved_errno = errno; + free(buffer); + buffer = NULL; + errno = saved_errno; + return -1; +} + + +/** + * Display a file word by word. + * + * @param ttyfd File descriptor for reading from the terminal. + * @param rate The number of words per minute to display. + * @return 0 on success, -1 on error. + */ +static int +display_file(int ttyfd, long rate) +{ +#define SET_RATE \ + (interval.it_value.tv_usec = 60000000L / rate, \ + interval.it_value.tv_sec = interval.it_value.tv_usec / 1000000L, \ + interval.it_value.tv_usec %= 1000000L) + + ssize_t n; + int timer_set = 1; + char c; + size_t i; + struct itimerval interval; + + memset(&interval, 0, sizeof(interval)); + + SET_RATE; + for (i = 0; i < word_count; i++) { + if (setitimer(ITIMER_REAL, &interval, NULL)) + goto fail; + rewait: + n = read(ttyfd, &c, sizeof(c)); + if (n < 0) { + if (errno != EINTR) + goto fail; + c = 0; + } else if (n == 0) { + break; + } + switch (c) { + case '+': /* plus */ + case '-': /* hyphen */ + rate += c == '+' ? RATE_DELTA : -RATE_DELTA; + rate = rate <= 0 ? 1 : rate; + SET_RATE; + goto rewait; + case 'p': /* P */ + if (timer_set) + memset(&interval, 0, sizeof(interval)); + else + SET_RATE; + if (setitimer(ITIMER_REAL, &interval, NULL)) + goto fail; + timer_set ^= 1; + goto rewait; + case 'q': /* Q */ + goto done; + case 'B': /* down */ + case 'C': /* right */ + break; + case 'A': /* up */ + case 'D': /* left */ + i = i < 2 ? 0 : i - 2; + break; + case 0: + if (!caught_sigalrm) + goto rewait; + caught_sigalrm = 0; + break; + default: + goto rewait; + } + + get_terminal_size(); + if (fprintf(stdout, "\033[H\033[2J\033[%zu;%zuH%s%s%s", + (height + 1) / 2, + (width - display_len(words[i].word)) / 2 + 1, + words[i].reverse_video ? "\033[7m" : "", + words[i].word, + words[i].reverse_video ? "\033[27m" : "") < 0) + goto fail; + if (fflush(stdout)) + goto fail; + } + + if (setitimer(ITIMER_REAL, &interval, NULL)) + goto fail; + (void) read(ttyfd, &c, sizeof(c)); + +done: + return 0; + +fail: + return -1; +} + + +int +main(int argc, char *argv[]) +{ + long rate = get_word_rate(); + int fd = -1, ttyfd = -1, tty_configured = 0; + struct termios stty, saved_stty; + struct stat _attr; + struct sigaction sa; + + /* Parse arguments. */ + argv0 = argv ? (argc--, *argv++) : "read-quickly"; + if (argc && argv[0][0] == '-') { + if (argv[0][1] == '-' && !argv[0][2]) { + argc--; + argv++; + } else if (argv[0][1]) { + goto usage; + } + } + if (argc > 1) + goto usage; + + /* Check that we have a stdout. */ + if (fstat(STDOUT_FILENO, &_attr)) + if (errno == EBADF) + goto fail; + + /* Open file. */ + if (argc && strcmp(*argv, "-")) { + fd = open(*argv, O_RDONLY); + if (fd < 0) + goto fail; + } else { + fd = STDIN_FILENO; + } + + /* Load file. */ + if (load_file(fd)) + goto fail; + + /* We do not need the file anymore. */ + close(fd); + fd = -1; + + /* Get a readable file descriptor for the controlling terminal. */ + ttyfd = open("/dev/tty", O_RDONLY); + if (ttyfd < 0) + goto fail; + + /* Configure terminal. */ + if (fprintf(stdout, "\033[?1049h\033[?25l") < 0) + goto fail; + if (fflush(stdout)) + goto fail; + if (tcgetattr(ttyfd, &stty)) + goto fail; + saved_stty = stty; + stty.c_lflag &= (tcflag_t)~(ICANON | ECHO | ISIG); + if (tcsetattr(ttyfd, TCSAFLUSH, &stty)) + goto fail; + tty_configured = 1; + + /* Display file. */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigalrm; + sigaction(SIGALRM, &sa, NULL); + sa.sa_handler = sigwinch; + sigaction(SIGWINCH, &sa, NULL); + if (display_file(ttyfd, rate)) + goto fail; + + /* Restore terminal configurations. */ + tcsetattr(ttyfd, TCSAFLUSH, &saved_stty); + fprintf(stdout, "\033[?25h\033[?1049l"); + fflush(stdout); + tty_configured = 0; + + free(words); + load_file(-1); + close(ttyfd); + return 0; + +fail: + perror(argv0); + free(words); + load_file(-1); + if (tty_configured) { + tcsetattr(ttyfd, TCSAFLUSH, &saved_stty); + fprintf(stdout, "\033[?25h\033[?1049l"); + fflush(stdout); + } + if (fd >= 0) + close(fd); + if (ttyfd >= 0) + close(ttyfd); + return 1; + +usage: + fprintf(stderr, "usage: %s [file].\n", argv0); + return 1; +} diff --git a/rq.1 b/rq.1 deleted file mode 100644 index 86298dc..0000000 --- a/rq.1 +++ /dev/null @@ -1,92 +0,0 @@ -.TH RQ 1 RQ -.SH NAME -rq \- read quickly -.SH SYNOPSIS -.B rq -.RI [ file ] -.SH DESCRIPTION -Displays a plain-text file word-by-word in the middle -of the terminal. Words are automatically paged in a -specifiable rate, by default 2 words per second. -This is a bit slow, you should turn it up when -gradually until your performance has been optimised. -Repeated words will be indicated by alternating reverse -video highlighting. -.PP -Escape sequences are printed as-is. -.PP -If no file is specified, or if \- i specified, -stdin will be paged. -.PP -.B rq -uses a method called rapid serial visual presentation. -.SH OPTIONS -None. -.SH ENVIRONMENT -.TP -.B RQ_RATE -The rate at which the words will be printed. -Defaults to 120 words per minute. -.br -.br -This should a positive integer, optionally -followed by a unit. If no unit is specified, -words per minute will be used. Valid units -are (case-insensitive): -.RS 14 -.TP -.B wpm -.TQ -.B w/m -.TQ -.B m -.TQ -.B wpmin -.TQ -.B w/min -.TQ -.B /min -Words per minute. -.PP -.TP -.B wpsec -.TQ -.B w/sec -.TQ -.B sec -.TQ -.B wps -.TQ -.B w/s -.TQ -.B /s -.TQ -.B Hz -Words per second. -.Re -.fi -.SH COMMANDS -.TP -.B \+ -Increase word rate. -.TP -.B \- -Decrease word rate. -.TP -.B p -Pause/resume. -.TP -.B q -Exit. -.TP -.BR left ,\ up -Go to the previous word. -.TP -.BR right ,\ down -Go to next previous word. -.SH RATIONALE -This should be obvious. -.SH SEE ALSO -No similar or otherwise related work known. -Please inform me if you know any. There probably -is a bunch. diff --git a/rq.c b/rq.c deleted file mode 100644 index fbcec13..0000000 --- a/rq.c +++ /dev/null @@ -1,482 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * The default word rate. - */ -#ifndef DEFAULT_RATE -# define DEFAULT_RATE 120 /* 2 hz */ -#endif - -/** - * Delta-value of rate increment and rate decrement. - */ -#ifndef RATE_DELTA -# define RATE_DELTA 10 /* 10/min */ -#endif - - - -/** - * The a word. - */ -struct word { - /** - * The word. - */ - const char *word; - - /** - * Should reverse video be applied? - */ - int reverse_video; -}; - -/** - * The name of the process. - */ -static const char *argv0; - -/** - * Have the terminal been resized? - */ -static volatile sig_atomic_t caught_sigwinch = 1; - -/** - * Has the timer expired? - */ -static volatile sig_atomic_t caught_sigalrm = 0; - -/** - * The width of the terminal. - */ -static size_t width = 80; - -/** - * The height of the terminal. - */ -static size_t height = 30; - -/** - * The number of words. - */ -static size_t word_count = 0; - -/** - * All loaded words. Refer to `word_count` - * for the number of contained words. - */ -static struct word *words; - - - -/** - * Signal handler for SIGWINCH. - * Invoked when the terminal resizes. - */ -static void -sigwinch(int signo) -{ - caught_sigwinch = 1; - (void) signo; -} - - -/** - * Signal handler for SIGALRM. - * Invoked when the timer expires. - */ -static void -sigalrm(int signo) -{ - caught_sigalrm = 1; - (void) signo; -} - - -/** - * Get the size of the terminal. - */ -static void -get_terminal_size(void) -{ - struct winsize winsize; - if (caught_sigwinch) { - caught_sigwinch = 0; - while (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) - if (errno != EINTR) - return; - height = winsize.ws_row; - width = winsize.ws_col; - } -} - - -/** - * Get the selected word rate by reading - * the environment variable RQ_RATE. - * - * @return The rate in words per minute. - */ -static long -get_word_rate(void) -{ - char *s; - long r; - - errno = 0; - s = getenv("RQ_RATE"); - if (!s || !*s || !isdigit(*s)) - return DEFAULT_RATE; - - r = strtol(s, &s, 10); - if (r <= 0) - return DEFAULT_RATE; - while (*s == ' ') - s++; - - if (!*s) r *= 1; - else if (!strcasecmp(s, "wpm")) r *= 1; - else if (!strcasecmp(s, "w/m")) r *= 1; - else if (!strcasecmp(s, "/m")) r *= 1; - else if (!strcasecmp(s, "wpmin")) r *= 1; - else if (!strcasecmp(s, "w/min")) r *= 1; - else if (!strcasecmp(s, "/min")) r *= 1; - else if (!strcasecmp(s, "wps")) r *= 60; - else if (!strcasecmp(s, "w/s")) r *= 60; - else if (!strcasecmp(s, "/s")) r *= 60; - else if (!strcasecmp(s, "wpsec")) r *= 60; - else if (!strcasecmp(s, "w/sec")) r *= 60; - else if (!strcasecmp(s, "/sec")) r *= 60; - else if (!strcasecmp(s, "hz")) r *= 60; - else - return DEFAULT_RATE; - - return r; -} - - -/** - * Count the number of character in a string. - * - * @param s The string. - * @return The number of characters in `s`. - */ -static size_t -display_len(const char *s) -{ - size_t r = 0; - wchar_t wc; - int len, w; - for (; *s; s += len) { - len = mbtowc(&wc, s, SIZE_MAX); - if (len <= 0) - break; - w = wcwidth(wc); - if (w < 0) - break; - r += (size_t)w; - } - return r; -} - - -/** - * Load the file and do some preparsing. - * - * @param fd The file descriptor to the file, -1 to clean up instead. - * @return 0 on success, -1 on error. - */ -static int -load_file(int fd) -{ - static char *buffer = NULL; - size_t ptr = 0; - size_t size = 0; - void *new; - int saved_errno; - char *s; - char *end; - size_t i; - ssize_t n; - - if (fd == -1) - return free(buffer), 0; - - /* Load file. */ - for (;;) { - if (ptr == size) { - size = size ? size << 1 : 8 << 10; - new = realloc(buffer, size); - if (!new) - goto fail; - buffer = new; - } - n = read(fd, buffer + ptr, size - ptr); - if (n <= 0) { - if (!n) - break; - if (errno == EINTR) - continue; - goto fail; - } - ptr += (size_t)n; - } - if (!buffer) - return 0; - new = realloc(buffer, ptr + 2); - if (!new) - goto fail; - buffer = new; - buffer[ptr++] = '\0'; - buffer[ptr++] = '\0'; - - /* Split words. */ - size = 0; - for (s = buffer; *s; s = &end[1]) { - if (word_count == size) { - size = size ? size << 1 : 512; - new = realloc(words, size * sizeof(*words)); - if (!new) - goto fail; - words = new; - } - while (isspace(*s)) - s++; - end = strpbrk(s, " \f\n\r\t\v"); - if (!end) - end = strchr(s, '\0'); - *end = '\0'; - words[word_count].word = s; - words[word_count].reverse_video = 0; - word_count++; - } - - /* Figure out which words should have reverse video. */ - for (i = 1; i < word_count; i++) - if (!strcmp(words[i].word, words[i - 1].word)) - words[i].reverse_video = words[i - 1].reverse_video ^ 1; - - return 0; - -fail: - saved_errno = errno; - free(buffer); - buffer = NULL; - errno = saved_errno; - return -1; -} - - -/** - * Display a file word by word. - * - * @param ttyfd File descriptor for reading from the terminal. - * @param rate The number of words per minute to display. - * @return 0 on success, -1 on error. - */ -static int -display_file(int ttyfd, long rate) -{ -#define SET_RATE \ - (interval.it_value.tv_usec = 60000000L / rate, \ - interval.it_value.tv_sec = interval.it_value.tv_usec / 1000000L, \ - interval.it_value.tv_usec %= 1000000L) - - ssize_t n; - int timer_set = 1; - char c; - size_t i; - struct itimerval interval; - - memset(&interval, 0, sizeof(interval)); - - SET_RATE; - for (i = 0; i < word_count; i++) { - if (setitimer(ITIMER_REAL, &interval, NULL)) - goto fail; - rewait: - n = read(ttyfd, &c, sizeof(c)); - if (n < 0) { - if (errno != EINTR) - goto fail; - c = 0; - } else if (n == 0) { - break; - } - switch (c) { - case '+': /* plus */ - case '-': /* hyphen */ - rate += c == '+' ? RATE_DELTA : -RATE_DELTA; - rate = rate <= 0 ? 1 : rate; - SET_RATE; - goto rewait; - case 'p': /* P */ - if (timer_set) - memset(&interval, 0, sizeof(interval)); - else - SET_RATE; - if (setitimer(ITIMER_REAL, &interval, NULL)) - goto fail; - timer_set ^= 1; - goto rewait; - case 'q': /* Q */ - goto done; - case 'B': /* down */ - case 'C': /* right */ - break; - case 'A': /* up */ - case 'D': /* left */ - i = i < 2 ? 0 : i - 2; - break; - case 0: - if (!caught_sigalrm) - goto rewait; - caught_sigalrm = 0; - break; - default: - goto rewait; - } - - get_terminal_size(); - if (fprintf(stdout, "\033[H\033[2J\033[%zu;%zuH%s%s%s", - (height + 1) / 2, - (width - display_len(words[i].word)) / 2 + 1, - words[i].reverse_video ? "\033[7m" : "", - words[i].word, - words[i].reverse_video ? "\033[27m" : "") < 0) - goto fail; - if (fflush(stdout)) - goto fail; - } - - if (setitimer(ITIMER_REAL, &interval, NULL)) - goto fail; - (void) read(ttyfd, &c, sizeof(c)); - -done: - return 0; - -fail: - return -1; -} - - -int -main(int argc, char *argv[]) -{ - long rate = get_word_rate(); - int fd = -1, ttyfd = -1, tty_configured = 0; - struct termios stty, saved_stty; - struct stat _attr; - struct sigaction sa; - - /* Parse arguments. */ - argv0 = argv ? (argc--, *argv++) : "rq"; - if (argc && argv[0][0] == '-') { - if (argv[0][1] == '-' && !argv[0][2]) { - argc--; - argv++; - } else if (argv[0][1]) { - goto usage; - } - } - if (argc > 1) - goto usage; - - /* Check that we have a stdout. */ - if (fstat(STDOUT_FILENO, &_attr)) - if (errno == EBADF) - goto fail; - - /* Open file. */ - if (argc && strcmp(*argv, "-")) { - fd = open(*argv, O_RDONLY); - if (fd < 0) - goto fail; - } else { - fd = STDIN_FILENO; - } - - /* Load file. */ - if (load_file(fd)) - goto fail; - - /* We do not need the file anymore. */ - close(fd); - fd = -1; - - /* Get a readable file descriptor for the controlling terminal. */ - ttyfd = open("/dev/tty", O_RDONLY); - if (ttyfd < 0) - goto fail; - - /* Configure terminal. */ - if (fprintf(stdout, "\033[?1049h\033[?25l") < 0) - goto fail; - if (fflush(stdout)) - goto fail; - if (tcgetattr(ttyfd, &stty)) - goto fail; - saved_stty = stty; - stty.c_lflag &= (tcflag_t)~(ICANON | ECHO | ISIG); - if (tcsetattr(ttyfd, TCSAFLUSH, &stty)) - goto fail; - tty_configured = 1; - - /* Display file. */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sigalrm; - sigaction(SIGALRM, &sa, NULL); - sa.sa_handler = sigwinch; - sigaction(SIGWINCH, &sa, NULL); - if (display_file(ttyfd, rate)) - goto fail; - - /* Restore terminal configurations. */ - tcsetattr(ttyfd, TCSAFLUSH, &saved_stty); - fprintf(stdout, "\033[?25h\033[?1049l"); - fflush(stdout); - tty_configured = 0; - - free(words); - load_file(-1); - close(ttyfd); - return 0; - -fail: - perror(argv0); - free(words); - load_file(-1); - if (tty_configured) { - tcsetattr(ttyfd, TCSAFLUSH, &saved_stty); - fprintf(stdout, "\033[?25h\033[?1049l"); - fflush(stdout); - } - if (fd >= 0) - close(fd); - if (ttyfd >= 0) - close(ttyfd); - return 1; - -usage: - fprintf(stderr, "usage: %s [file].\n", argv0); - return 1; -} -- cgit v1.2.3-70-g09d2