#include #include #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_ABORT, } 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 int carrytab; static char *savefile; #define Cisdigit(c) isdigit((unsigned char)(c)) static void init(void) { tabs = 0; tabs_a = 0; tabs_n = 0; curtab = 0; toptab = 0; carrytab = 0; savefile = 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 urls_from_file(const char *fn) { FILE *f; char *b; int a; int l; int c; void savec(char ch) { if (l >= a) b = realloc(b,a=l+8); b[l++] = ch; } f = fopen(fn,"r"); if (f == 0) { fprintf(stderr,"%s: %s: %s\n",__progname,fn,strerror(errno)); exit(1); } b = 0; a = 0; l = 0; while <"readloop"> (1) { c = getc(f); switch (c) { case EOF: if (l > 0) { fprintf(stderr,"%s: %s: warning: newline supplied at EOF\n",__progname,fn); savec('\0'); saveurl(b); } break <"readloop">; case '\n': savec('\0'); saveurl(b); l = 0; break; default: savec(c); break; } } free(b); fclose(f); } static void set_savefile(const char *fn) { free(savefile); savefile = strdup(fn); } 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; } if (!strcmp(*av,"-load")) { WANTARG(); urls_from_file(av[skip]); set_savefile(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) { if (carrytab) { standout(); addstr(">"); standend(); } else { 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; offscreen = line_off_screen(val); if (carrytab) { TAB *t; int rdmin; int rdmax; t = tabs[curtab]; if (val < curtab) { bcopy(&tabs[val],&tabs[val+1],(curtab-val)*sizeof(*tabs)); tabs[val] = t; rdmin = val; rdmax = curtab; } else { bcopy(&tabs[curtab+1],&tabs[curtab],(val-curtab)*sizeof(*tabs)); tabs[val] = t; rdmin = curtab; rdmax = val; } curtab = val; if (offscreen) { redraw_all(); } else { int i; for (i=rdmin;i<=rdmax;i++) redraw_scrline(i-toptab); } } else { if (offscreen) { 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 <"pkrv"> ((*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 */ case <"pkrv"> PKRV_ABORT: s->l = -1; return; 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); if (s->l < 0) { free(s->buf); return(0); } insert_n_at(s,1,"",s->l); return(s->buf); } static int alldigits(const char *s, int l) { for (;l>0;s++,l--) if (! Cisdigit(*s)) return(0); return(1); } 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: if ( ((s->l == 4) && !bcmp(s->buf,"URL:",4)) || ((s->l == 6) && !bcmp(s->buf,"-more-",6)) || ((s->l >= 2) && (s->buf[s->l-1] == '.') && alldigits(s->buf,s->l-1)) ) { s->l = 0; s->c = 0; } break; default: abort(); break; } return(PKRV_DOIT); } static void new_tab_at_pos(const char *s, int pos) { TAB *t; int i; saveurl(s); if (pos != tabs_n) { t = tabs[tabs_n-1]; bcopy(&tabs[pos],&tabs[pos+1],(tabs_n-1-pos)*sizeof(*tabs)); tabs[pos] = t; for (i=pos;i ",&ilcb_new_tab); input_line_read(&les); s = input_line_done(&les); if (s) { new_tab_at_pos(s,pos); free(s); } redraw_scrline(LINES-1); } static void start_curses(void) { initscr(); noecho(); cbreak(); leaveok(stdscr,FALSE); #ifdef __linux__ // doesn't seem to have flushok()! fflush(0); #else flushok(stdscr,TRUE); #endif } 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); setpgid(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; int i; 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; for (i=curtab;i<=tabs_n;i++) redraw_scrline(i-toptab); } else if (curtab > 0) { curtab --; if (line_off_screen(curtab)) { redraw_all(); } else { redraw_scrline(curtab+1-toptab); redraw_scrline(curtab-toptab); } } else { redraw_scrline(0); } carrytab = 0; } 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 edit_tab(void) { TAB *t; char *s; LESTATE les; if (curtab >= tabs_n) return; t = tabs[curtab]; if (t->proc >= 0) return; input_line_init(&les,"New tab> ",&ilcb_new_tab); insert_n_at(&les,strlen(t->url),t->url,0); les.c = les.l; input_line_read(&les); s = input_line_done(&les); if (s) { free(t->url); t->url = s; redraw_scrline(curtab-toptab); } redraw_scrline(LINES-1); } static void pickup_or_drop_tab(void) { if (curtab >= tabs_n) return; carrytab = ! carrytab; redraw_scrline(curtab-toptab); } static void clone_tab(void) { TAB *t; if (curtab >= tabs_n) return; t = tabs[curtab]; new_tab_at_pos(t->url,curtab+1); } static void save_to(const char *fn) { __label__ msgthrow; FILE *f; char *tfn; int i; int err; auto void msg(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void msg(const char *fmt, ...) { va_list ap; char *msg; va_start(ap,fmt); vasprintf(&msg,fmt,ap); va_end(ap); move(LINES-1,0); standout(); addnstr(msg,COLS-1); standend(); clrtoeol(); refresh(); #ifdef KEY_RESIZE while (getch() == KEY_RESIZE) ; #else getch(); #endif goto msgthrow; } if (0) { msgthrow:; if (f) fclose(f); return; } asprintf(&tfn,"%s-TEMP",fn); f = fopen(tfn,"w"); if (f == 0) msg("%s: %s",tfn,strerror(errno)); err = 0; for (i=0;iurl) < 0) { err = 1; break; } } if (fclose(f) < 0) err = 1; f = 0; if (err) { unlink(tfn); msg("Error writing temp file %s",tfn); } if (rename(tfn,fn) < 0) { err = errno; unlink(tfn); msg("rename %s -> %s: %s",tfn,fn,strerror(err)); } msg("Done."); } static PKRV ilcb_save_file(LESTATE *s __attribute__((__unused__)), PKOP op) { switch (op) { case PKOP_INIT: case PKOP_DONE: case PKOP_PREKEY: case PKOP_POSTKEY: break; default: abort(); break; } return(PKRV_DOIT); } static void save(void) { char *s; LESTATE les; input_line_init(&les,"Save to> ",&ilcb_save_file); if (savefile) insert_n_at(&les,strlen(savefile),savefile,0); les.c = les.l; input_line_read(&les); s = input_line_done(&les); if (s) { save_to(s); set_savefile(s); free(s); } redraw_scrline(LINES-1); } 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(tabs_n); break; case 'r': run_or_resume(); break; case 'C': clone_tab(); break; case 'D': delete_tab(); break; case 'E': edit_tab(); redraw_scrline(curtab-toptab); break; case 'K': kill_tab(); break; case 'N': new_tab(curtab+1); break; case 'Q': quit(); break; case 'S': save(); break; case ' ': move_curtab_by((LINES*4)/5); break; case 'b': move_curtab_by(-((LINES*4)/5)); break; case '<': move_curtab_to(0); break; case '>': move_curtab_to(tabs_n-1); break; case '.': pickup_or_drop_tab(); 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); start_curses(); redraw_all(); while (1) keycmd(); }