/usr/local/bin/analogterm2 -WS -w80 -h25 -e '/bin/cat /usr/local/share/analogterm2/ds-stat-setup.txt; clear; fortune'
Exercise left to the reader for a window to launch wargames(6). :)
+
+
+Recording
+=========
+
+If AnalogTerm ][ is started with the -R option to specify a FIFO
+file to issue video frames to, it is possible for a recording
+application to read them there. It is important for both to use
+the same FIFO and the same resolution. When started, AT2 reports
+its resolution at the upper left corner of the terminal. The
+xwininfo(1) command can also be used to obtain the dimensions of
+the window. The recording application should already be listening
+on the FIFO before recording begins. AnalogTerm2 can then be told
+when to begin and stop recording, using the following special ATC
+sequences, respectively: [?658467;65552;1h and [?658467;65552;0h.
+Utility aliases for these exist in at2-aliases.sh, atrecord and
+atnorecord. You may want to adapt record.sh for your needs. In
+the future, AT2 may manage its own ffmpeg(1) process or directly
+use the library.
+
+Example with the provided script, depending on ffmpeg(1):
+
+$ analogterm2 -W -w80 -h50 -R /tmp/at2recfifo1 &
+$ record.sh /tmp/at2recfifo1 1122x904
+
https://en.wikipedia.org/wiki/Box-drawing_character
- Overwrite/clear selections before freeing them
- ≣ † ☆ ツ ⌘›🍺∴ ( ͡° ͜ʖ ͡°) ƒ ︵ ₂ 😈 θ ƒ › ʼ ƒ ∂ ʻ μ › ∫ ◇ ♪
- ► ə β ə ſ ρ ə ∴ ♪ 😱 † 😳 › ▛ ᵗ * ‽ ℣ Ω ⌘ ❇
+ ► ə β ə ſ ρ ə ∴ ♪ 😱 † 😳 › ▛ ᵗ * ‽ ℣ Ω ⌘ ❇ ⸮
- Verify if dead key support is incomplete for ISO-8859-4 and ISO-8859-10.
There were special characters that were unicode since the start but also
could have punctuation. And others that used two at a time... It might
- Tack seems to be a decent program for terminal testing, other
than vttest.
- Maybe include ffmpeg recording as part of analogterm. This would be easier
- of already supporting YUV internally for xvideo(4).
+ if already supporting YUV internally for xvideo(4).
+ Currently AT2 can send frames via a FIFO for an external process to record.
+ It would also be possible to manage our own ffmpeg process.
- Maybe consider xvideo(4) zooming. If YUV was native, it'd be useful for
xvideo and ffmpeg and custom ratios could be applied.
- Maybe an alternative VT52 or ADM-3A emulation mode for use with old CP/M
int cfg_leading;
int cfg_inputsleep, cfg_inputsleepskip;
bool cfg_uppercaseview;
+const char *cfg_recordfifo;
void
cfg_inputsleepskip = INPUT_SLEEP_SKIP;
cfg_uppercaseview = UPPERCASE_VIEW;
+
+ cfg_recordfifo = NULL;
}
int
{
const struct eparam_s *ep;
- fprintf(stderr, "\nScanline emulation parameters (-p):\n"
+ (void)printf("\nScanline emulation parameters (-p):\n"
"These should be comma-separated and in the form <var>=<0-255>.\n");
- fprintf(stderr, "The following equivalent sequence may be used:\n"
+ (void)printf("The following equivalent sequence may be used:\n"
" [?658467;65539;<p>;<v>h where p = 0-4 and v = 0-255.\n");
for (ep = eparams; ep->param != NULL; ep++)
- fprintf(stderr, " %s (%d) - %s\n",
+ (void)printf(" %s (%d) - %s\n",
ep->param, *ep->var, ep->descr);
- fprintf(stderr, "\n");
+ (void)printf("\n");
}
void
*/
#define HAVE_XSHM_EXTENSION 1
+extern const char *cfg_recordfifo;
+
#endif
#include <err.h>
+#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <sys/time.h>
+#include <unistd.h>
#include <state.h>
#include <screen.h>
static int cursor_blink_ticks = 0, text_blink_ticks = 0;
static uint8_t *empty = NULL;
+/* Recording */
+static int recfd = -1;
+static size_t recsize = 0;
+
/* Exported as a general utility */
bool draw_cursor_blink_state = true,
draw_text_blink_state = true;
if ((row_selected = malloc(cfg_text_width * sizeof(bool))) == NULL)
goto err;
- /* Compute scanjmp */
- if (cfg_condensed)
- scanjmp = (int)((cfg_text_width * cfg_font_width) * 1.5) + 1;
- else
- scanjmp = ((cfg_text_width * cfg_font_width) * 2) + 1;
+ /* Scanline length, scanjmp */
+ scanjmp = screen->pixels_width;
/* Setup signal handler */
act.sa_handler = alarm_sighandler;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1)
goto err;
+ /*
+ * Ignore SIGPIPE to not be killed when trying to write for recording
+ */
+ act.sa_handler = SIG_IGN;
+ (void)sigaction(SIGPIPE, &act, NULL);
+
(void)atexit(draw_cleanup);
return;
draw_lines(st, sc, low, high);
draw_update(sc);
}
+
+ /*
+ * If in record mode, write video memory to FIFO.
+ */
+ if (recfd != -1) {
+ uint8_t *ptr;
+ ssize_t tsize, rsize = 0;
+ for (ptr = (uint8_t *)sc->pixels, tsize = recsize; tsize > 0;
+ tsize -= rsize, ptr += rsize) {
+ if ((rsize = write(recfd, ptr, tsize)) == -1) {
+ warn("draw_update_screen() - write(recfifo)");
+ record_stop();
+ break;
+ }
+ }
+ }
+}
+
+void
+record_start(void)
+{
+ struct stat st;
+
+ if (cfg_recordfifo == NULL)
+ return;
+
+ /* Ensure FIFO exists */
+ if (stat(cfg_recordfifo, &st) != 0 || !(S_ISFIFO(st.st_mode)))
+ return;
+
+ /* If not already open, attempt to. */
+ if (recfd == -1) {
+ recfd = open(cfg_recordfifo, O_WRONLY);
+ if (recfd != -1)
+ (void)fprintf(stderr, "Recording: START\n");
+ recsize = screen->pixels_width * screen->pixels_height * 4;
+ }
+}
+
+void
+record_stop(void)
+{
+
+ if (recfd != -1) {
+ (void)fprintf(stderr, "Recording: STOP\n");
+ (void)close(recfd);
+ recfd = -1;
+ }
}
void draw_custom_rgb(int, int, int);
void draw_update(screen_t *);
void draw_update_screen(screen_t *, state_t *);
+void record_start(void);
+void record_stop(void);
/* Regularly toggled by a timer */
};
/* Map of supported Latin-8 characters in glyph order. */
-static uint32_t iso8859_14_characters[27] = {
+static uint32_t iso8859_14_characters[26] = {
0x0174, 0x0175, 0x0176, 0x0177, 0x1E02, 0x1E03, 0x1E0A, 0x1E0B,
0x1E1E, 0x1E1F, 0x1E40, 0x1E41, 0x1E56, 0x1E57, 0x1E60, 0x1E61,
0x1E6A, 0x1E6B, 0x1E80, 0x1E81, 0x1E82, 0x1E83, 0x1E84, 0x1E85,
raw:
+ /* Special, used for delays on some terminals */
+ if (c == 0)
+ goto done;
+
/* Contiguous character blocks */
for (font = state->font; font != NULL; font = font->next) {
if (c < font->end && c >= font->start) {
/*
* Glyph order character tables.
- * XXX In these grow significantly a hash table or tree may be useful.
+ * XXX If these grow significantly a hash table or tree may be useful.
*/
for (table = state->ftable; table != NULL; table = table->next) {
/* Check for possible empty user table */
{
errno = EINVAL;
- fprintf(stderr,
+ (void)printf(
"\nUsage: %s [-1 | -2] [-8|-u] [-w <cols>] [-h <rows>] [-E <n>]\n"
- " [-s] [-C <col>] [-c] [-W] [-b] [-r <ms>]\n"
+ " [-s] [-C <col>] [-c] [-W] [-b] [-r <ms>] [-R <recordfifo>]\n"
" [-B <ticks>[,<ticks>]] [-j <n>] [-P] [-m <mode>] [-M] [-d]\n"
" [-D] [-S] [-p <parameters>] [-g <w>,<h>] [-l <pixels>]\n"
" [-f <delay>] [-t] [-T <ticks>[,<ticks>]] [-U] [-z <ms>]\n"
" blinking rate settings -B/-T and the speed of smooth\n"
" scrolling when enabled. Low settings save CPU but will\n"
" affect interactive performance.\n"
+ " -R - Specify the fullpath to the FIFO file AT2 should write\n"
+ " video frames to when recording (not allowed by default).\n"
+ " To begin and stop recording, the following ATC sequences\n"
+ " should be issued, respectively: [?658467;65552;1h and\n"
+ " [?658467;65552;0h. A recording program is expected to be\n"
+ " reading from that file. See mkfifo(1) and the example\n"
+ " script record.sh.\n"
" -b - Toggle blinking cursor.\n"
" -B - Set the cursor blinking speed, in refresh ticks.\n"
" Two comma-separated values may optionally be provided for\n"
progname = strdup(argv[0]);
while ((ch = getopt(argc, argv,
- "?128uw:h:E:scC:Wr:bB:j:Pm:MdDStT:p:g:l:f:Uz:Z:e:")) != -1) {
+ "?128uw:h:E:scC:Wr:R:bB:j:Pm:MdDStT:p:g:l:f:Uz:Z:e:")) != -1) {
switch (ch) {
case '1':
cfg_slowscroll = cfg_smoothscroll = true;
cfg_text_width = i;
break;
case 'h':
- if ((i = atoi(optarg)) >= 24 && i < 10000)
+ if ((i = atoi(optarg)) >= 4 && i < 10000)
cfg_text_height = i;
break;
case 'E':
if ((i = atoi(optarg)) >= 11111 && i <= 100000)
cfg_refreshspeed = i;
break;
+ case 'R':
+ if ((cfg_recordfifo = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "strdup()");
+ break;
case 'b':
cfg_cursorblink = !cfg_cursorblink;
break;
goto endopt;
}
break;
- case '?': /* FALLTHROUGH */
- default:
+ case '?':
+ default: /* FALLTHROUGH */
usage();
+ break;
}
}
argc -= optind;
/*
* Fancy retro welcome message.
*/
+ state_goto(state, 0, 1);
+ state_printf(state, 0, "(%dx%d, %dx%d)",
+ screen->pixels_width, screen->pixels_height,
+ cfg_text_width, cfg_text_height);
state_goto(state, 1, 1);
state_prints(state, "READY", 0);
state_goto(state, 1, (cfg_text_width / 2) - 17);
#else
height = (cfg_text_height * (cfg_font_height + cfg_leading)) * 2;
#endif
+ width += width % 16;
+ height += height % 16;
s->pixels_width = width;
s->pixels_height = height;
#include <ctype.h>
#include <err.h>
+#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
}
void
+state_printf(state_t *st, uint32_t m, const char *fmt, ...)
+{
+ va_list lst;
+ char buf[1024];
+
+ va_start(lst, fmt);
+ (void)vsnprintf(buf, 1023, fmt, lst);
+ va_end(lst);
+ buf[1023] = '\0';
+
+ state_prints(st, buf, m);
+}
+
+void
state_goto(state_t *st, int y, int x)
{
break;
}
st->text_updateall = true;
+ } else if (state->curparam == 2 &&
+ state->csiparam[1] == 65552) {
+ switch (state->csiparam[2]) {
+ case 0:
+ record_stop();
+ break;
+ case 1:
+ record_start();
+ break;
+ }
} else if (state->curparam == 3) {
/* Custom foreground color */
draw_custom_rgb(
void state_printc(state_t *, uint32_t, uint32_t);
void state_printc_noscroll(state_t *, uint32_t, uint32_t);
void state_prints(state_t *, const char *, uint32_t);
+void state_printf(state_t *, uint32_t, const char *, ...);
void state_goto(state_t *, int, int);
void state_emul_printc(state_t *, uint8_t);
void state_emul_prints(state_t *, const char *);
alias atintreset="printf '\033[?658467;65539;0;115h\033[?658467;65539;1;26h\033[?658467;65539;2;230h\033[?658467;65539;3;13h\033[?658467;65539;4;0h'"
alias atintbright1="printf '\033[?658467;65539;0;150h\033[?658467;65539;1;26h\033[?658467;65539;2;255h\033[?658467;65539;3;16h\033[?658467;65539;4;16h'"
alias atintbright2="printf '\033[?658467;65539;0;140h\033[?658467;65539;1;26h\033[?658467;65539;2;255h\033[?658467;65539;3;26h\033[?658467;65539;4;26h'"
+alias atintbright3="printf '\033[?658467;65539;0;255h\033[?658467;65539;1;255h\033[?658467;65539;2;255h\033[?658467;65539;3;0h\033[?658467;65539;4;0h'"
alias atintscan1="printf '\033[?658467;65539;0;160h\033[?658467;65539;1;26h\033[?658467;65539;2;32h\033[?658467;65539;3;32h\033[?658467;65539;4;0h'"
alias atintscan2="printf '\033[?658467;65539;0;160h\033[?658467;65539;1;26h\033[?658467;65539;2;32h\033[?658467;65539;3;16h\033[?658467;65539;4;0h'"
alias atintscan3="printf '\033[?658467;65539;0;100h\033[?658467;65539;1;35h\033[?658467;65539;2;32h\033[?658467;65539;3;16h\033[?658467;65539;4;0h'"
alias atintscan4="printf '\033[?658467;65539;0;160h\033[?658467;65539;1;20h\033[?658467;65539;2;8h\033[?658467;65539;3;8h\033[?658467;65539;4;8h'"
+alias atintscan5="printf '\033[?658467;65539;0;100h\033[?658467;65539;1;50h\033[?658467;65539;2;80h\033[?658467;65539;3;0h\033[?658467;65539;4;0h'"
alias atintgrad="printf '\033[?658467;65539;0;100h\033[?658467;65539;1;18h\033[?658467;65539;2;230h\033[?658467;65539;3;13h\033[?658467;65539;4;0h'"
+alias atintreverse="printf '\033[?658467;65539;0;85h\033[?658467;65539;1;5h\033[?658467;65539;2;255h\033[?658467;65539;3;0h\033[?658467;65539;4;0h'"
# Font
alias atdashedzero="printf '\033[?658467;65540;48;0;0h\033[?658467;65540;48;1;28h\033[?658467;65540;48;2;34h\033[?658467;65540;48;3;38h\033[?658467;65540;48;4;42h\033[?658467;65540;48;5;50h\033[?658467;65540;48;6;34h\033[?658467;65540;48;7;28h\033[?658467;65540;48;8;0h'"
alias atfontreset="printf '\033[?658467;65540h'"
alias atfontblank="printf '\033[?658467;65540;0h'"
+
+# Reverse video
+alias atreverse="printf '\033[?5h'"
+alias atnoreverse="printf '\033[?5l'"
+
+# Recording
+alias atrecord="printf '\033[?658467;65552;1h'"
+alias atnorecord="printf '\033[?658467;65552;0h'"
--- /dev/null
+#!/bin/sh
+#
+# Utility script to record AnalogTerm2 output with ffmpeg.
+# Usage:
+# record.sh <fifo> <WIDTHxHEIGHT>
+
+FIFO="$1"
+SIZE="$2"
+
+if [ -z "$FIFO" -o -z "$SIZE" ]; then
+ echo "Usage: $0 <fifo> <WIDTHxHEIGHT>"
+ exit 1
+fi
+
+# Adapt these to your needs
+FFMPEG=$(find 2>/dev/null $(echo $PATH | sed s/:/\ /g) -iname 'ffmpeg*' | sort -n | head -n1)
+VBITRATE=512k
+# Also acceptable but lower quality
+#VBITRATE=384k
+THREADS=4
+FPS=30
+
+# Echo parameters
+echo
+echo "AnalogTerm ][ recording script"
+echo
+echo "Using:"
+echo " ffmpeg command: $FFMPEG"
+echo " video bitrate: $VBITRATE"
+echo "encoding threads: $THREADS"
+echo " resolution: $SIZE"
+echo " rate (FPS): $FPS"
+echo " input FIFO file: $FIFO"
+echo
+echo "Copy and customize this script as necessary."
+echo "Should be running before starting AnalogTerm2 recording."
+echo
+
+# Ensure that FIFO exists
+if [ ! -p "$FIFO" ]; then
+ echo "Creating FIFO $FIFO"
+ mkfifo -m 600 "$FIFO"
+fi
+
+echo "$0: started"
+echo
+
+VPID=''
+
+cleanup()
+{
+
+ # Kill accumulated subprocesses
+ pkill -fx "cat $FIFO"
+
+ # ffmpeg expects 3 interrupts to quit.
+ # In some cases, it just hangs and needs SIGKILL.
+ if [ -n VPID ]; then
+ kill $VPID
+ kill $VPID
+ kill $VPID
+ sleep 1
+ kill -9 $VPID
+ fi
+
+ exit 0
+}
+trap cleanup INT TERM
+
+while true; do
+
+ # Launch our video and audio encoding processes.
+ # Cat is useful here since ffmpeg does not gracefully handle
+ # FIFO file input with its pipe mode designed for piping.
+ # Cat does this conversion and gracefully exits when the
+ # writer closes its end; ffmpeg then gracefully exits doing
+ # its final envelope adjustments like if it had been told
+ # to exit using q at a terminal.
+ # Using cat(1) appears redundant but it helps ffmpeg to
+ # cleanly save its buffers and exit when the pipe chain breaks.
+ # If it doesn't work, use 2> to a temporary file to obtain the
+ # error messages issued by ffmpeg.
+
+ TIMESTAMP=$(date -u +"%Y-%m-%d_%H:%M:%S")
+
+ { cat "$FIFO" | \
+ $FFMPEG >/dev/null 2>&1 -analyzeduration 100000 -f rawvideo \
+ -vcodec rawvideo -s $SIZE -pix_fmt bgra -r $FPS \
+ -fflags nobuffer -i pipe:0 -an -b:v $VBITRATE \
+ -pix_fmt yuv420p -vf realtime -threads $THREADS -y \
+ video-${TIMESTAMP}.mp4; } &
+ VPID=$!
+
+ # Wait for processes to exit
+ wait $VPID
+
+ VPID=''
+
+done