Mercurial > projects > pwm
diff pager.c @ 17:a08ef0674d8e
Page long output in interactive mode
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Sat, 12 Aug 2017 10:41:52 +0200 |
parents | |
children | 1e39a251cbe9 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pager.c Sat Aug 12 10:41:52 2017 +0200 @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "compat.h" + +#ifdef HAVE_ERR_H +#include <err.h> +#endif /* HAVE_ERR_H */ +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_QUEUE_H +#include <sys/queue.h> +#endif /* HAVE_SYS_QUEUE_H */ +#include <sys/stat.h> +#include <sys/types.h> +#include <termios.h> +#include <unistd.h> +#include <wchar.h> + +#include "pwm.h" +#include "pager.h" +#include "util.h" + +#ifndef TCSASOFT +#define TCSASOFT 0 +#endif /* !TCSASOFT */ + +#ifndef _NSIG +#ifdef NSIG +#define _NSIG NSIG +#else /* NSIG */ +#define _NSIG 128 +#endif /* NSIG */ +#endif /* !_NSIG */ + +#ifndef _PATH_TTY +#define _PATH_TTY "/dev/tty" +#endif + +struct pager { + TAILQ_HEAD(lines_head, lines_entry) lines_head; + FILE *fp; + size_t buf_size; + char *buf; +}; + +struct lines_entry { + TAILQ_ENTRY(lines_entry) entry; + char *line; +}; + +static volatile sig_atomic_t signals[_NSIG]; + +static void +sig_handler(int signo) +{ + signals[signo] = 1; +} + +static int +getch_prompt(const char *prompt) +{ + int retval = -1; + int signo; + int fd; + struct termios saved_term; + struct termios term; + struct sigaction sa; + struct sigaction saved_sa_hup; + struct sigaction saved_sa_int; + struct sigaction saved_sa_quit; + struct sigaction saved_sa_pipe; + struct sigaction saved_sa_alrm; + struct sigaction saved_sa_term; + struct sigaction saved_sa_tstp; + struct sigaction saved_sa_ttin; + struct sigaction saved_sa_ttou; + char c; + int need_restart = 0; + + printf("%s", prompt); + fflush(stdout); + +restart: + for (signo = 0; signo < _NSIG; signo++) { + signals[signo] = 0; + } + + fd = open(_PATH_TTY, O_RDONLY); + if (fd < 0) { + return (-1); + } + + if (tcgetattr(fd, &saved_term) != 0) { + close(fd); + return (-1); + } + memcpy(&term, &saved_term, sizeof (struct termios)); + + /* enter raw mode and turn echo off */ + term.c_lflag &= ~(ICANON | ECHO); + while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) < 0) && + (errno != EINTR)) { + continue; + } + + /* install temporary signal handler */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = sig_handler; + sigaction(SIGHUP, &sa, &saved_sa_hup); + sigaction(SIGINT, &sa, &saved_sa_int); + sigaction(SIGQUIT, &sa, &saved_sa_quit); + sigaction(SIGPIPE, &sa, &saved_sa_pipe); + sigaction(SIGALRM, &sa, &saved_sa_alrm); + sigaction(SIGTERM, &sa, &saved_sa_term); + sigaction(SIGTSTP, &sa, &saved_sa_tstp); + sigaction(SIGTTIN, &sa, &saved_sa_ttin); + sigaction(SIGTTOU, &sa, &saved_sa_ttou); + + /* read key */ + if (read(fd, &c, 1) == sizeof (c)) { + retval = c; + } + + /* restore previous mode and echo setting */ + do { + while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, + &saved_term) < 0) && (errno != EINTR)) { + continue; + } + } while ((tcgetattr(fd, &term) == 0) && + (term.c_lflag != saved_term.c_lflag)); + + /* restore previous signal handlers */ + sigaction(SIGHUP, &saved_sa_hup, &sa); + sigaction(SIGINT, &saved_sa_int, &sa); + sigaction(SIGQUIT, &saved_sa_quit, &sa); + sigaction(SIGPIPE, &saved_sa_pipe, &sa); + sigaction(SIGALRM, &saved_sa_alrm, &sa); + sigaction(SIGTERM, &saved_sa_term, &sa); + sigaction(SIGTSTP, &saved_sa_tstp, &sa); + sigaction(SIGTTIN, &saved_sa_ttin, &sa); + sigaction(SIGTTOU, &saved_sa_ttou, &sa); + + /* erase prompt */ + printf("\r%*s\r", (int)strlen(prompt), ""); + fflush(stdout); + + while ((close(fd) < 0) && (errno != EINTR)) { + continue; + } + + /* re-raise signals received */ + for (signo = 0; signo < _NSIG; signo++) { + if (signals[signo]) { + raise(signo); + if ((signo == SIGTSTP) || (signo == SIGTTIN) || + (signo == SIGTTOU)) { + need_restart = 1; + } + } + } + if (need_restart) { + goto restart; + } + + return (retval); +} + +struct pager * +pager_create(FILE *fp) +{ + struct pager *pager; + + pager = xmalloc(sizeof (struct pager)); + TAILQ_INIT(&pager->lines_head); + pager->fp = fp; + pager->buf_size = BUFSIZ; + pager->buf = xmalloc(BUFSIZ); + + return (pager); +} + +void +pager_destroy(struct pager *pager) +{ + struct lines_entry *entry; + struct lines_entry *entry_tmp; + + if (pager == NULL) { + return; + } + + TAILQ_FOREACH_SAFE(entry, &pager->lines_head, entry, entry_tmp) { + TAILQ_REMOVE(&pager->lines_head, entry, entry); + free(entry->line); + free(entry); + } + free(pager->buf); + free(pager); +} + +int +pager_vprintf(struct pager *pager, const char *fmt, va_list args) +{ + int len; + va_list args_new; + char *p; + size_t line_len; + struct lines_entry *entry; + size_t line_offset; + + va_copy(args_new, args); + + /* format multibyte string in buffer */ + len = vsnprintf(NULL, 0, fmt, args); + if (len < 0) { + err(1, "vsnprintf"); + } + + if (pager->buf_size < (size_t)len + 1) { + pager->buf_size = len + 1; + pager->buf = xrealloc(pager->buf, pager->buf_size); + } + + len = vsnprintf(pager->buf, pager->buf_size, fmt, args_new); + if (len < 0) { + err(1, "vsnprintf"); + } + + /* split buffer into lines */ + for (p = pager->buf; *p != '\0'; p += line_len) { + line_len = strcspn(p, "\n"); + if (p[line_len] == '\n') { + line_len++; + } + + entry = TAILQ_LAST(&pager->lines_head, lines_head); + line_offset = (entry != NULL) ? strlen(entry->line) : 0; + if ((entry != NULL) && (entry->line[line_offset - 1] != '\n')) { + /* + * append to the last line if it doesn't end with a + * newline + */ + entry->line = xrealloc(entry->line, line_offset + + line_len + 1); + memcpy(entry->line + line_offset, p, line_len); + entry->line[line_offset + line_len] = '\0'; + } else { + entry = xmalloc(sizeof (struct lines_entry)); + entry->line = xmalloc(line_len + 1); + memcpy(entry->line, p, line_len); + entry->line[line_len] = '\0'; + TAILQ_INSERT_TAIL(&pager->lines_head, entry, entry); + } + } + + va_end(args_new); + + return (len); +} + +int +pager_printf(struct pager *pager, const char *fmt, ...) +{ + int len; + va_list args; + + va_start(args, fmt); + len = pager_vprintf(pager, fmt, args); + va_end(args); + + return (len); +} + +static unsigned int +mbsnprint(unsigned int cols, const char *s, FILE *fp) +{ + const char *p = s; + unsigned int col = 0; + int mb_len; + wchar_t wc; + int width; + char mb_buf[MB_LEN_MAX]; + + while ((*p != '\n') && (*p != '\0') && (col < cols)) { + mb_len = mbtowc(&wc, p, MB_CUR_MAX); + if (mb_len != -1) { + width = wcwidth(wc); + if (width != -1) { + if (snprintf(mb_buf, sizeof (mb_buf), "%.*s", + mb_len, p) != mb_len) { + err(1, "snprintf"); + } + } else { + /* + * non-printable character, print + * replacement character + */ + width = 1; + if (snprintf(mb_buf, sizeof (mb_buf), + "\357\277\275") != 3) { + err(1, "snprintf"); + } + } + } else { + /* + * decoding failed, reset state and skip one + * byte + */ + mbtowc(NULL, NULL, MB_CUR_MAX); + mb_len = 1; + + /* print replacement character */ + width = 1; + if (snprintf(mb_buf, sizeof (mb_buf), + "\357\277\275") != 3) { + err(1, "snprintf"); + } + } + + p += mb_len; + col += width; + if (col <= cols) { + if (fputs(mb_buf, fp) == EOF) { + err(1, "fputs"); + } + } + } + + fputc('\n', fp); + fflush(fp); + + return (col); +} + +void +pager_show(struct pager *pager) +{ + int is_interactive; + unsigned int rows = 24; + unsigned int cols = 80; +#ifdef TIOCGWINSZ + struct winsize ws; +#endif /* TIOCGWINSZ */ + unsigned int row = 0; + struct lines_entry *entry; + + is_interactive = (isatty(STDIN_FILENO) && (pager->fp == stdout)); + +#ifdef TIOCGWINSZ + if (is_interactive) { + /* determine terminal size */ + if (ioctl(fileno(pager->fp), TIOCGWINSZ, &ws) == 0) { + rows = (ws.ws_row > 0) ? ws.ws_row : rows; + cols = (ws.ws_col > 0) ? ws.ws_col : cols; + } + } +#endif /* TIOCGWINSZ */ + + TAILQ_FOREACH(entry, &pager->lines_head, entry) { + if (is_interactive) { + mbsnprint(cols, entry->line, pager->fp); + row++; + if (row == rows - 1) { + getch_prompt("--More--"); + row = 0; + } + } else { + fprintf(pager->fp, "%s", entry->line); + fflush(pager->fp); + } + } +}