/* * This program is in the public domain. * * Displays text in frames. The input is taken to consist of multiple * input frames, delimited by a line matching a command-line argument. * The first frame is displayed. Space steps forward one frame, b * backward; either can be given a digit-string prefix to jump that * many frames. * * We assume frames are small enough to fit on the screen. We also * assume we're displaying in an mterm; this is abstractly wrong, but * pragmatically useful - and very helpful from a code simplicity * perspective. * * Command line: $0 FILE SEP * * FILE names a file containing the frames; SEP is the separator line * contents. * * Lines may begin with escape sequences. Specifically, a line may * begin with * * @\@" * The rest of the line is processed as normal text, even * if it would otherwise be special somehow. (This is how * you get a displayed line to begin with @\@.) * * @\@swsh: * @\@swdht: * @\@swdhb: * @\@dwsh: * @\@dwdht: * @\@dwdhb: * The rest of the line is (single|double)-width * (single-height|double-height (top|bottom)-half), as the * sequence spelling abbreviates. * * Except for @\@", the sequence is stripped and the rest of the line * is reprocessed. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; static const char *datafile = 0; static const char *sep = 0; typedef struct frame FRAME; struct frame { FRAME *flink; FRAME *blink; char *data; int len; } ; static FRAME *frame_h; static FRAME *frame_t; static int seplen; static struct termios save_tio; static AIO_OQ oq; static FRAME *curframe; static int want_redisplay; static int i_id; static int o_id; static int r_id; static unsigned int pfx; static ES pfxtext; static void nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void no_nbio(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)&~O_NONBLOCK); } static void new_frame(const char *content, int len) { FRAME *f; f = malloc(sizeof(FRAME)+len); f->data = (void *)(f+1); f->len = len; bcopy(content,f->data,len); f->flink = 0; f->blink = frame_t; if (frame_t) frame_t->flink = f; else frame_h = f; frame_t = f; } static int empty_frame(FRAME *f) { int i; for (i=f->len-1;i>=0;i--) if (f->data[i] != '\n') return(0); return(1); } static void drop_frame(FRAME *f) { if (f->flink) f->flink->blink = f->blink; else frame_t = f->blink; if (f->blink) f->blink->flink = f->flink; else frame_h = f->flink; free(f); } static void load_frames(void) { FILE *f; ES a; int c; char *b; int l; f = fopen(datafile,"r"); if (! f) { fprintf(stderr,"%s: %s: %s\n",__progname,datafile,strerror(errno)); exit(1); } frame_h = 0; frame_t = 0; es_init(&a); while <"read"> (1) { c = getc(f); switch (c) { case EOF: if (ferror(f)) { fprintf(stderr,"%s: read error on %s: %s\n",__progname,datafile,strerror(errno)); exit(1); } new_frame(es_buf(&a),es_len(&a)); break <"read">; case '\n': b = es_buf(&a); l = es_len(&a); if ((l >= seplen) && !bcmp(b+l-seplen,sep,seplen)) { if (l == seplen) { new_frame(0,0); es_clear(&a); break; } else if (b[l-seplen] == '\n') { new_frame(b,l-seplen-1); es_clear(&a); break; } } // fall through default: es_append_1(&a,c); break; } } if (frame_h && empty_frame(frame_h)) drop_frame(frame_h); if (frame_t && empty_frame(frame_t)) drop_frame(frame_t); if (! frame_h) { fprintf(stderr,"%s: %s: no frames!\n",__progname,datafile); exit(1); } fclose(f); } static void setup_term(void) { struct termios tio; tcgetattr(0,&save_tio); tio = save_tio; tio.c_oflag = 0; tio.c_lflag = 0; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; tcsetattr(0,TCSANOW|TCSASOFT,&tio); } static void closedown(void) { aio_remove_poll(i_id); aio_remove_poll(o_id); aio_remove_block(r_id); aio_oq_flush(&oq); no_nbio(0); no_nbio(1); write(1,"\37cursor: underscore/none\1",25); } static int prefix(void) { return((es_len(&pfxtext)<1)?1:pfx); es_clear(&pfxtext); } static void typed_char(unsigned char c) { int i; int dv; switch (c) { case 'Q': closedown(); exit(0); break; case 'b': for (i=prefix();(i>0)&&(curframe->blink);i--) { curframe = curframe->blink; want_redisplay = 1; } break; case 'f': case ' ': for (i=prefix();(i>0)&&(curframe->flink);i--) { curframe = curframe->flink; want_redisplay = 1; } break; case '<': prefix(); curframe = frame_h; want_redisplay = 1; break; case '>': prefix(); curframe = frame_t; want_redisplay = 1; break; case '\14': prefix(); want_redisplay = 1; break; case '0': dv = 0; if (0) { case '1': dv = 1; } if (0) { case '2': dv = 2; } if (0) { case '3': dv = 3; } if (0) { case '4': dv = 4; } if (0) { case '5': dv = 5; } if (0) { case '6': dv = 6; } if (0) { case '7': dv = 7; } if (0) { case '8': dv = 8; } if (0) { case '9': dv = 9; } pfx = (pfx * 10) + dv; es_append_1(&pfxtext,c); if (0) { case '\b': case '\177': if (es_len(&pfxtext) < 1) break; es_pop_1(&pfxtext); pfx /= 10; } aio_oq_queue_point(&oq,"\22\10\16",3); for (i=es_len(&pfxtext);i>0;i--) aio_oq_queue_point(&oq,"\b",1); aio_oq_queue_copy(&oq,es_buf(&pfxtext),es_len(&pfxtext)); break; } } static void rd_i(void *arg __attribute__((__unused__))) { unsigned char rbuf[256]; int nr; int i; nr = read(0,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } closedown(); fprintf(stderr,"%s: stdin read: %s\n",__progname,strerror(errno)); exit(1); } if (nr == 0) { closedown(); fprintf(stderr,"%s: stdin read EOF\n",__progname); exit(1); } for (i=0;i= 3) && !bcmp(body,"@\\@",3)) { if ((len >= 4) && (body[3] == '"')) { body += 4; len -= 4; break; } else if ((len >= 8) && !bcmp(body+3,"swsh:",5)) { aio_oq_queue_point(&oq,"\37swsh:\1",AIO_STRLEN); n = 8; } else if ((len >= 9) && !bcmp(body+3,"swdht:",6)) { aio_oq_queue_point(&oq,"\37swdht:\1",AIO_STRLEN); n = 9; } else if ((len >= 9) && !bcmp(body+3,"swdhb:",6)) { aio_oq_queue_point(&oq,"\37swdhb:\1",AIO_STRLEN); n = 9; } else if ((len >= 8) && !bcmp(body+3,"dwsh:",5)) { aio_oq_queue_point(&oq,"\37dwsh:\1",AIO_STRLEN); n = 8; } else if ((len >= 9) && !bcmp(body+3,"dwdht:",6)) { aio_oq_queue_point(&oq,"\37dwdht:\1",AIO_STRLEN); n = 9; } else if ((len >= 9) && !bcmp(body+3,"dwdhb:",6)) { aio_oq_queue_point(&oq,"\37dwdhb:\1",AIO_STRLEN); n = 9; } else { n = 3; } body += n; len -= n; } aio_oq_queue_point(&oq,body,len); aio_oq_queue_point(&oq,"\n",1); } static int maybe_redisplay(void *arg __attribute__((__unused__))) { const char *b; int l; int o; const char *nl; if (! want_redisplay) return(AIO_BLOCK_NIL); want_redisplay = 0; aio_oq_queue_point(&oq,"\x0c",1); // ^L b = curframe->data; l = curframe->len; o = 0; while (o < l) { nl = memchr(b+o,'\n',l-o); if (nl) { redisplay_line(b+o,nl-(b+o)); o = (nl + 1) - b; } else { redisplay_line(b+o,l-o); o = l; } } aio_oq_queue_point(&oq,"\22\10\16",3); return(AIO_BLOCK_LOOP); } int main(int, char **); int main(int ac, char **av) { if (ac != 3) { fprintf(stderr,"Usage: %s FILE SEP\n",__progname); exit(1); } datafile = av[1]; sep = av[2]; seplen = strlen(sep); load_frames(); setup_term(); aio_poll_init(); aio_oq_init(&oq); es_init(&pfxtext); nbio(0); nbio(1); i_id = aio_add_poll(0,&aio_rwtest_always,&aio_rwtest_never,&rd_i,0,0); o_id = aio_add_poll(1,&aio_rwtest_never,&wtest_o,0,&wr_o,0); curframe = frame_h; want_redisplay = 1; r_id = aio_add_block(&maybe_redisplay,0); aio_oq_queue_point(&oq,"\37cursor: none/none\1",AIO_STRLEN); aio_event_loop(); return(0); }