ref: 0946587d6c95b365174c88abe47b6f4fcb465741
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;
}
void
show(Gmenu *m)
{
plinittextview(textp, PACKE|EXPAND, ZP, m->text, texthit);
pldraw(textp, screen);
plinitlabel(urlp, PACKN|FILLX, m->url->url);
pldraw(urlp, screen);
message("Castor9");
}
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;
}
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;
Hist *h;
h = malloc(sizeof *h);
if(h == nil)
sysfatal("malloc: %r");
plrtstr(&m->text, 1000000, 0, 0, font, strdup(" "), 0, 0);
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);
m->url = url;
char c;
do {
c = Bgetc(&body);
} while (c != '\n');
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");
char *label;
if(rest != NULL)
{
while(isspace(*rest))
{
rest++;
}
label = smprint("→ %s%s", rest, protocol(link));
}else{
label = smprint("→ %s%s", link, protocol(link));
}
plrtstr(&m->text, 1000000, 8, 0, font, strdup(label), PL_HOT, strdup(link));
}
free(line);
}
h->p = hist;
h->n = nil;
h->m = m;
hist = h;
Bflush(&body);
close(fd);
show(m);
}
void
backhit(Panel *p, int b)
{
USED(p);
if(b!=1)
return;
if(hist==nil || hist->p==nil)
return;
hist->p->n = hist;
hist = hist->p;
//set_current_url(hist->m->url);
show(hist->m);
}
void
nexthit(Panel *p, int b)
{
USED(p);
if(b!=1)
return;
if(hist==nil || hist->n==nil)
return;
hist = hist->n;
//set_current_url(hist->m->url);
show(hist->m);
}
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;
}
}
}