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 }