diff options
| author | Mattias Andrée <m@maandree.se> | 2026-06-06 12:19:28 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-06-06 12:19:28 +0200 |
| commit | 82110edd4f6c1a4460b4e5b42de9d413acf1bfd2 (patch) | |
| tree | 1078a7a12265d6fb14f8bce78cdfe46cf5618a0e /blkshowr.c | |
| parent | Third commit (diff) | |
| download | blkseekr-82110edd4f6c1a4460b4e5b42de9d413acf1bfd2.tar.gz blkseekr-82110edd4f6c1a4460b4e5b42de9d413acf1bfd2.tar.bz2 blkseekr-82110edd4f6c1a4460b4e5b42de9d413acf1bfd2.tar.xz | |
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'blkshowr.c')
| -rw-r--r-- | blkshowr.c | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/blkshowr.c b/blkshowr.c new file mode 100644 index 0000000..bbb0ea0 --- /dev/null +++ b/blkshowr.c @@ -0,0 +1,402 @@ +/* See LICENSE file for copyright and license details. */ +#include <termios.h> +#include <libsimple.h> +#include <libsimple-arg.h> +#include <libterminput.h> + +USAGE("[-b block-size] file [block-list]"); + + +#define IS_KEY(INPUT, KEY)\ + ((INPUT)->keypress.key == LIBTERMINPUT_SYMBOL &&\ + toupper((INPUT)->keypress.symbol[0]) == toupper((KEY)) &&\ + !(INPUT)->keypress.symbol[1]) + +#define IS_CTRL_KEY(INPUT, KEY)\ + (((INPUT)->keypress.mods & (enum libterminput_mod)~LIBTERMINPUT_SHIFT) == LIBTERMINPUT_CTRL && IS_KEY((INPUT), (KEY))) + + +static void configure_terminal(void); +static void restore_terminal(void); + + +static size_t blksize = 4ul << 10; +static off_t *blocks = NULL; +static size_t blocks_size = 0u; +static size_t nblocks = 0u; +static int max_block_len; + +static volatile sig_atomic_t term_resized = 0; +static struct termios term_attributes_stdin; +static int term_attributes_stdin_saved = 0; +static int term_entered_submode_stdout = 0; +static int ttyfd = -1; + +static size_t term_width; +static size_t term_height; +static int exiting = 0; +static int interrupt_deferred = 0; + + +static void +do_redraw(void) +{ + dprintf(ttyfd, "\033[H\033[2J"); + + /* TODO draw everything */ +} + + +static void +handle_keyboard_input(union libterminput_input *input) +{ + if (input->type == LIBTERMINPUT_KEYPRESS) { + if (IS_CTRL_KEY(input, 'Z')) { + restore_terminal(); + raise(SIGTSTP); + configure_terminal(); + redraw: + interrupt_deferred = 1; + term_resized = 1; + } else if (IS_CTRL_KEY(input, 'L')) { + goto redraw; + } else if (IS_CTRL_KEY(input, 'Q')) { + exiting = 1; + } + } +} + + +static void +enter_subterminal(void) +{ + dprintf(ttyfd, + "\033[?1049h" /* enter subterminal */ + "\033[?25l" /* hide cursor */ + "\033[H\033[2J" /* clear terminal */ + ); + term_entered_submode_stdout = 1; +} + + +static void +exit_subterminal(void) +{ + if (term_entered_submode_stdout) { + term_entered_submode_stdout = 0; + dprintf(ttyfd, + "\033[H\033[2J" /* clear terminal */ + "\033[?25h" /* show cursor */ + "\033[?1049l" /* exit subterminal */ + ); + } +} + + +static void +set_term_attributes(void) +{ + struct termios new_term_attributes_stdin; + + if (tcgetattr(ttyfd, &term_attributes_stdin)) + eprintf("tcgetattr /dev/tty:"); + term_attributes_stdin_saved = 1; + + memcpy(&new_term_attributes_stdin, &term_attributes_stdin, sizeof(term_attributes_stdin)); + new_term_attributes_stdin.c_iflag &= (tcflag_t)~(IXON | IXANY | IXOFF); + new_term_attributes_stdin.c_lflag &= (tcflag_t)~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); + + if (tcsetattr(ttyfd, TCSANOW, &new_term_attributes_stdin)) + weprintf("tcsetattr /dev/tty TCSANOW:"); +} + + +static void +restore_term_attributes(void) +{ + if (term_attributes_stdin_saved) { + term_attributes_stdin_saved = 0; + if (tcsetattr(ttyfd, TCSAFLUSH, &term_attributes_stdin)) + weprintf("tcsetattr /dev/tty TCSAFLUSH:"); + } +} + + +static void +configure_terminal(void) +{ + set_term_attributes(); + enter_subterminal(); +} + + +static void +restore_terminal(void) +{ + restore_term_attributes(); + exit_subterminal(); +} + + +static void +cleanup(void) +{ + restore_terminal(); +} + + +static void +term_resized_callback(int signo) +{ + (void) signo; + term_resized = 1; +} + + +static void +subscribe_term_resize(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = &term_resized_callback; + + if (sigaction(SIGWINCH, &sa, NULL)) + eprintf("sigaction SIGWINCH:"); +} + + +static void +update_term_size(void) +{ + struct winsize ws; + + if (ioctl(ttyfd, TIOCGWINSZ, &ws)) + eprintf("ioclt /dev/tty TIOCGWINSZ:"); + + term_width = ws.ws_col; + term_height = ws.ws_row; +} + + +#if defined(__GNUC__) +__attribute__((__pure__)) +#endif +static uintmax_t +parse_uint_arg(const char *s, uintmax_t min, uintmax_t max) +{ + uintmax_t v = 0u, d; + if (!*s) + usage(); + while ('0' <= *s && *s <= '9') { + d = (uintmax_t)(*s++ - '0'); + if (d > 9u || v > (max - d) / 10u) /* we know that max>d */ + usage(); + v = v * 10u + d; + } + if (*s || v < min) + usage(); + return v; +} + + +#if defined(__GNUC__) +__attribute__((__pure__)) +#endif +static int +get_fd(const char *s) +{ + int fd, d; + + if (!strcmp(s, "/dev/stdin")) return STDIN_FILENO; + if (!strcmp(s, "/dev/stdout")) return STDOUT_FILENO; + if (!strcmp(s, "/dev/stderr")) return STDERR_FILENO; + + if (!strstarts(s, "/proc/self/fd/")) + s = &s[sizeof("/proc/self/fd/") - 1u]; + else if (!strstarts(s, "/dev/fd/")) + s = &s[sizeof("/dev/fd/") - 1u]; + else + return -1; + + if ('0' > *s || *s > '9') + return -1; + + fd = 0; + while ('0' <= *s && *s <= '9') { + d = *s++ - '0'; + if (d < 0 || d > 9 || fd > (INT_MAX - d) / 10) + return -1; + fd = fd * 10 + d; + } + return fd; +} + + +static int +offpcmp(const void *a_, const void *b_) +{ + const off_t *a = a_, *b = b_; + return *a < *b ? -1 : *a > *b; +} + + +static void +add_to_block_list(off_t block) +{ + if (nblocks == blocks_size) + blocks = ereallocarray(blocks, blocks_size += 64u, sizeof(*blocks)); + blocks[nblocks++] = block; +} + + +static void +read_block_list(int fd, const char *fname) +{ + char buf[4096]; + ssize_t r; + size_t i, n; + off_t v = 0, d; + int have_v = 0; + + for (;;) { + r = read(fd, buf, sizeof(buf)); + if (r <= 0) { + if (!r) + break; + if (errno == EINTR) + continue; + eprintf("read %s:", fname); + } + n = (size_t)r; + + for (i = 0u; i < n; i++) { + if (buf[i] == '\n') { + if (!have_v) + continue; + have_v = 0; + add_to_block_list(v); + } else if ('0' <= buf[i] && buf[i] <= '9') { + have_v = 1; + d = (off_t)(buf[i] - '0'); + if (v > (OFF_MAX - d) / 10) + goto inval; + v = v * 10 + d; + } else { + inval: + eprintf("%s: invalid file content", fname); + } + } + } + if (!have_v) + add_to_block_list(v); + if (!nblocks) + goto inval; + + qsort(blocks, nblocks, sizeof(*blocks), &offpcmp); + + for (i = n = 1u; i < nblocks; i++) + if (blocks[i] != blocks[n - 1]) + blocks[n++] = blocks[i]; + nblocks = n; +} + + +int +main(int argc, char *argv[]) +{ + struct libterminput_state input_ctx; + union libterminput_input input; + const char *path, *listpath; + int fd, listfd; + + ARGBEGIN { + case 'b': + blksize = (size_t)parse_uint_arg(ARG(), 1u, SIZE_MAX); + break; + default: + usage(); + } ARGEND; + + if (argc < 1 || argc > 2) + usage(); + + path = argv[0]; + if (!*path) + usage(); + + listpath = argv[1]; + if (!listpath || !strcmp(listpath, "-")) + listpath = "/dev/stdin"; + else if (!*listpath) + usage(); + + /* Open terminal and ensure process is running in the foreground */ + ttyfd = open("/dev/tty", O_RDONLY); + if (ttyfd < 0) + eprintf("open /dev/tty O_RDONLY:"); + if (getpgrp() != tcgetpgrp(ttyfd)) + eprintf("process not running in the foreground"); + + /* Open block list */ + listfd = get_fd(listpath); + if (listfd < 0) { + listfd = open(listpath, O_RDONLY); + if (listfd < 0) + eprintf("open %s O_RDONLY", listpath); + } + + /* Open file to display */ + fd = open(path, O_RDONLY); + if (fd < 0) + eprintf("open %s O_RDONLY", path); + + /* Advise the kernel about the access pattern */ + posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM); + + /* Set up cleanup on exit */ + atexit(&cleanup); + libsimple_eprintf_preprint = &cleanup; + + /* Read block list */ + read_block_list(listfd, listpath); + close(listfd); + max_block_len = snprintf("%ji", (uintmax_t)blocks[nblocks - 1u]); + if (max_block_len < 1) + eprintf("snprintf %%ji:") + + /* UI loop */ + update_term_size(); + subscribe_term_resize(); + configure_terminal(); + memset(&input_ctx, 0, sizeof(input_ctx)); + do { + if (interrupt_deferred) { + interrupt_deferred = 0; + if (term_resized) { + term_resized = 0; + update_term_size(); + do_redraw(); + } + } + + switch (libterminput_read(ttyfd, &input, &input_ctx)) { + case 1: + handle_keyboard_input(&input); + break; + case 0: + exiting = 1; + break; + default: + if (errno == EINTR) + interrupt_deferred = 1; + else + eprintf("libterminput <stdin>:"); + break; + } + } while (!exiting); + + close(fd); + close(ttyfd); + return 0; +} |
