v04i021: Terminal screen snapshot program

David MacKenzie edf at ROCKY2.ROCKEFELLER.EDU
Sun Aug 14 13:51:36 AEST 1988


Posting-number: Volume 4, Issue 21
Submitted-by: "David MacKenzie" <edf at ROCKY2.ROCKEFELLER.EDU>
Archive-name: snapshot

Here is a program that reads the return values of the "transmit line"
function that some terminals provide, and saves them in a file.

Currently, it supports only the Wyse 50 and Heath/Zenith 29, but
support for other terminals shouldn't be hard to add.

David MacKenzie

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  MANIFEST Makefile README echo.c h29.c main.c signals.c
#   snapshot.1 snapshot.c snapshot.h wy50.c
# Wrapped by dave at edfdc  on Sat Aug 13 23:44:28 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(455 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	This shipping list
X Makefile                   1	
X README                     1	
X echo.c                     1	
X h29.c                      1	
X main.c                     1	
X signals.c                  1	
X snapshot.1                 1	
X snapshot.c                 1	
X snapshot.h                 1	
X wy50.c                     1	
END_OF_FILE
if test 455 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(761 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X# Makefile for snapshot.
X# Latest revision: 07/29/88
X
XCFLAGS = -O
XLIBS   =
XDEFS   =
X 
XTARGET = snapshot
X 
XHDRS  = snapshot.h 
XSRCS  = echo.c h29.c main.c signals.c snapshot.c wy50.c 
XOBJS  = echo.o h29.o main.o signals.o snapshot.o wy50.o 
X
X$(TARGET): $(OBJS) $(SRCS) $(HDRS)
X	$(CC) $(CFLAGS) $(DEFS) $(OBJS) -o $(TARGET) $(LIBS)
X	strip $(TARGET)
X
Xecho.o: echo.c 
X	$(CC) $(CFLAGS) $(DEFS) -c echo.c
X
Xh29.o: h29.c 
X	$(CC) $(CFLAGS) $(DEFS) -c h29.c
X
Xmain.o: main.c 
X	$(CC) $(CFLAGS) $(DEFS) -c main.c
X
Xsignals.o: signals.c 
X	$(CC) $(CFLAGS) $(DEFS) -c signals.c
X
Xsnapshot.o: snapshot.c 
X	$(CC) $(CFLAGS) $(DEFS) -c snapshot.c
X
Xwy50.o: wy50.c 
X	$(CC) $(CFLAGS) $(DEFS) -c wy50.c
X
Xclean: $(OBJS)
X	rm -f $(OBJS)
X
Xlint:
X	lint $(SRCS) > LINT.OUT
X
Xkit:
X	makekit -m -p
END_OF_FILE
if test 761 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(2351 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
Xsnapshot
X
X08/10/88
X
XThis program gives users of serial terminals some of the capability of
Xthe PC's PrtSc key.
X
XCurrently it supports the Wyse 50 and Heath/Zenith 29 terminals.  I
Xthink that the H29 code also works on H19's.  The Wyse 50 code only
Xworks in 80-column mode; I experimented with the extended cursor
Xpositioning that supports the 132-column mode, but I couldn't get it
Xto work.
X
XInstalling Terminal Definitions
X
XTermcap/terminfo are not used because it would be difficult to coerce
Xthem into dealing with the oddities of producing a screen dump.
XHowever, snapshot is easily extendable, by recompiling, for any
Xterminal that can transmit the contents of the screen.
X
XIssues that must be dealt with include:
X - where must the cursor be in order to transmit a line?  (probably
X   either at the start or end of the line)
X - what is the code to tell the terminal to transmit a line?
X - in what format does it transmit the line?  (does it include special
X   escape sequences?)
X - how is the cursor position saved/restored?  (might be defined as the 
X   sc and rc termcap capabilities.  if the terminal can't save/restore
X   the cursor position, you can try using its "return cursor
X   coordinates" code; I had trouble reading the Wyse 50's cursor
X   position, so I #ifdef'd that code out and just have it return to the
X   bottom of the screen every time)
X
XHere's what you need to do:
X
X1.  Create a file similar to wy50.c and h29.c that defines
Xname_savepos(), name_restorepos(), and name_transline() for the new
Xterminal.  Transmitting a whole screenful at once has been unreliable
Xin my experience, and it precludes specifying starting and ending lines;
Xbut if you want to do it, you can have name_transline() keep a static
Xvariable and be a no-op all but the first time it is called.
X
X2.  Add function declarations and an entry in tdtab[], and increment
XNTD, in main.c
X
X3.  Add the file from step 1. to the Makefile and remake the program.
X
XIf you can figure out a better way to support different terminals, feel
Xfree to change things around!
X
XThe program is designed modularly so that it is easy to replace parts of
Xit, if necessary (e.g. for porting to other OS's), and its functions
Xclean up after themselves so they could be linked in as parts of other
Xprograms.
X
XDavid MacKenzie
Xedf at rocky2.rockefeller.edu (...rutgers!cmcl2!rocky2!edf).
END_OF_FILE
if test 2351 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'echo.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'echo.c'\"
else
echo shar: Extracting \"'echo.c'\" \(526 characters\)
sed "s/^X//" >'echo.c' <<'END_OF_FILE'
X/*
X * echo.c
X */
X
X#include <sgtty.h>
X
X/*
X * Turn echo mode off.  Return 0 if ok, -1 if error.
X */
Xecho_off()
X{
X    struct sgttyb ttybuf;
X
X    if (ioctl(0, TIOCGETP, (char *) &ttybuf) == -1)
X	return -1;
X    ttybuf.sg_flags &= ~ECHO;
X    return ioctl(0, TIOCSETP, (char *) &ttybuf);
X}
X
X/*
X * Turn echo mode on.  Return 0 if ok, -1 if error.
X */
Xecho_on()
X{
X    struct sgttyb ttybuf;
X
X    if (ioctl(0, TIOCGETP, (char *) &ttybuf) == -1)
X	return -1;
X    ttybuf.sg_flags |= ECHO;
X    return ioctl(0, TIOCSETP, (char *) &ttybuf);
X}
END_OF_FILE
if test 526 -ne `wc -c <'echo.c'`; then
    echo shar: \"'echo.c'\" unpacked with wrong size!
fi
# end of 'echo.c'
fi
if test -f 'h29.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'h29.c'\"
else
echo shar: Extracting \"'h29.c'\" \(643 characters\)
sed "s/^X//" >'h29.c' <<'END_OF_FILE'
X/*
X * h29.c
X */
X
X/*
X * Save the current cursor position.
X */
Xh29_savepos()
X{
X    printf("\033j");
X}
X
X/*
X * Restore the initial cursor position.
X */
Xh29_restorepos()
X{
X    printf("\033k");
X}
X
X/*
X * Tell the terminal to transmit a line.
X */
Xh29_transline(line)
X    int     line;
X{
X    /* Locate cursor at start of next line. */
X    printf("\033Y%c ", line + 32);
X    /*
X     * Send "transmit line" code.  This will send the whole line, padding to
X     * 80 columns with spaces.  Due to graphics and special mode codes, which
X     * will be sent as well, it could end up being well over 80 characters
X     * long. 
X     */
X    printf("\033^");
X}
END_OF_FILE
if test 643 -ne `wc -c <'h29.c'`; then
    echo shar: \"'h29.c'\" unpacked with wrong size!
fi
# end of 'h29.c'
fi
if test -f 'main.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'main.c'\"
else
echo shar: Extracting \"'main.c'\" \(2774 characters\)
sed "s/^X//" >'main.c' <<'END_OF_FILE'
X/*
X * snapshot - make a snapshot of the terminal screen into a file
X *
X * Usage: snapshot [-s startline] [-e endline] destfile
X *        snapshot -l
X *
X * Options:
X * -s   specify alternate line # for start of snapshot
X * -e   specify alternate line # for end of snapshot
X * -l   list known terminal ($TERM) types and quit
X *
X * David MacKenzie
X * Latest revision: 07/29/88
X */
X
X#define ALLOCATE		/* Allocate memory for the global variables. */
X#include <stdio.h>
X#include "snapshot.h"
X
Xextern int
Xwy50_savepos(), wy50_restorepos(), wy50_transline();
Xextern int
Xh29_savepos(), h29_restorepos(), h29_transline();
X
X/* Number of entries in tdtab. */
X#define NTD 2
X
Xstruct termdef tdtab[NTD] = {
X    {
X	{"h29", "z29", "h19", "z19", NULL, NULL},
X	h29_savepos,
X	h29_restorepos,
X	h29_transline,
X	0,
X	24
X    },
X    {
X	{"wy50", "wyse50", "wy50-t", "wyse50-t", NULL, NULL},
X	wy50_savepos,
X	wy50_restorepos,
X	wy50_transline,
X	1,
X	24
X    }
X};
X
Xmain(argc, argv)
X    int     argc;
X    char  **argv;
X{
X    char   *getenv();
X    struct termdef *getdef();
X    extern char *optarg;
X    extern int optind;
X    char   *term;		/* Value of $TERM. */
X    int     start = -1;		/* First line (row) to snap. */
X    int     end = -1;		/* Last line (row) to snap. */
X    register int c;		/* Option character. */
X
X    while ((c = getopt(argc, argv, "s:e:l")) != EOF)
X	switch (c) {
X	case 's':
X	    start = atoi(optarg);
X	    break;
X	case 'e':
X	    end = atoi(optarg);
X	    break;
X	case 'l':
X	    listknown();
X	    exit(0);
X	default:
X	    usage(argv[0]);
X	}
X    if (optind != argc - 1)
X	usage(argv[0]);
X
X    term = getenv("TERM");
X    tp = getdef(term);
X    if (!tp) {
X	fprintf(stderr, "%s: Unknown terminal type\n", term);
X	exit(1);
X    }
X    if (start == -1)
X	start = minline;
X    if (end == -1)
X	end = maxline;
X
X    if (snapshot(argv[optind], start, end))
X	exit(1);
X
X    exit(0);
X}
X
X/*
X * If term has a legal value for a known termdef, return a pointer
X * to that termdef, else return NULL.
X */
Xstruct termdef *
Xgetdef(term)
X    register char *term;
X{
X    register int tdi;
X    register int termi;
X
X    for (tdi = 0; tdi < NTD; ++tdi)
X	for (termi = 0; termi < NTERMS && tdtab[tdi].td_terms[termi]; ++termi)
X	    if (!strcmp(term, tdtab[tdi].td_terms[termi]))
X		return &tdtab[tdi];
X    return NULL;
X}
X
X/*
X * List the $TERM values that are legal.
X */
Xlistknown()
X{
X    register int tdi;
X    register int termi;
X
X    printf("Known terminal types:\n");
X    for (tdi = 0; tdi < NTD; ++tdi) {
X	for (termi = 0; termi < NTERMS && tdtab[tdi].td_terms[termi]; ++termi)
X	    printf("%s ", tdtab[tdi].td_terms[termi]);
X	printf("\n");
X    }
X}
X
Xusage(file)
X    char   *file;
X{
X    fprintf(stderr, "Usage: %s [-s startline] [-e endline] destfile\n", file);
X    fprintf(stderr, "       %s -l\n", file);
X    exit(1);
X}
END_OF_FILE
if test 2774 -ne `wc -c <'main.c'`; then
    echo shar: \"'main.c'\" unpacked with wrong size!
fi
# end of 'main.c'
fi
if test -f 'signals.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'signals.c'\"
else
echo shar: Extracting \"'signals.c'\" \(1376 characters\)
sed "s/^X//" >'signals.c' <<'END_OF_FILE'
X/*
X * signals.c
X */
X
X#include <stdio.h>
X#include <signal.h>
X#include "snapshot.h"
X
X#ifndef BADSIG
X#define BADSIG (int (*)()) -1
X#endif
X
X/* pointers to functions returning int */
Xstatic int (*oldint) ();	/* Old value of SIGINT. */
X
X#ifdef SIGTTIN
Xstatic int (*oldttin) ();	/* Old value of SIGTTIN. */
Xstatic int (*oldttou) ();	/* Old value of SIGTTOU. */
X#endif
X
X/*
X * Interrupt signal handler.
X */
X
Xstatic int
Xint_handler()
X{
X    fprintf(stderr, "Interrupted\n");
X    longjmp(sjbuf, -1);
X}
X
X/*
X * Set up signal handlers.  Return 0 if ok, -1 if error.
X */
Xhandlesigs()
X{
X#ifdef SIGTTIN
X    /*
X     * Ignore the signals sent if someone in our process group tries a
X     * background read from/write to the tty. 
X     */
X    if ((oldttin = signal(SIGTTIN, SIG_IGN)) == BADSIG ||
X	(oldttou = signal(SIGTTOU, SIG_IGN)) == BADSIG) {
X	return -1;
X    }
X#endif
X
X    /* if interrupts were being ignored, don't catch them */
X    if ((oldint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
X	if (signal(SIGINT, int_handler) == BADSIG) {
X	    return -1;
X	}
X    return 0;
X}
X
X/*
X * Restore original signal values.  Return 0 if ok, -1 if error.
X */
Xrestoresigs()
X{
X    int status = 0;
X    
X#ifdef SIGTTIN
X    if (signal(SIGTTIN, oldttin) == BADSIG || signal(SIGTTOU, oldttou) ==
X	BADSIG) {
X	status = -1;
X    }
X#endif
X    if (signal(SIGINT, oldint) == BADSIG) {
X	status = -1;
X    }
X    return status;
X}
END_OF_FILE
if test 1376 -ne `wc -c <'signals.c'`; then
    echo shar: \"'signals.c'\" unpacked with wrong size!
fi
# end of 'signals.c'
fi
if test -f 'snapshot.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'snapshot.1'\"
else
echo shar: Extracting \"'snapshot.1'\" \(784 characters\)
sed "s/^X//" >'snapshot.1' <<'END_OF_FILE'
X.TH SNAPSHOT 1
X.SH NAME
Xsnapshot \- make a snapshot of the terminal screen into a file
X.SH SYNOPSIS
X.B snapshot
X[
X.B \-s start
X] [
X.B \-e end
X]
X.B destfile
X.br
X.B snapshot \-l
X.SH DESCRIPTION
X.I Snapshot
Xmakes a copy of the terminal screen (optionally, only certain lines of it;
Xsee below) in the specified file.  It strips any trailing spaces from the
Xend of each line.
X.PP
XOptions:
X.TP
X.I \-s start
Xuse
X.I start
Xas the starting line instead of the default (usually 0 or 1).
X.TP
X.I \-e end
Xuse
X.I end
Xas the ending line instead of the default (usually 24).
X.TP
X.I \-l
Xlist the known terminal types
X.SH BUGS
XOnly works on certain terminals.  Also, the last character in 80-column
Xwide lines is replaced with a newline so that the file can be easily
Xedited.
X.SH AUTHOR
XDavid MacKenzie
END_OF_FILE
if test 784 -ne `wc -c <'snapshot.1'`; then
    echo shar: \"'snapshot.1'\" unpacked with wrong size!
fi
# end of 'snapshot.1'
fi
if test -f 'snapshot.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'snapshot.c'\"
else
echo shar: Extracting \"'snapshot.c'\" \(2430 characters\)
sed "s/^X//" >'snapshot.c' <<'END_OF_FILE'
X/*
X * snapshot.c
X */
X
X#include <stdio.h>
X#include <ctype.h>
X#include "snapshot.h"
X
Xstatic FILE *fp;		/* Output stream. */
X
X/*
X * Make a snapshot of lines start through end of the screen into file. 
X * Return 0 if ok, -1 if error.
X */
Xsnapshot(file, start, end)
X    char   *file;		/* Output file. */
X    int     start,
X            end;		/* First, last lines to copy. */
X{
X    char    buf[300];		/* Contents of one line of the screen. */
X
X    if (start < minline || end > maxline || start > end) {
X	fprintf(stderr, "Illegal line range\n");
X	return -1;
X    }
X    if (!(fp = fopen(file, "w"))) {
X	perror(file);
X	return -1;
X    }
X    if (handlesigs() == -1) {
X        perror("signal");
X	return -1;
X    }
X    if (echo_off() == -1) {
X	perror("ioctl");
X	return -1;
X    }
X    savepos();
X    /*
X     * If we've been interrupted, die gracefully.  The original call to
X     * setjmp() returns 0; int_handler() makes it return -1. setjmp is used
X     * so this function can return -1 on ^C. 
X     */
X    if (setjmp(sjbuf)) {
X	(void) cleanup();
X	return -1;
X    }
X    for (; start <= end; ++start) {
X	transline(start);
X	/* Get null-terminated string with newline stripped. */
X	if (fflush(stdout) == EOF || gets(buf) == NULL) {
X	    /* Something weird happened. */
X	    fprintf(stderr, "Transmission error\n");
X	    (void) cleanup();
X	    return -1;
X	}
X	editable(buf);
X	fputs(buf, fp);
X    }
X
X    return cleanup();
X}
X
X/*
X * Remove trailing spaces or any character in the last column.
X */
Xeditable(buf)
X    char   *buf;
X{
X    register char *tmp;
X
X    tmp = buf + strlen(buf) - 1;
X    /* Make the line editable in case it's a full screen wide. */
X    *tmp = '\n';
X    /* Skip past trailing spaces. */
X    while (tmp != buf && isspace(*tmp))
X	--tmp;
X    /*
X     * If we got all the way to the start of the line, it's a blank line.
X     * Otherwise, we're positioned at the last non-space char. 
X     */
X    if (tmp != buf)
X	++tmp;
X    tmp[0] = '\n';
X    tmp[1] = '\0';
X}
X
X/*
X * Clean up the mess we've made.  Return 0 if ok, -1 if error.
X */
Xcleanup()
X{
X    int     status = 0;
X
X    if (restoresigs() == -1) {
X        perror("signal");
X	status = -1;
X    }
X    if (fclose(fp) == EOF) {
X	fprintf(stderr, "Error closing file\n");
X	status = -1;
X    }
X    if (fflush(stdout) == EOF) {
X	fprintf(stderr, "Error flushing output\n");
X	status = -1;
X    }
X    if (echo_on() == -1) {
X	perror("ioctl");
X	status = -1;
X    }
X    restorepos();
X    return status;
X}
END_OF_FILE
if test 2430 -ne `wc -c <'snapshot.c'`; then
    echo shar: \"'snapshot.c'\" unpacked with wrong size!
fi
# end of 'snapshot.c'
fi
if test -f 'snapshot.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'snapshot.h'\"
else
echo shar: Extracting \"'snapshot.h'\" \(961 characters\)
sed "s/^X//" >'snapshot.h' <<'END_OF_FILE'
X/*
X * snapshot.h
X */
X
X#ifdef ALLOCATE
X#define EXTERN
X#define INIT(x) = {x}
X#else
X#define EXTERN extern
X#define INIT(x)
X#endif
X
X#define NTERMS 6		/* Max. # of $TERM values per termdef. */
X
X/*
X * All the info we need to know about a terminal in order to make a
X * snapshot of its screen.
X */
Xstruct termdef {
X    char   *td_terms[NTERMS];	/* Possible $TERM values or NULLs. */
X    int     (*td_savepos) ();	/* Function to save cursor position. */
X    int     (*td_restorepos) ();/* Function to restore cursor position. */
X    int     (*td_transline) ();	/* Function to request 1 line transmit. */
X    int     td_minline;		/* Minimum legal line number. */
X    int     td_maxline;		/* Maximum legal line number. */
X};
X
XEXTERN struct termdef *tp;
X
X#define savepos (*tp->td_savepos)
X#define restorepos (*tp->td_restorepos)
X#define transline (*tp->td_transline)
X#define minline tp->td_minline
X#define maxline tp->td_maxline
X
X#include <setjmp.h>
X
XEXTERN jmp_buf sjbuf;
END_OF_FILE
if test 961 -ne `wc -c <'snapshot.h'`; then
    echo shar: \"'snapshot.h'\" unpacked with wrong size!
fi
# end of 'snapshot.h'
fi
if test -f 'wy50.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'wy50.c'\"
else
echo shar: Extracting \"'wy50.c'\" \(1277 characters\)
sed "s/^X//" >'wy50.c' <<'END_OF_FILE'
X/*
X * wy50.c
X */
X
X#include <stdio.h>
X
X/* Define as 1 to restore original cursor position at end. */
X#define RESTORE_POS 0
X
X/* Row and column to return to when finished. */
Xstatic int saver,
X        savec;
X
X/*
X * Save the initial cursor position.
X */
Xwy50_savepos()
X{
X#if RESTORE_POS
X    /* Transmit "send the cursor address" code. */
X    printf("\033?");
X    if (fflush(stdout) == EOF) {
X	fprintf(stderr, "Transmission error\n");
X	(void) cleanup();
X	return -1;
X    }
X    saver = getchar();
X    savec = getchar();
X    /* Discard CR. */
X    (void) getchar();
X#else
X    /* Place cursor on bottom line (row) for neatness. */
X    saver = 24 + 31;
X    savec = 1 + 31;
X#endif
X}
X
X/*
X * Restore the initial cursor position.
X */
Xwy50_restorepos()
X{
X    printf("\033=%c%c", saver, savec);
X}
X
X/*
X * Tell the terminal to transmit a line.
X */
Xwy50_transline(line)
X    int     line;
X{
X    /* Locate cursor at end of next line (row n, column 80). */
X    printf("\033=%c%c", line + 31, 80 + 31);
X    /*
X     * Send "transmit line" code.  This will send the whole line, padding to
X     * 80 columns with spaces.  Due to graphics and special mode codes, which
X     * will be sent as well, it could end up being well over 80 characters
X     * long. 
X     */
X    printf("\033");
X    printf("6");
X}
END_OF_FILE
if test 1277 -ne `wc -c <'wy50.c'`; then
    echo shar: \"'wy50.c'\" unpacked with wrong size!
fi
# end of 'wy50.c'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0



More information about the Comp.sources.misc mailing list