Annotation of dgamelaunch-openbsd/ttyplay.c, Revision 1.1
1.1 ! rubenllo 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 "config.h"
! 35:
! 36: #define _GNU_SOURCE /* need sighandler_t */
! 37:
! 38: #include <sys/types.h>
! 39: #include <sys/time.h>
! 40: #include <sys/stat.h>
! 41: #ifdef HAVE_KQUEUE
! 42: #include <sys/event.h>
! 43: #endif
! 44:
! 45: #include <stdio.h>
! 46: #include <stdlib.h>
! 47: #include <assert.h>
! 48: #include <unistd.h>
! 49: #include <termios.h>
! 50: #include <string.h>
! 51: #include <curses.h>
! 52: #include <signal.h>
! 53: #include <errno.h>
! 54:
! 55: #include "dgamelaunch.h"
! 56: #include "ttyplay.h"
! 57: #include "ttyrec.h"
! 58: #include "io.h"
! 59: #include "stripgfx.h"
! 60:
! 61: #if defined(__MACH__) || defined(__FreeBSD__) || defined(__NetBSD__)
! 62: typedef void (*sighandler_t)(int);
! 63: #endif
! 64:
! 65: int stripped = NO_GRAPHICS;
! 66: static int got_sigwinch = 0;
! 67:
! 68: static int term_resizex = -1;
! 69: static int term_resizey = -1;
! 70:
! 71: void
! 72: ttyplay_sigwinch_func(int sig)
! 73: {
! 74: signal(SIGWINCH, ttyplay_sigwinch_func);
! 75: got_sigwinch = 1;
! 76: }
! 77:
! 78: struct timeval
! 79: timeval_diff (struct timeval tv1, struct timeval tv2)
! 80: {
! 81: struct timeval diff;
! 82:
! 83: diff.tv_sec = tv2.tv_sec - tv1.tv_sec;
! 84: diff.tv_usec = tv2.tv_usec - tv1.tv_usec;
! 85: if (diff.tv_usec < 0)
! 86: {
! 87: diff.tv_sec--;
! 88: diff.tv_usec += 1000000;
! 89: }
! 90:
! 91: return diff;
! 92: }
! 93:
! 94: struct timeval
! 95: timeval_div (struct timeval tv1, double n)
! 96: {
! 97: double x = ((double) tv1.tv_sec + (double) tv1.tv_usec / 1000000.0) / n;
! 98: struct timeval div;
! 99:
! 100: div.tv_sec = (int) x;
! 101: div.tv_usec = (x - (int) x) * 1000000;
! 102:
! 103: return div;
! 104: }
! 105:
! 106: double
! 107: ttywait (struct timeval prev, struct timeval cur, double speed)
! 108: {
! 109: struct timeval diff = timeval_diff (prev, cur);
! 110:
! 111: assert (speed != 0);
! 112: diff = timeval_div (diff, speed);
! 113:
! 114: select (1, NULL, NULL, NULL, &diff); /* skip if a user hits any key */
! 115:
! 116: return speed;
! 117: }
! 118:
! 119: double
! 120: ttynowait (struct timeval prev, struct timeval cur, double speed)
! 121: {
! 122: return 0; /* Speed isn't important. */
! 123: }
! 124:
! 125: int
! 126: kbhit(void)
! 127: {
! 128: int i = 0;
! 129: nodelay(stdscr, TRUE);
! 130: timeout(0);
! 131: i = wgetch(stdscr);
! 132: nodelay(stdscr, FALSE);
! 133:
! 134: if (i == -1)
! 135: i = 0;
! 136: else
! 137: ungetch(i);
! 138: return (i);
! 139: }
! 140:
! 141: int
! 142: ttyplay_keyboard_action(int c)
! 143: {
! 144: struct termios t;
! 145: switch (c)
! 146: {
! 147: case ERR:
! 148: case 'q':
! 149: return READ_QUIT;
! 150: case 'r':
! 151: if (term_resizex > 0 && term_resizey > 0) {
! 152: printf ("\033[8;%d;%dt", term_resizey, term_resizex);
! 153: return READ_RESTART;
! 154: }
! 155: break;
! 156: case 's':
! 157: switch (stripped)
! 158: {
! 159: case NO_GRAPHICS: populate_gfx_array ((stripped = DEC_GRAPHICS)); break;
! 160: case DEC_GRAPHICS: populate_gfx_array ((stripped = IBM_GRAPHICS)); break;
! 161: case IBM_GRAPHICS: populate_gfx_array ((stripped = NO_GRAPHICS)); break;
! 162: }
! 163: return READ_RESTART;
! 164:
! 165: case 'm':
! 166: tcgetattr (0, &t);
! 167: if (!loggedin)
! 168: {
! 169: initcurses();
! 170: loginprompt(1);
! 171: }
! 172: if (loggedin)
! 173: {
! 174: initcurses ();
! 175: domailuser (chosen_name);
! 176: }
! 177: endwin ();
! 178: tcsetattr (0, TCSANOW, &t);
! 179: return READ_RESTART;
! 180: case '?':
! 181: tcgetattr (0, &t);
! 182: initcurses();
! 183: (void) runmenuloop(dgl_find_menu("watchmenu_help"));
! 184: endwin ();
! 185: tcsetattr (0, TCSANOW, &t);
! 186: return READ_RESTART;
! 187: }
! 188: return (READ_DATA);
! 189: }
! 190:
! 191: int
! 192: ttyread (FILE * fp, Header * h, char **buf, int pread)
! 193: {
! 194: long offset;
! 195: int kb = kbhit();
! 196:
! 197: if (kb == ERR) return READ_QUIT;
! 198: else if (kb) {
! 199: const int c = dgl_getch();
! 200: const int action = ttyplay_keyboard_action(c);
! 201: if (action != READ_DATA)
! 202: return (action);
! 203: }
! 204:
! 205: /* do this BEFORE header read, hlen bug */
! 206: offset = ftell (fp);
! 207:
! 208: if (read_header (fp, h) == 0)
! 209: {
! 210: return READ_EOF;
! 211: }
! 212:
! 213: /* length should never be longer than one BUFSIZ */
! 214: if (h->len > BUFSIZ)
! 215: {
! 216: fprintf (stderr, "h->len too big (%ld) limit %ld\n",
! 217: (long)h->len, (long)BUFSIZ);
! 218: return READ_QUIT;
! 219: }
! 220:
! 221: *buf = malloc (h->len + 1);
! 222: if (*buf == NULL)
! 223: {
! 224: perror ("malloc");
! 225: return READ_QUIT;
! 226: }
! 227:
! 228: if (fread (*buf, 1, h->len, fp) != h->len)
! 229: {
! 230: fseek (fp, offset, SEEK_SET);
! 231: return READ_EOF;
! 232: }
! 233: (*buf)[h->len] = 0;
! 234: return READ_DATA;
! 235: }
! 236:
! 237: int
! 238: ttypread (FILE * fp, Header * h, char **buf, int pread)
! 239: {
! 240: int n;
! 241: #ifdef HAVE_KQUEUE
! 242: struct kevent evt[2];
! 243: static int kq = -1;
! 244: #endif
! 245: struct timeval w = { 0, 100000 };
! 246: struct timeval origw = { 0, 100000 };
! 247: int counter = 0;
! 248: fd_set readfs;
! 249: int doread = 0;
! 250: int action = READ_DATA;
! 251:
! 252: #ifdef HAVE_KQUEUE
! 253: if (kq == -1)
! 254: kq = kqueue ();
! 255: if (kq == -1)
! 256: {
! 257: printf ("kqueue() failed.\n");
! 258: return READ_QUIT;
! 259: }
! 260: #endif
! 261:
! 262: /*
! 263: * Read persistently just like tail -f.
! 264: */
! 265: while ((action = ttyread (fp, h, buf, 1)) == READ_EOF)
! 266: {
! 267: idle_alarm_reset();
! 268: fflush(stdout);
! 269: clearerr (fp);
! 270: #ifdef HAVE_KQUEUE
! 271: n = -1;
! 272: if (kq != -2)
! 273: {
! 274: EV_SET (&evt[0], STDIN_FILENO, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, NULL);
! 275: EV_SET (&evt[1], fileno (fp), EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, NULL);
! 276: n = kevent (kq, evt, 2, evt, 1, NULL);
! 277: doread = (n >= 1 && evt[0].ident == STDIN_FILENO &&
! 278: evt[0].filter == EVFILT_READ) ||
! 279: (n >= 2 && evt[1].ident == STDIN_FILENO &&
! 280: evt[1].filter == EVFILT_READ);
! 281: if (n == -1)
! 282: {
! 283: /*
! 284: * Perhaps kevent(2) doesn't work on this fstype,
! 285: * use select(2) instead. Never use kevent again, assuming all
! 286: * active ttyrecs are on the same fstype.
! 287: */
! 288: close(kq);
! 289: kq = -2;
! 290: }
! 291: }
! 292: if (n == -1)
! 293: #endif
! 294: {
! 295: if (counter++ > (20 * 60 * 10))
! 296: {
! 297: /*
! 298: * The reason for this timeout is that the select() method uses
! 299: * some CPU in waiting. The kqueue() method does not do that, so it
! 300: * does not need the timeout.
! 301: */
! 302: endwin ();
! 303: printf ("Exiting due to 20 minutes of inactivity.\n");
! 304: return READ_QUIT;
! 305: }
! 306: FD_ZERO (&readfs);
! 307: FD_SET (STDIN_FILENO, &readfs);
! 308: n = select (1, &readfs, NULL, NULL, &w);
! 309: w = origw;
! 310: doread = n >= 1 && FD_ISSET (0, &readfs);
! 311: }
! 312: if (n == -1)
! 313: {
! 314: if ((errno == EINTR) && got_sigwinch) {
! 315: got_sigwinch = 0;
! 316: return READ_RESTART;
! 317: } else {
! 318: printf("select()/kevent() failed.\n");
! 319: return READ_QUIT;
! 320: }
! 321: }
! 322: if (doread)
! 323: { /* user hits a character? */
! 324: const int c = dgl_getch();
! 325: action = ttyplay_keyboard_action(c);
! 326: if (action != READ_DATA)
! 327: return action;
! 328:
! 329: }
! 330: }
! 331: return (action);
! 332: }
! 333:
! 334: void
! 335: ttywrite (char *buf, int len)
! 336: {
! 337: int i;
! 338:
! 339: for (i = 0; i < len; i++)
! 340: {
! 341: if (stripped != NO_GRAPHICS)
! 342: buf[i] = strip_gfx (buf[i]);
! 343: }
! 344:
! 345: fwrite (buf, 1, len, stdout);
! 346: }
! 347:
! 348: void
! 349: ttynowrite (char *buf, int len)
! 350: {
! 351: /* do nothing */
! 352: }
! 353:
! 354: int
! 355: ttyplay (FILE * fp, double speed, ReadFunc read_func,
! 356: WriteFunc write_func, WaitFunc wait_func, off_t offset)
! 357: {
! 358: int first_time = 1;
! 359: int r = READ_EOF;
! 360: struct timeval prev;
! 361:
! 362: /* for dtype's attempt to get the last clrscr and playback from there */
! 363: if (offset != -1)
! 364: {
! 365: fseek (fp, offset, SEEK_SET);
! 366: }
! 367:
! 368: while (1)
! 369: {
! 370: char *buf;
! 371: Header h;
! 372:
! 373: r = read_func (fp, &h, &buf, 0);
! 374: if (r != READ_DATA)
! 375: {
! 376: break;
! 377: }
! 378:
! 379: if (!first_time)
! 380: {
! 381: speed = wait_func (prev, h.tv, speed);
! 382: }
! 383: first_time = 0;
! 384:
! 385: write_func (buf, h.len);
! 386: prev = h.tv;
! 387: free (buf);
! 388: }
! 389: return r;
! 390: }
! 391:
! 392: static off_t
! 393: find_last_string_in_file(FILE * fp, const char *seq)
! 394: {
! 395: char buf[512];
! 396: struct stat mystat;
! 397: off_t offset = 0L;
! 398: const long readsz = sizeof(buf);
! 399: int bytes_read = 0;
! 400: const int seqlen = strlen(seq);
! 401: const char *reset_pos = seq + seqlen - 1;
! 402: const char *match_pos = reset_pos;
! 403:
! 404: fstat(fileno (fp), &mystat);
! 405: offset = mystat.st_size - readsz;
! 406: if (offset < 0)
! 407: offset = 0;
! 408: while (1)
! 409: {
! 410: const char *search_pos = 0;
! 411:
! 412: fseeko(fp, offset, SEEK_SET);
! 413: bytes_read = fread(buf, 1, readsz, fp);
! 414:
! 415: if (bytes_read <= 0)
! 416: break;
! 417:
! 418: search_pos = buf + bytes_read - 1;
! 419: while (search_pos >= buf)
! 420: {
! 421: int matched = *search_pos == *match_pos;
! 422: if (!matched && match_pos != reset_pos)
! 423: {
! 424: match_pos = reset_pos;
! 425: matched = *search_pos == *match_pos;
! 426: }
! 427: if (matched)
! 428: {
! 429: if (match_pos == seq)
! 430: return offset + (search_pos - buf);
! 431: --match_pos;
! 432: }
! 433: --search_pos;
! 434: }
! 435:
! 436: // If we've reached the start of the file, exit.
! 437: if (!offset)
! 438: break;
! 439:
! 440: offset -= readsz;
! 441: if (offset < 0)
! 442: offset = 0;
! 443: }
! 444:
! 445: return 0;
! 446: }
! 447:
! 448: static off_t
! 449: find_seek_offset_clrscr (FILE * fp)
! 450: {
! 451: off_t raw_seek_offset = 0;
! 452: off_t raw_seek_offset2 = 0;
! 453: off_t seek_offset_clrscr;
! 454:
! 455: raw_seek_offset = find_last_string_in_file(fp, "\033[2J");
! 456: raw_seek_offset2 = find_last_string_in_file(fp, "\033[H\033[J");
! 457: if (raw_seek_offset2>raw_seek_offset) raw_seek_offset=raw_seek_offset2;
! 458:
! 459: seek_offset_clrscr = 0;
! 460: /* now find last filepos that is less than seek offset */
! 461: fseek (fp, 0, SEEK_SET);
! 462: while (1)
! 463: {
! 464: char *buf;
! 465: Header h;
! 466: long offset;
! 467:
! 468: if (ttyread (fp, &h, &buf, 0) != READ_DATA)
! 469: {
! 470: break;
! 471: }
! 472:
! 473: free (buf);
! 474:
! 475: offset = ftell(fp);
! 476: if (offset < raw_seek_offset)
! 477: seek_offset_clrscr = ftell (fp);
! 478: else
! 479: break;
! 480: }
! 481:
! 482: return seek_offset_clrscr;
! 483: }
! 484:
! 485: #if 0 /* not used anymore */
! 486: void
! 487: ttyskipall (FILE * fp)
! 488: {
! 489: /*
! 490: * Skip all records.
! 491: */
! 492: ttyplay (fp, 0, ttyread, ttynowrite, ttynowait, 0);
! 493: }
! 494: #endif
! 495:
! 496: void
! 497: ttyplayback (FILE * fp, double speed, ReadFunc read_func, WaitFunc wait_func)
! 498: {
! 499: ttyplay (fp, speed, ttyread, ttywrite, wait_func, 0);
! 500: }
! 501:
! 502: void
! 503: ttypeek (FILE * fp, double speed)
! 504: {
! 505: int r;
! 506: do
! 507: {
! 508: setvbuf (fp, NULL, _IOFBF, 0);
! 509: r = ttyplay(fp, 0, ttyread, ttywrite, ttynowait, find_seek_offset_clrscr(fp));
! 510: if (r == READ_EOF) {
! 511: clearerr (fp);
! 512: setvbuf (fp, NULL, _IONBF, 0);
! 513: fflush (stdout);
! 514: r = ttyplay (fp, speed, ttypread, ttywrite, ttynowait, -1);
! 515: }
! 516: } while (r == READ_RESTART);
! 517: }
! 518:
! 519:
! 520: int
! 521: ttyplay_main (char *ttyfile, int mode, int resizex, int resizey)
! 522: {
! 523: double speed = 1.0;
! 524: ReadFunc read_func = ttyread;
! 525: WaitFunc wait_func = ttywait;
! 526: FILE *input = stdin;
! 527: struct termios old, new;
! 528: sighandler_t old_sigwinch;
! 529:
! 530: populate_gfx_array (stripped);
! 531:
! 532: input = efopen (ttyfile, "r");
! 533:
! 534: tcgetattr (0, &old); /* Get current terminal state */
! 535: new = old; /* Make a copy */
! 536: new.c_lflag &= ~(ICANON | ECHO | ECHONL); /* unbuffered, no echo */
! 537: new.c_cc[VMIN] = 1;
! 538: new.c_cc[VTIME] = 0;
! 539: tcsetattr (0, TCSANOW, &new); /* Make it current */
! 540: raw();
! 541:
! 542: if (resizex > 0 && resizey > 0) {
! 543: term_resizex = resizex;
! 544: term_resizey = resizey;
! 545: }
! 546:
! 547: got_sigwinch = 0;
! 548: old_sigwinch = signal(SIGWINCH, ttyplay_sigwinch_func);
! 549:
! 550: if (mode == 1)
! 551: ttypeek (input, speed);
! 552: else
! 553: ttyplayback (input, speed, read_func, wait_func);
! 554:
! 555: tcsetattr (0, TCSANOW, &old); /* Return terminal state */
! 556: fclose (input);
! 557:
! 558: if (old_sigwinch != SIG_ERR)
! 559: signal(SIGWINCH, old_sigwinch);
! 560:
! 561: term_resizex = term_resizey = -1;
! 562:
! 563: printf("\033[2J"); /* clear screen afterwards */
! 564:
! 565: return 0;
! 566: }
CVSweb