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
(HTM) 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);
+}