URI: 
       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);
       +}