[BACK]Return to ttyplay.c CVS log [TXT][DIR] Up to [contributed] / dgamelaunch-openbsd

Annotation of dgamelaunch-openbsd/ttyplay.c, Revision 1.3

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

CVSweb