Add vtv-viewer. - vtv-tools - virtual terminal video tools DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit 991a6f94bb058979f28763ecd33b5920cad034d5 DIR parent 121db131a875338f494bb1ab98e6dd7245cea1f6 HTML Author: Troels Henriksen <athas@sigkill.dk> Date: Tue, 22 Aug 2023 18:30:18 +0200 Add vtv-viewer. Diffstat: M .gitignore | 1 + M Makefile | 2 +- M man/vtv-player.1 | 2 +- A man/vtv-viewer.1 | 41 +++++++++++++++++++++++++++++++ A src/vtv-viewer.c | 147 +++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 2 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -1 +1,2 @@ bin/vtv-from-ff +bin/vtv-viewer DIR diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ MANPREFIX ?= ${PREFIX}/share/man CFLAGS?=-O -Wall -Wextra -pedantic CC?=cc -all: bin/vtv-from-ff +all: bin/vtv-from-ff bin/vtv-viewer bin/%: src/%.c $(CC) -o $@ $< $(CFLAGS) DIR diff --git a/man/vtv-player.1 b/man/vtv-player.1 @@ -16,7 +16,7 @@ .Sh DESCRIPTION .Bd -filled .Nm -plays a VTV file in the terminal. Loops until manually terminated. +Plays a VTV file in the terminal. Loops until manually terminated. . .Sh OPTIONS .Bl -tag -width Ds DIR diff --git a/man/vtv-viewer.1 b/man/vtv-viewer.1 @@ -0,0 +1,41 @@ +.Dd August 22, 2023 +.Dt VTV-VIEWER 1 +.OS +. +.sh NAME +.Nm vtv-viewer +.Nd Interactively view frames of VTV file. +. +.Sh SYNOPSIS +.Nm +.Bk +.Ar FILE +.Ek +. +.Sh DESCRIPTION +.Bd -filled +.Nm +Interactively shows frames of a VTV file in the terminal. This can be +useful for trying to figure out the intended frame size of a VTV. +. +.Sh COMMANDS +. +The following keyboard commands are supported. +.Bl -tag -width Ds +.It h +Go to previous frame. +.It l +Go to next frame. +.It j +Decrease frame size by one line. +.It k +Increase frame size by one line. +.El +. +.Sh BUGS +The VTV frame is emitted directly to the terminal, which can cause +user interface corruption if it writes to the parts of the screen used +by vtv-viewer itself. +. +.Sh LICENSE +The vtv-tools are released under the GPLv3 or later. DIR diff --git a/src/vtv-viewer.c b/src/vtv-viewer.c @@ -0,0 +1,147 @@ +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> +#include <errno.h> + +struct termios orig_termios; + +void cooked_mode() { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); + printf("\033[?25h"); +} + +void raw_mode() { + printf("\033[?25l"); + + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(cooked_mode); + + struct termios raw = orig_termios; + raw.c_iflag &= ~(IXON); + raw.c_lflag &= ~(ECHO | ICANON); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +void move(int x, int y) { printf("\033[%d;%dH", y, x); } +void home() { printf("\033[;H"); } +void clear_screen() { printf("\033[2J"); } +void clear_line() { printf("\033[2K"); } +void def() { printf("\033[0m"); } + +void fg_rgb(uint8_t r, uint8_t g, uint8_t b) { + printf("\033[38;2;%d;%d;%dm", r, g, b); +} + +void bg_rgb(uint8_t r, uint8_t g, uint8_t b) { + printf("\033[48;2;%d;%d;%dm", r, g, b); +} + +int read_lines(FILE* f, char*** lines_out, size_t *num_lines_out) { + size_t n, num_lines = 0, capacity = 10; + char** lines = calloc(capacity, sizeof(char*)); + ssize_t len; + + while ((len = getline(&lines[num_lines], &n, f)) > 0) { + lines[num_lines][len-1] = 0; // Strip newline. + if (++num_lines == capacity) { + capacity *= 2; + lines = reallocarray(lines, capacity, sizeof(char*)); + for (unsigned int i = num_lines; i < capacity; i++) { + lines[i] = NULL; + } + } + } + + *lines_out = lines; + *num_lines_out = num_lines; + return 0; +} + +struct { + int frame; + int lines_per_frame; + char** lines; + int num_lines; +} state; + +void show_status() { + printf("Frame (h/l): %5d Framesize (j/k): %5d\n", + state.frame, state.lines_per_frame); +} + +void show_frame() { + for (int i = 0; i < state.lines_per_frame; i++) { + int j = state.frame*state.lines_per_frame + i; + if (j < state.num_lines) { + puts(state.lines[j]); + } else { + puts(" MISSING LINE"); + } + } +} + +int view(char** lines, size_t num_lines) { + raw_mode(); + + state.frame = 0; + state.lines_per_frame = 25; + state.lines = lines; + state.num_lines = num_lines; + + while (1) { + home(); + def(); + clear_screen(); + show_frame(); + move(0, state.lines_per_frame+1); + def(); + // It is intentional that the status is at the bottom, as VTV + // files may assume that they are drawn from line 0. + show_status(); + switch(getchar()) { + case 'q': + return 0; + case 'h': + if (state.frame != 0) { state.frame--; } + break; + case 'l': + state.frame = (state.frame+1); + break; + case 'j': + if (state.lines_per_frame != 0) {state.lines_per_frame--; } + break; + case 'k': + state.lines_per_frame++; + break; + } + } +} + +int main(int argc, char** argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s FILE\n", argv[0]); + return 1; + } + + FILE* f = fopen(argv[1], "r"); + if (f == NULL) { + fprintf(stderr, "%s: cannot open %s: %s\n", + argv[0], argv[1], strerror(errno)); + } + + char** lines; + size_t num_lines; + if (read_lines(f, &lines, &num_lines) != 0) { + fprintf(stderr, "%s: failed to read from %s: %s\n", + argv[0], argv[1], strerror(errno)); + exit(1); + } + return view(lines, num_lines); +}