Mercurial > projects > pwm
view 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 source
/* * 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); } } }