ttyrex

ttyrec with more options
git clone https://a3nm.net/git/ttyrex/
Log | Files | Refs | README

ttyplay.c (8583B)


      1 /*
      2  * Copyright (c) 2000 Satoru Takabayashi <satoru@namazu.org>
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  * 3. All advertising materials mentioning features or use of this software
     14  *    must display the following acknowledgement:
     15  *	This product includes software developed by the University of
     16  *	California, Berkeley and its contributors.
     17  * 4. Neither the name of the University nor the names of its contributors
     18  *    may be used to endorse or promote products derived from this software
     19  *    without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     31  * SUCH DAMAGE.
     32  */
     33 
     34 #include <stdio.h>
     35 #include <stdlib.h>
     36 #include <assert.h>
     37 #include <unistd.h>
     38 #include <termios.h>
     39 #include <sys/time.h>
     40 #include <string.h>
     41 
     42 #include "ttyrec.h"
     43 #include "io.h"
     44 
     45 typedef double	(*WaitFunc)	(struct timeval prev, 
     46 				 struct timeval cur, 
     47 				 double speed);
     48 typedef int	(*ReadFunc)	(FILE *fp, Header *h, char **buf);
     49 typedef void	(*WriteFunc)	(char *buf, int len);
     50 typedef void	(*ProcessFunc)	(FILE *fp, int jump, double speed,
     51 				 ReadFunc read_func, WaitFunc wait_func);
     52 
     53 struct timeval
     54 timeval_diff (struct timeval tv1, struct timeval tv2)
     55 {
     56     struct timeval diff;
     57 
     58     diff.tv_sec = tv2.tv_sec - tv1.tv_sec;
     59     diff.tv_usec = tv2.tv_usec - tv1.tv_usec;
     60     if (diff.tv_usec < 0) {
     61 	diff.tv_sec--;
     62 	diff.tv_usec += 1000000;
     63     }
     64 
     65     return diff;
     66 }
     67 
     68 struct timeval
     69 timeval_div (struct timeval tv1, double n)
     70 {
     71     double x = ((double)tv1.tv_sec  + (double)tv1.tv_usec / 1000000.0) / n;
     72     struct timeval div;
     73     
     74     div.tv_sec  = (int)x;
     75     div.tv_usec = (x - (int)x) * 1000000;
     76 
     77     return div;
     78 }
     79 
     80 double
     81 ttywait (struct timeval prev, struct timeval cur, double speed)
     82 {
     83     static struct timeval drift = {0, 0};
     84     struct timeval start;
     85     struct timeval diff = timeval_diff(prev, cur);
     86     struct timeval *diffp = &diff;
     87     fd_set readfs;
     88 
     89     gettimeofday(&start, NULL);
     90 
     91     if (speed == 0.0)
     92       diffp = NULL;
     93     else
     94       diff = timeval_diff(drift, timeval_div(diff, speed));
     95 
     96     if (diff.tv_sec < 0) {
     97 	diff.tv_sec = diff.tv_usec = 0;
     98     }
     99 
    100     FD_ZERO(&readfs);
    101     FD_SET(STDIN_FILENO, &readfs);
    102     /* 
    103      * We use select() for sleeping with subsecond precision.
    104      * select() is also used to wait user's input from a keyboard.
    105      *
    106      * Save "diff" since select(2) may overwrite it to {0, 0}. 
    107      */
    108     struct timeval orig_diff = diff;
    109     int r;
    110     r = select(1, &readfs, NULL, NULL, diffp); /* skip if a user hits any key */
    111     diff = orig_diff;  /* Restore the original diff value. */
    112     if (r > 0 && FD_ISSET(0, &readfs)) { /* a user hits a character? */
    113         char c;
    114         read(STDIN_FILENO, &c, 1); /* drain the character */
    115         switch (c) {
    116         case '+':
    117         case 'f':
    118             speed *= 2;
    119             break;
    120         case '-':
    121         case 's':
    122             speed /= 2;
    123             break;
    124         case '1':
    125             speed = 1.0;
    126             break;
    127         case '0':
    128             speed = 0.0;
    129             break;
    130         }
    131 	drift.tv_sec = drift.tv_usec = 0;
    132     } else {
    133 	struct timeval stop;
    134 	gettimeofday(&stop, NULL);
    135 	/* Hack to accumulate the drift */
    136 	if (diff.tv_sec == 0 && diff.tv_usec == 0) {
    137             diff = timeval_diff(drift, diff);  // diff = 0 - drift.
    138         }
    139 	drift = timeval_diff(diff, timeval_diff(start, stop));
    140     }
    141     return speed;
    142 }
    143 
    144 double
    145 ttynowait (struct timeval prev, struct timeval cur, double speed)
    146 {
    147     /* do nothing */
    148     return 1.0; /* Speed isn't important. */
    149 }
    150 
    151 int
    152 ttyread (FILE *fp, Header *h, char **buf)
    153 {
    154     fpos_t pos;
    155     int can_seek=0;
    156     if (fgetpos(fp, &pos) == 0) {
    157 	can_seek=1;
    158     }
    159     clearerr(fp);
    160 
    161     if (read_header(fp, h) == 0) {
    162 	goto err;
    163     }
    164 
    165     *buf = malloc(h->len);
    166     if (*buf == NULL) {
    167 	perror("malloc");
    168     }
    169 	
    170     if (fread(*buf, 1, h->len, fp) == 0) {
    171 	goto err;
    172     }
    173     return 1;
    174 
    175 err:
    176     if (ferror(fp)) {
    177 	perror("fread");
    178     }
    179     else {
    180 	/* Short read. Seek back to before header, to set up for retry. */
    181 	if (can_seek) {
    182 	    fsetpos(fp, &pos);
    183 	}
    184     }
    185     return 0;
    186 }
    187 
    188 int
    189 ttypread (FILE *fp, Header *h, char **buf)
    190 {
    191     /*
    192      * Read persistently just like tail -f.
    193      */
    194     while (ttyread(fp, h, buf) == 0) {
    195 	struct timeval w = {0, 250000};
    196 	select(0, NULL, NULL, NULL, &w);
    197 	clearerr(fp);
    198     }
    199     return 1;
    200 }
    201 
    202 void
    203 ttywrite (char *buf, int len)
    204 {
    205     fwrite(buf, 1, len, stdout);
    206 }
    207 
    208 void
    209 ttynowrite (char *buf, int len)
    210 {
    211     /* do nothing */
    212 }
    213 
    214 void
    215 ttyplay (FILE *fp, int jump, double speed, ReadFunc read_func,
    216 	 WriteFunc write_func, WaitFunc wait_func)
    217 {
    218     int first_time = 1;
    219     struct timeval prev;
    220 
    221     setbuf(stdout, NULL);
    222     setbuf(fp, NULL);
    223 
    224     while (1) {
    225 	char *buf;
    226 	Header h;
    227 
    228 	if (read_func(fp, &h, &buf) == 0) {
    229 	    break;
    230 	}
    231 
    232 	if (!first_time) {
    233 	    do {
    234 		speed = wait_func(prev, h.tv, speed);
    235 	    } while (speed == 0.0);
    236 	}
    237         if (jump && h.tv.tv_sec > jump) {
    238             jump = 0;
    239             wait_func = ttywait;
    240         }
    241 	first_time = 0;
    242 
    243 	write_func(buf, h.len);
    244 	prev = h.tv;
    245 	free(buf);
    246     }
    247 }
    248 
    249 void
    250 ttyskipall (FILE *fp)
    251 {
    252     /*
    253      * Skip all records.
    254      */
    255     ttyplay(fp, 0, 0, ttyread, ttynowrite, ttynowait);
    256 }
    257 
    258 void ttyplayback (FILE *fp, int jump, double speed,
    259 		  ReadFunc read_func, WaitFunc wait_func)
    260 {
    261     ttyplay(fp, jump, speed, ttyread, ttywrite, wait_func);
    262 }
    263 
    264 void ttypeek (FILE *fp, int jump, double speed,
    265 	      ReadFunc read_func, WaitFunc wait_func)
    266 {
    267     ttyskipall(fp);
    268     ttyplay(fp, jump, speed, ttypread, ttywrite, ttynowait);
    269 }
    270 
    271 
    272 void
    273 usage (void)
    274 {
    275     printf("Usage: ttyplay [OPTION] [FILE]\n");
    276     printf("  -s SPEED Set speed to SPEED [1.0]\n");
    277     printf("  -n       No wait mode\n");
    278     printf("  -p       Peek another person's ttyrecord\n");
    279     exit(EXIT_FAILURE);
    280 }
    281 
    282 /*
    283  * We do some tricks so that select(2) properly works on
    284  * STDIN_FILENO in ttywait().
    285  */
    286 FILE *
    287 input_from_stdin (void)
    288 {
    289     int fd = edup(STDIN_FILENO);
    290     edup2(STDOUT_FILENO, STDIN_FILENO);
    291     return efdopen(fd, "r");
    292 }
    293 
    294 int 
    295 main (int argc, char **argv)
    296 {
    297     double speed = 1.0;
    298     int jump = 0;
    299     ReadFunc read_func  = ttyread;
    300     WaitFunc wait_func  = ttywait;
    301     ProcessFunc process = ttyplayback;
    302     FILE *input = NULL;
    303     struct termios old, new;
    304 
    305     set_progname(argv[0]);
    306     while (1) {
    307         int ch = getopt(argc, argv, "s:npj:");
    308         if (ch == EOF) {
    309             break;
    310 	}
    311 	switch (ch) {
    312 	case 's':
    313 	    if (optarg == NULL) {
    314 		perror("-s option requires an argument");
    315 		exit(EXIT_FAILURE);
    316 	    }
    317 	    sscanf(optarg, "%lf", &speed);
    318 	    break;
    319 	case 'j':
    320 	    if (optarg == NULL) {
    321 		perror("-j option requires an argument");
    322 		exit(EXIT_FAILURE);
    323 	    }
    324 	    sscanf(optarg, "%d", &jump);
    325 	    wait_func = ttynowait;
    326 	    break;
    327 	case 'n':
    328 	    wait_func = ttynowait;
    329 	    break;
    330 	case 'p':
    331 	    process = ttypeek;
    332 	    break;
    333 	default:
    334 	    usage();
    335 	}
    336     }
    337 
    338     if (optind < argc) {
    339 	input = efopen(argv[optind], "r");
    340     } else {
    341         input = input_from_stdin();
    342     }
    343     assert(input != NULL);
    344 
    345     tcgetattr(0, &old); /* Get current terminal state */
    346     new = old;          /* Make a copy */
    347     new.c_lflag &= ~(ICANON | ECHO | ECHONL); /* unbuffered, no echo */
    348     tcsetattr(0, TCSANOW, &new); /* Make it current */
    349 
    350     process(input, jump, speed, read_func, wait_func);
    351     tcsetattr(0, TCSANOW, &old);  /* Return terminal state */
    352 
    353     return 0;
    354 }