shithub: castor9

ref: 0f5e8eb6cd16ae6e1a54bf769826bd7df5cd321d
dir: /castor.c/

View raw version
#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;
		}
	}
}