ref: 0f5e8eb6cd16ae6e1a54bf769826bd7df5cd321d
dir: /castor.c/
#include <u.h> #include <libc.h> #include <libsec.h> #include <String.h> #include <regexp.h> #include <draw.h> #include <event.h> #include <keyboard.h> #include <panel.h> #include <bio.h> #include <stdio.h> #include <ctype.h> //#include <plumb.h> #include "dat.h" #include "icons.h" void texthit(Panel *p, int b, Rtext *t); void message(char *s, ...); Image *backi; Image *fwdi; Image *reloadi; Panel *root; Panel *backp; Panel *fwdp; Panel *reloadp; Panel *entryp; Panel *urlp; Panel *textp; Panel *statusp; Panel *popup; Url *current_url; Mouse *mouse; Hist *hist = nil; enum { Mback, Mforward, Msearch, Mexit, }; char *menu3[] = { "back", "forward", "search", "exit", 0 }; char* replace_char(char* str, char find, char replace){ char *current_pos = strchr(str,find); while (current_pos){ *current_pos = replace; current_pos = strchr(current_pos,find); } return str; } char * cleanup(char *line) { char *src, *dst; for (src = dst = line; *src != '\0'; src++) { *dst = *src; if (*dst != '\r' && *dst != '\n') dst++; } *dst = '\0'; replace_char(line, '\t', ' '); return line; } void set_current_url(Url *url) { free(current_url); current_url = url; } Url * parseurl(char *url) { char *server, *port, *s, *e; Url *u; url = strdup(url); if((s = strpbrk(url, ":/")) != nil && s[0] == ':' && s[1] == '/' && s[2] == '/'){ server = s + 3; } else { s = smprint("gemini://%s", url); free(url); url = s; server = s + 9; } port = strdup("1965"); if((e = strpbrk(server, ":/")) != nil){ s = mallocz(e-server+1, 1); memmove(s, server, e-server); server = s; if(*e == ':'){ port = strdup(e+1); if((e = strchr(port, '/')) != nil) *e = 0; } } else { server = strdup(server); } u = calloc(1, sizeof(*u)); u->url = url; u->server = server; u->port = port; return u; } int starts_with(char *pre, char *str) { int lenpre = strlen(pre), lenstr = strlen(str); return lenstr < lenpre ? 1 : memcmp(pre, str, lenpre); } char * protocol(char *link) { if(strstr(link, "http://") != nil) { return " [WWW]"; } else if(strstr(link, "https://") != nil) { return " [WWW]"; } else if(strstr(link, "gopher://") != nil) { return " [GOPHER]"; } else if(strstr(link, "finger://") != nil) { return " [FINGER]"; } else { return ""; } } void gemini_get(Url *url) { Thumbprint *th; TLSconn conn; int fd; char *line; Biobuf body; Gmenu *m; m = malloc(sizeof *m); if(m==nil) sysfatal("malloc: %r"); m->text = nil; plrtstr(&m->text, 1000000, 0, 0, font, strdup(" "), 0, 0); set_current_url(url); message("loading %s...", url->url); char *naddr = netmkaddr(url->server, "tcp", url->port); fd = dial(naddr, 0, 0, 0); if(fd < 0){ message("unable to connect to %s: %r", url->server); return; } conn.serverName = url->server; memset(&conn, 0, sizeof(conn)); fd = tlsClient(fd, &conn); th = initThumbprints("/sys/lib/ssl/gemini", nil, "x509"); if(th != nil){ okCertificate(conn.cert, conn.certlen, th); freeThumbprints(th); free(conn.cert); } fprint(fd, "%s\r\n", url->url); Binit(&body, fd, OREAD); while((line = Brdstr(&body, '\n', 0)) != nil) { if (strstr(line, "=>") == nil) { /* Not a link so wrapping text */ char *base, *right_margin; int length, width; length = strlen(strdup(line)); base = strdup(line); width = 80; while(*base) { if(length <= width) { plrtstr(&m->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0); break; } right_margin = base+width; while(!isspace(*right_margin)) { right_margin--; if(right_margin == base) { right_margin += width; while(!isspace(*right_margin)) { if(*right_margin == '\0') break; right_margin++; } } } *right_margin = '\0'; plrtstr(&m->text, 1000000, 8, 0, font, strdup(cleanup(base)), 0, 0); length -= right_margin - base + 1; /* +1 for the space */ base = right_margin + 1; } } else { /* a link */ char *copy = strdup(cleanup(line)); strtok(copy, " "); char *link = strtok(NULL, " "); char *rest = strtok(NULL, "\0"); while(isspace(*rest)) rest++; char *label = smprint("→ %s%s", rest, protocol(link)); plrtstr(&m->text, 1000000, 8, 0, font, strdup(label), PL_HOT, strdup(link)); } free(line); } Bflush(&body); close(fd); plinittextview(textp, PACKE|EXPAND, ZP, m->text, texthit); pldraw(textp, screen); plinitlabel(urlp, PACKN|FILLX, strdup(url->url)); pldraw(urlp, screen); message("Castor!"); } void menuhit(int button, int item) { USED(button); switch(item){ case Mback: //backhit(backp, 1); break; case Mforward: //nexthit(fwdp, 1); break; case Msearch: //search(); break; case Mexit: exits(nil); break; } } void entryhit(Panel *p, char *t) { USED(p); switch(strlen(t)){ case 0: return; case 1: switch(*t){ case 'b': //backhit(backp, 1); break; case 'n': //nexthit(fwdp, 1); break; case 'q': exits(nil); break; default: message("unknown command %s", t); break; } break; default: gemini_get(parseurl(t)); } plinitentry(entryp, PACKN|FILLX, 0, "", entryhit); pldraw(root, screen); } void texthit(Panel *p, int b, Rtext *rt) { //message("text hit %s", rt->user); char *copy, *next_url; char *link = rt->user; copy = link; int len = strlen(copy); if ((strpbrk(link, " :/") == nil) || link[0] == '/' || link[len-1] == '/') { /* assuming relative URL */ message(copy); if (*copy == '/') { next_url = smprint("gemini://%s:%s/%s", current_url->server, current_url->port, copy+1); } else { next_url = smprint("gemini://%s:%s/%s", current_url->server, current_url->port, copy); } } else { /* absolute URL */ next_url = strdup(link); } free(link); if(strstr(next_url, "gemini://") != nil) { gemini_get(parseurl(next_url)); } else { message("%s protocol not supported yet!", next_url); } } void message(char *s, ...) { static char buf[1024]; char *out; va_list args; va_start(args, s); out = buf + vsnprint(buf, sizeof(buf), s, args); va_end(args); *out='\0'; plinitlabel(statusp, PACKN|FILLX, buf); pldraw(statusp, screen); flushimage(display, 1); } void mkpanels(void) { Panel *p, *ybar, *xbar, *m; m = plmenu(0, 0, menu3, PACKN|FILLX, menuhit); root = plpopup(0, EXPAND, 0, 0, m); p = plgroup(root, PACKN|FILLX); statusp = pllabel(p, PACKN|FILLX, "Castor!"); plplacelabel(statusp, PLACEW); //plbutton(p, PACKW|BITMAP|NOBORDER, backi, backhit); //plbutton(p, PACKW|BITMAP|NOBORDER, fwdi, nexthit); //plbutton(p, PACKW|BITMAP|NOBORDER, reloadi, reloadhit); pllabel(p, PACKW, "Go: "); entryp = plentry(p, PACKN|FILLX, 0, "", entryhit); p = plgroup(root, PACKN|FILLX); urlp = pllabel(p, PACKN|FILLX, ""); plplacelabel(urlp, PLACEW); p = plgroup(root, PACKN|EXPAND); ybar = plscrollbar(p, PACKW|USERFL); xbar = plscrollbar(p, IGNORE); textp = pltextview(p, PACKE|EXPAND, ZP, nil, nil); plscroll(textp, xbar, ybar); plgrabkb(entryp); } void eresized(int new) { if(new && getwindow(display, Refnone)<0) sysfatal("cannot reattach: %r"); plpack(root, screen->r); pldraw(root, screen); } Image* loadicon(Rectangle r, uchar *data, int ndata) { Image *i; int n; i = allocimage(display, r, RGBA32, 0, DNofill); if(i==nil) sysfatal("allocimage: %r"); n = loadimage(i, r, data, ndata); if(n<0) sysfatal("loadimage: %r"); return i; } void loadicons(void) { Rectangle r = Rect(0,0,16,16); backi = loadicon(r, ibackdata, sizeof ibackdata); fwdi = loadicon(r, ifwddata, sizeof ifwddata); reloadi = loadicon(r, ireloaddata, sizeof ireloaddata); } void scrolltext(int dy, int whence) { Scroll s; s = plgetscroll(textp); switch(whence){ case 0: s.pos.y = dy; break; case 1: s.pos.y += dy; break; case 2: s.pos.y = s.size.y+dy; break; } if(s.pos.y > s.size.y) s.pos.y = s.size.y; if(s.pos.y < 0) s.pos.y = 0; plsetscroll(textp, s); /* BUG: there is a redraw issue when scrolling This fixes the issue albeit not properly */ pldraw(textp, screen); } void main(int argc, char *argv[]) { Event e; Response *r; Url *url; if(argc == 2) url = parseurl(argv[1]); else url = parseurl("gemini.circumlunar.space/capcom/"); quotefmtinstall(); if(initdraw(nil, nil, "gemini")<0) sysfatal("initdraw: %r"); einit(Emouse|Ekeyboard); plinit(screen->depth); loadicons(); mkpanels(); gemini_get(url); eresized(0); for(;;){ switch(event(&e)){ case Ekeyboard: switch(e.kbdc){ default: plgrabkb(entryp); plkeyboard(e.kbdc); break; case Khome: scrolltext(0, 0); break; case Kup: scrolltext(-textp->size.y/4, 1); break; case Kpgup: scrolltext(-textp->size.y/2, 1); break; case Kdown: scrolltext(textp->size.y/4, 1); break; case Kpgdown: scrolltext(textp->size.y/2, 1); break; case Kend: scrolltext(-textp->size.y, 2); break; case Kdel: exits(nil); break; } break; case Emouse: mouse = &e.mouse; if(mouse->buttons & (8|16) && ptinrect(mouse->xy, textp->r)){ if(mouse->buttons & 8) scrolltext(textp->r.min.y - mouse->xy.y, 1); else scrolltext(mouse->xy.y - textp->r.min.y, 1); break; } plmouse(root, mouse); /* BUG: there is a redraw issue when scrolling This fixes the issue albeit not properly */ //pldraw(textp, screen); break; } } }