#include #include #include #include #include #include #include #include #include #include extern const char *__progname; typedef enum { PKOP_INIT = 1, PKOP_PREKEY, PKOP_POSTKEY, PKOP_DONE, } PKOP; typedef enum { PKRV_DOIT = 1, PKRV_IGNORE, } PKRV; typedef struct lestate LESTATE; typedef struct tab TAB; struct lestate { const char *prompt; int pl; char *buf; int a; int l; int o; int c; int ks; PKRV (*callback)(LESTATE *, PKOP); void *cbpriv; } ; struct tab { char *url; pid_t proc; } ; static TAB **tabs; static int tabs_a; static int tabs_n; static int curtab; static int toptab; static void init(void) { tabs = 0; tabs_a = 0; tabs_n = 0; } static void saveurl(const char *s) { TAB *t; int i; if (tabs_n >= tabs_a) { tabs = realloc(tabs,(tabs_a=tabs_n+8)*sizeof(*tabs)); for (i=tabs_n;iurl = strdup(s); t->proc = -1; tabs_n ++; } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { saveurl(*av); continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-url")) { WANTARG(); saveurl(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void redraw_scrline(int l) { TAB *t; if ((l < 0) || (l >= LINES)) return; t = (toptab+l < tabs_n) ? tabs[toptab+l] : 0; move(l,0); if (t) { int len; if (toptab+l == curtab) addstr(">"); else addstr(" "); if (t->proc < 0) addstr("-"); else addstr(" "); len = strlen(t->url); do <"printed"> { if (len <= COLS-3) { addstr(t->url); } else { char *css; int x1; int x2; css = strstr(t->url,"://"); if (css) { char *s; char *rs; s = index(css+3,'/'); if (s) { rs = rindex(s,'/'); if ((rs != s) && (((s+1)-t->url)+3+((t->url+len)-rs) <= COLS-3)) { x1 = (COLS-3) - ((s+1)-t->url) - ((t->url+len)-rs) - 3; x2 = x1 / 2; x1 -= x2; addnstr(t->url,(s+1+x1)-t->url); addstr("..."); addstr(rs-x2); break <"printed">; } } } x1 = (COLS-3 - 3) / 2; x2 = (COLS-3 - 3) - x1; addnstr(t->url,x1); addstr("..."); addstr(t->url+len-x2); } } while (0); } clrtoeol(); } static int line_off_screen(int l) { return((l >= toptab + LINES) || (l < toptab)); } static void redraw_all(void) { int l; clear(); if (curtab >= tabs_n) curtab = tabs_n - 1; if (curtab < 0) curtab = 0; if (line_off_screen(curtab)) toptab = curtab - (LINES / 2); if (toptab >= tabs_n) toptab = tabs_n; if (toptab < 0) toptab = 0; for (l=0;l= tabs_n) val = tabs_n - 1; if (val < 0) val = 0; if (val == curtab) return; if (line_off_screen(val)) { curtab = val; redraw_all(); } else { int old; old = curtab; curtab = val; redraw_scrline(old-toptab); redraw_scrline(curtab-toptab); } } static void move_curtab_by(int inc) { move_curtab_to(curtab+inc); } static void input_line_init(LESTATE *s, const char *prompt, PKRV (*cb)(LESTATE *, PKOP)) { s->prompt = prompt; s->pl = strlen(prompt); s->buf = 0; s->a = 0; s->l = 0; s->c = 0; s->o = 0; s->callback = cb; s->cbpriv = 0; (*cb)(s,PKOP_INIT); } static void input_line_update(LESTATE *s) { int pspc; int cspc; int margin; int dots; int cx; move(LINES-1,0); if (COLS-1 > s->pl) { pspc = s->pl; } else { pspc = COLS-1 - 1; } cspc = COLS-1 - pspc; if (cspc > 0) { if (pspc >= 10) { dots = 3; } else if (pspc <= 5) { dots = 0; } else { dots = (pspc - 4) / 2; } cspc -= dots * 2; margin = cspc / 5; if (s->c < s->o+margin) { s->o = s->c - (cspc-1) + margin; } else if (s->c >= s->o+cspc-margin) { s->o = s->c - margin; } if (s->o < 0) s->o = 0; } addstr(s->prompt+s->pl-pspc); cx = pspc; if (s->o > 0) { addnstr("...",dots); cx += dots; } if (s->l-s->o > cspc) { addnstr(s->buf+s->o,cspc); if (s->o+cspc > s->l) addnstr("...",dots); } else { addnstr(s->buf+s->o,s->l-s->o); } clrtoeol(); move(LINES-1,cx+s->c-s->o); } static void delete_n_at(LESTATE *s, int n, int at) { if ((n < 0) || (at < 0) || (n > s->l) || (at > s->l) || (n+at > s->l)) abort(); if (n+at < s->l) bcopy(s->buf+at+n,s->buf+at,s->l-(n+at)); s->l -= n; } static void insert_n_at(LESTATE *s, int n, const char *src, int at) { if ((n < 0) || (at < 0) || (at > s->l)) abort(); if (s->l+n > s->a) s->buf = realloc(s->buf,s->a=s->l+n+8); if (at < s->l) bcopy(s->buf+at,s->buf+at+n,s->l-at); bcopy(src,s->buf+at,n); s->l += n; } static void input_line_read(LESTATE *s) { while (1) { input_line_update(s); refresh(); s->ks = getch(); switch ((*s->callback)(s,PKOP_PREKEY)) { case PKRV_DOIT: switch (s->ks) { case 0x00: /* ^@ */ /* XXX */ break; case 0x01: /* ^A */ s->c = 0; break; case 0x02: /* ^B */ if (s->c > 0) s->c --; break; case 0x04: /* ^D */ if (s->c < s->l) delete_n_at(s,1,s->c); break; case 0x05: /* ^E */ s->c = s->l; break; case 0x06: /* ^F */ if (s->c < s->l) s->c ++; break; case 0x07: /* ^G */ /* XXX */ break; case 0x08: /* ^H */ case 0x7f: /* DEL */ if (s->c > 0) delete_n_at(s,1,--s->c); break; case 0x09: /* ^I */ /* XXX */ break; case 0x0a: /* ^J */ case 0x0d: /* ^M */ return; break; case 0x0b: /* ^K */ s->l = s->c; break; case 0x0c: /* ^L */ clearok(stdscr,TRUE); break; case 0x14: /* ^T */ if (s->c >= 2) { char c; c = s->buf[s->c-2]; s->buf[s->c-2] = s->buf[s->c-1]; s->buf[s->c-1] = c; } break; case 0x15: /* ^U */ /* XXX */ break; case 0x16: /* ^V */ /* XXX */ break; case 0x17: /* ^W */ /* XXX */ break; case 0x18: /* ^X */ s->c = 0; s->l = 0; break; case 0x19: /* ^Y */ /* XXX */ break; case 0x1b: /* ^[, ESC */ /* XXX */ break; case 0x20 ... 0x7e: case 0xa0 ... 0xff: { char c; c = s->ks; insert_n_at(s,1,&c,(++s->c)-1); } break; default: break; } break; case PKRV_IGNORE: break; default: abort(); break; } (*s->callback)(s,PKOP_POSTKEY); } } static char *input_line_done(LESTATE *s) { (*s->callback)(s,PKOP_DONE); insert_n_at(s,1,"",s->l); return(s->buf); } static PKRV ilcb_new_tab(LESTATE *s, PKOP op) { switch (op) { case PKOP_INIT: case PKOP_DONE: break; case PKOP_PREKEY: switch (s->ks) { case 0x00: /* ^@ */ s->ks = 0x0a; break; case 0x0a: /* ^J */ case 0x0d: /* ^M */ case 0x20: /* space */ return(PKRV_IGNORE); break; } break; case PKOP_POSTKEY: break; default: abort(); break; } return(PKRV_DOIT); } static void new_tab(void) { char *s; LESTATE les; input_line_init(&les,"New tab> ",&ilcb_new_tab); input_line_read(&les); s = input_line_done(&les); if (s) { saveurl(s); free(s); redraw_scrline(tabs_n-1-toptab); } redraw_scrline(LINES-1); } static void start_curses(void) { initscr(); noecho(); cbreak(); leaveok(stdscr,FALSE); flushok(stdscr,TRUE); } static void stop_curses(void) { endwin(); } static void await_tab(TAB *t) { pid_t kid; int status; do kid = waitpid(t->proc,&status,WUNTRACED); while (kid != t->proc); if (WIFEXITED(status) || WIFSIGNALED(status)) { t->proc = -1; } else if (! WIFSTOPPED(status)) { fprintf(stderr,"%s: wait status %d violates trichotomy\n",__progname,status); exit(1); } tcsetpgrp(1,getpgrp()); } static void run_tab(TAB *t) { pid_t kid; int xp[2]; int e; int n; fflush(0); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } kid = fork(); if (kid == 0) { close(xp[0]); fcntl(xp[1],F_SETFD,FD_CLOEXEC); setpgrp(0,0); write(xp[1],&e,1); read(xp[1],&e,1); execlp("lynx","lynx","-cookies",t->url,(char *)0); write(xp[1],&e,sizeof(e)); _exit(0); } close(xp[1]); read(xp[0],&e,1); tcsetpgrp(1,kid); write(xp[0],&e,1); n = recv(xp[0],&e,sizeof(e),MSG_WAITALL); switch (n) { case 0: break; case sizeof(e): fprintf(stderr,"%s: exec lynx: %s\n",__progname,strerror(e)); exit(1); break; default: fprintf(stderr,"%s: exec pipe error: read %d, wanted %d\n",__progname,n,(int)sizeof(e)); exit(1); break; } close(xp[0]); t->proc = kid; await_tab(t); } static void resume_tab(TAB *t) { tcsetpgrp(1,t->proc); killpg(t->proc,SIGCONT); await_tab(t); } static void run_or_resume(void) { TAB *t; if (curtab >= tabs_n) return; stop_curses(); t = tabs[curtab]; if (t->proc < 0) { run_tab(t); } else { resume_tab(t); } start_curses(); redraw_all(); } static void quit(void) { clear(); move(LINES-1,0); refresh(); endwin(); exit(0); } static void delete_tab(void) { TAB *t; if (curtab >= tabs_n) return; t = tabs[curtab]; if (t->proc >= 0) return; free(t->url); tabs_n --; if (curtab < tabs_n) { bcopy(&tabs[curtab+1],&tabs[curtab],(tabs_n-curtab)*sizeof(*tabs)); tabs[tabs_n] = t; } else if (curtab > 0) { curtab --; } } static void kill_tab(void) { TAB *t; if (curtab >= tabs_n) return; t = tabs[curtab]; if (t->proc < 0) return; kill(t->proc,SIGTERM); t->proc = -1; redraw_scrline(curtab-toptab); } static void keycmd(void) { int ks; move(curtab-toptab,0); ks = getch(); switch (ks) { case 'j': move_curtab_by(1); break; case 'k': move_curtab_by(-1); break; case 'n': new_tab(); break; case 'r': run_or_resume(); break; case 'D': delete_tab(); break; case 'K': kill_tab(); break; case 'Q': quit(); break; #ifdef KEY_RESIZE case KEY_RESIZE: redraw_all(); break; #endif } } int main(int, char **); int main(int ac, char **av) { init(); handleargs(ac,av); curtab = 0; toptab = 0; start_curses(); redraw_all(); while (1) keycmd(); }