From e7a87862e6553873a53e90d982be6f3ef08a5ed2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 3 Nov 2020 13:29:45 -0500 Subject: bar: Implement terminal status bars --- bar.c | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 bar.c (limited to 'bar.c') diff --git a/bar.c b/bar.c new file mode 100644 index 0000000..9cb08d3 --- /dev/null +++ b/bar.c @@ -0,0 +1,262 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2020 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "bar.h" +#include "dstring.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct bfs_bar { + int fd; + volatile sig_atomic_t width; + volatile sig_atomic_t height; +}; + +/** The global status bar instance. */ +static struct bfs_bar the_bar = { + .fd = -1, +}; + +/** Get the terminal size, if possible. */ +static int bfs_bar_getsize(struct bfs_bar *bar) { +#ifdef TIOCGWINSZ + struct winsize ws; + if (ioctl(bar->fd, TIOCGWINSZ, &ws) != 0) { + return -1; + } + + bar->width = ws.ws_col; + bar->height = ws.ws_row; + return 0; +#else + errno = ENOTSUP; + return -1; +#endif +} + +/** Async Signal Safe puts(). */ +static int ass_puts(int fd, const char *str) { + size_t len = strlen(str); + + while (len > 0) { + ssize_t ret = write(fd, str, len); + if (ret <= 0) { + return -1; + } + str += ret; + len -= ret; + } + + return 0; +} + +/** Number of decimal digits needed for terminal sizes. */ +#define ITOA_DIGITS ((sizeof(unsigned short) * CHAR_BIT + 2) / 3) + +/** Async Signal Safe itoa(). */ +static char *ass_itoa(char *str, unsigned int n) { + char *end = str + ITOA_DIGITS; + *end = '\0'; + + char *c = end; + do { + *--c = '0' + (n % 10); + n /= 10; + } while (n); + + size_t len = end - c; + memmove(str, c, len + 1); + return str + len; +} + +/** Update the size of the scrollable region. */ +static int bfs_bar_resize(struct bfs_bar *bar) { + char esc_seq[12 + ITOA_DIGITS] = + "\0337" // DECSC: Save cursor + "\033[;"; // DECSTBM: Set scrollable region + + // DECSTBM takes the height as the second argument + char *ptr = esc_seq + strlen(esc_seq); + ptr = ass_itoa(ptr, bar->height - 1); + + strcpy(ptr, + "r" // DECSTBM + "\0338" // DECRC: Restore the cursor + "\033[J" // ED: Erase display from cursor to end + ); + + return ass_puts(bar->fd, esc_seq); +} + +#ifdef SIGWINCH +/** SIGWINCH handler. */ +static void sighand_winch(int sig) { + int error = errno; + + bfs_bar_getsize(&the_bar); + bfs_bar_resize(&the_bar); + + errno = error; +} +#endif + +/** Reset the scrollable region and hide the bar. */ +static int bfs_bar_reset(struct bfs_bar *bar) { + return ass_puts(bar->fd, + "\0337" // DECSC: Save cursor + "\033[r" // DECSTBM: Reset scrollable region + "\0338" // DECRC: Restore cursor + "\033[J" // ED: Erase display from cursor to end + ); +} + +/** Signal handler for process-terminating signals. */ +static void sighand_reset(int sig) { + bfs_bar_reset(&the_bar); + raise(sig); +} + +/** Register sighand_reset() for a signal. */ +static void reset_before_death_by(int sig) { + struct sigaction sa = { + .sa_handler = sighand_reset, + .sa_flags = SA_RESETHAND, + }; + sigemptyset(&sa.sa_mask); + sigaction(sig, &sa, NULL); +} + +/** printf() to the status bar with a single write(). */ +BFS_FORMATTER(2, 3) +static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { + va_list args; + va_start(args, format); + char *str = dstrvprintf(format, args); + va_end(args); + + if (!str) { + return -1; + } + + int ret = ass_puts(bar->fd, str); + dstrfree(str); + return ret; +} + +struct bfs_bar *bfs_bar_show(void) { + int error; + + if (the_bar.fd >= 0) { + error = EBUSY; + goto fail; + } + + char term[L_ctermid]; + ctermid(term); + if (strlen(term) == 0) { + error = ENOTTY; + goto fail; + } + + the_bar.fd = open(term, O_RDWR | O_CLOEXEC); + if (the_bar.fd < 0) { + error = errno; + goto fail; + } + + if (bfs_bar_getsize(&the_bar) != 0) { + error = errno; + goto fail_close; + } + + reset_before_death_by(SIGABRT); + reset_before_death_by(SIGINT); + reset_before_death_by(SIGPIPE); + reset_before_death_by(SIGQUIT); + reset_before_death_by(SIGTERM); + +#ifdef SIGWINCH + struct sigaction sa = { + .sa_handler = sighand_winch, + .sa_flags = SA_RESTART, + }; + sigemptyset(&sa.sa_mask); + sigaction(SIGWINCH, &sa, NULL); +#endif + + bfs_bar_printf(&the_bar, + "\n" // Make space for the bar + "\0337" // DECSC: Save cursor + "\033[;%ur" // DECSTBM: Set scrollable region + "\0338" // DECRC: Restore cursor + "\033[1A", // CUU: Move cursor up 1 row + (unsigned int)(the_bar.height - 1) + ); + + return &the_bar; + +fail_close: + close(the_bar.fd); + the_bar.fd = -1; +fail: + errno = error; + return NULL; +} + +unsigned int bfs_bar_width(const struct bfs_bar *bar) { + return bar->width; +} + +int bfs_bar_update(struct bfs_bar *bar, const char *str) { + return bfs_bar_printf(bar, + "\0337" // DECSC: Save cursor + "\033[%u;0f" // HVP: Move cursor to row, column + "\033[K" // EL: Erase line + "\033[7m" // SGR reverse video + "%s" + "\033[27m" // SGR reverse video off + "\0338", // DECRC: Restore cursor + (unsigned int)bar->height, + str + ); +} + +void bfs_bar_hide(struct bfs_bar *bar) { + if (!bar) { + return; + } + + signal(SIGABRT, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); +#ifdef SIGWINCH + signal(SIGWINCH, SIG_DFL); +#endif + + bfs_bar_reset(bar); + + close(bar->fd); + bar->fd = -1; +} -- cgit v1.2.3