gophra

gopher client for plan9
git clone git://nsmpr.xyz/gophra.git
Log | Files | Refs

gophra.c (9680B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <thread.h>
      5 #include <mouse.h>
      6 #include <cursor.h>
      7 #include <keyboard.h>
      8 #include <frame.h>
      9 
     10 #include "uri.h"
     11 #include "load.h"
     12 
     13 enum {
     14 	DBorder = DBlue,
     15 	DWBG    = DBlack,
     16 	DWFG    = 0xAAAAAAFF,
     17 	DWFGL   = DWhite,
     18 	DStatus = DYellow,
     19 };
     20 
     21 typedef struct History History;
     22 struct History {
     23 	char addr[255];
     24 	char path[255];
     25 	History *prev;
     26 };
     27 
     28 Channel *lc, *sc;
     29 History hzero, *hp;
     30 Image *Iborder, *Iwbg, *Iwfg, *Iwfgl, *Istatus;
     31 Rectangle rstatus, rw;
     32 Text text;
     33 char status[256], path[256], addr[256], type;
     34 const char *tmpfile = "/tmp/gophra.tmp";
     35 long scroll;
     36 Mousectl *mc;
     37 Keyboardctl *kc;
     38 
     39 char* getlinep(Text*, ulong);
     40 URI extracturi(char *s);
     41 int runuri(char*);
     42 int save(char*, char*, char*);
     43 void back(void);
     44 void calcrects(void);
     45 void drawborder(void);
     46 void drawmenu(void);
     47 void drawstatus(void);
     48 void drawtext(void);
     49 void handlelink(char*, char, char*);
     50 void loadtext(char*, char*);
     51 void runhold(void*);
     52 void runpage(void*);
     53 void usage(void);
     54 
     55 void
     56 threadmain(int argc, char **argv)
     57 {
     58 	URI uri;
     59 	char *sv;
     60 	Mouse mv;
     61 	Rune  kv;
     62 	int   rv[2];
     63 	Text tv;
     64 	
     65 	long sline;
     66 	char *s;
     67 	int mpress, flush;
     68 
     69 	ARGBEGIN{
     70 	default:
     71 		usage();
     72 		break;
     73 	}ARGEND;
     74 
     75 	if(initdraw(0, 0, "gophra") < 0)
     76 		sysfatal("inidraw failed: %r");
     77 
     78 	Iborder = allocimage(display, Rect(0,0,1,1),
     79 		RGB24, 1, DBorder);
     80 	Iwbg = allocimage(display, Rect(0,0,1,1),
     81 		RGB24, 1, DWBG);
     82 	Iwfg = allocimage(display, Rect(0,0,1,1),
     83 		RGB24, 1, DWFG);
     84 	Iwfgl = allocimage(display, Rect(0,0,1,1),
     85 		RGB24, 1, DWFGL);
     86 	Istatus = allocimage(display, Rect(0,0,1,1),
     87 		RGB24, 1, DStatus);
     88 
     89 	mpress = 0;
     90 	flush = 0;
     91 	scroll = 0;
     92 	hp = &hzero;
     93 	lc = chancreate(sizeof(Text), 0);
     94 	sc = chancreate(sizeof(char*), 0);
     95 	snprint(status, 255, "gophra!");
     96 
     97 	calcrects();
     98 	drawborder();
     99 	drawstatus();
    100 	drawmenu();
    101 	flushimage(display, 1);
    102 
    103 	if (argc == 1) {
    104 		runuri(argv[0]);
    105 	} else if (argc > 0) {
    106 		usage();
    107 	}
    108 
    109 	if((mc = initmouse(0, screen)) == nil)
    110 		sysfatal("initmouse failed: %r");
    111 	if((kc = initkeyboard(0)) == nil)
    112 		sysfatal("initkeyboard failed: %r");
    113 
    114 	Alt alts[] = {
    115 		{kc->c, &kv, CHANRCV},
    116 		{mc->c, &mv, CHANRCV},
    117 		{mc->resizec, rv, CHANRCV},
    118 		{lc, &tv, CHANRCV},
    119 		{sc, &sv, CHANRCV},
    120 		{0, 0, CHANEND},
    121 	};
    122 
    123 	for (;;) {
    124 		switch (alt(alts)) {
    125 		case 0: /* keyboard */
    126 			if (kv == 0x7f) threadexitsall(nil);
    127 			if (kv == 'g') {
    128 				char buf[256];
    129 				buf[0] = 0;
    130 				if (enter("goto:", buf, 255, mc, kc, nil) >0)
    131 					if (runuri(buf) == 0){
    132 						drawmenu();
    133 						flush = 1;
    134 					}
    135 			}
    136 			break;
    137 		case 1: /* mouse */
    138 			if (ptinrect(mv.xy, rw) != 1) break;
    139 			if (mv.buttons == 0) mpress = 0;
    140 			if (mv.buttons == 8) {
    141 				mpress = 1;
    142 				scroll -= 1 + (mv.xy.y-rw.min.y)/font->height;
    143 				if (scroll < 0) scroll = 0;
    144 				drawmenu();
    145 				flush = 1;
    146 			}
    147 			if (mv.buttons == 16) {
    148 				mpress = 1;
    149 				scroll += 1 + (mv.xy.y-rw.min.y)/font->height;
    150 				drawmenu();
    151 				flush = 1;
    152 			}
    153 			if (mpress != 0) break;
    154 			if (mv.buttons == 1) {
    155 				mpress = 1;
    156 				sline = (mv.xy.y - rw.min.y) / font->height +
    157 					scroll;
    158 				s = getlinep(&text, sline);
    159 				if (s == nil) break;
    160 			 	uri = extracturi(s);
    161 				snprint(status, 255, "%s:%s/%s", uri.host, uri.port, uri.path);
    162 				freeuri(&uri);
    163 				drawstatus();
    164 				flush = 1;
    165 			}
    166 			if (mv.buttons == 2) {
    167 				mpress = 1;
    168 				sline = (mv.xy.y - rw.min.y) / font->height +
    169 					scroll;
    170 				s = getlinep(&text, sline);
    171 				if (s == nil) break;
    172 			 	uri = extracturi(s);
    173 				snprint(addr, 1024, "tcp!%s!%s", uri.host, uri.port); 
    174 				handlelink(addr, uri.path[0], uri.path + 1);
    175 				freeuri(&uri);
    176 			}
    177 			if (mv.buttons == 4) {
    178 				mpress = 1;
    179 				back();
    180 			}
    181 			break;
    182 		case 2: /* resize */
    183 			if(getwindow(display, Refnone) < 0) sysfatal("resize failed: %r");
    184 			calcrects();
    185 			drawborder();
    186 			drawstatus();
    187 			drawmenu();
    188 			flush = 1;
    189 			break;
    190 		case 3: /* loading */
    191 			realloc(text.data, 0);
    192 			text = tv;
    193 			scroll = 0;
    194 			drawmenu();
    195 			flush = 1;
    196 			break;
    197 		case 4: /* status */
    198 			snprint(status, 255, "%s", sv);
    199 			drawstatus();
    200 			flush = 1;
    201 			realloc(sv, 0);
    202 			break;
    203 		}
    204 		if (flush != 0) {
    205 			flush = 0;
    206 			flushimage(display, 1);
    207 		}
    208 	}
    209 }
    210 
    211 void
    212 usage(void)
    213 {
    214 	fprint(2, "usage: %s [gopher_uri]\n", argv0);
    215 	threadexitsall("usage");
    216 }
    217 
    218 void
    219 back(void)
    220 {
    221 	History *oldh;
    222 	if (hp == &hzero) return;
    223 	scroll = 0;
    224 	oldh = hp;
    225 	hp = hp->prev;
    226 	free(oldh);
    227 	loadtext(hp->addr, hp->path);
    228 }
    229 
    230 void
    231 calcrects(void)
    232 {
    233 	rw = Rect(
    234 		screen->r.min.x + 4,
    235 		screen->r.min.y + 8 + font->height,
    236 		screen->r.max.x - 4,
    237 		screen->r.max.y - 4);
    238 	rstatus = Rect(
    239 		screen->r.min.x + 4,
    240 		screen->r.min.y + 4,
    241 		screen->r.max.x - 4,
    242 		screen->r.min.y + 4 + font->height);
    243 }
    244 
    245 void
    246 drawborder(void)
    247 {
    248 	draw(screen, screen->r, Iborder, 0, ZP);
    249 	draw(screen, rw, Iwbg, 0, ZP);
    250 }
    251 
    252 void
    253 drawstatus(void)
    254 {
    255 	draw(screen, rstatus, Iborder, 0, ZP);
    256 	string(screen, rstatus.min, Istatus, ZP, font, status);
    257 }
    258 
    259 void
    260 drawtext(void)
    261 {
    262 	char sbuf[1024], *tp, *sp;
    263 	long i;
    264 	int fflag;
    265 	Point dp;
    266 	fflag = 0;
    267 	dp = rw.min;
    268 	tp = text.data;
    269 	sp = sbuf;
    270 	draw(screen, rw, Iwbg, 0, ZP);
    271 	if (sp == nil) return;
    272 	while (tp < text.data + text.size) {
    273 		*sp = *tp;
    274 		sp++;
    275 		tp++;
    276 		if (*tp == '\n'){
    277 			*(sp-1) = 0;
    278 			fflag = 1;
    279 		}
    280 		if (dp.x + stringwidth(font, sbuf) > rw.max.x) {
    281 			*(sp-1) = 0;
    282 			tp--;
    283 			fflag = 1;
    284 		}
    285 		if (fflag) {
    286 			string(screen, dp, Iwfg, ZP, font, sbuf);
    287 			for (i = 0; i < 1024; i++) sbuf[i] = 0;
    288 			sp = sbuf;
    289 			dp.y += font->height;
    290 			if (dp.y + font->height > rw.max.y) break;
    291 			fflag = 0;
    292 		}
    293 	}
    294 }
    295 
    296 void
    297 drawmenu(void)
    298 {
    299 	char *s, *buf;
    300 	Image *Ifg;
    301 	Point dp;
    302 	ulong n, line;
    303 	dp = rw.min;
    304 	draw(screen, rw, Iwbg, 0, ZP);
    305 	if (text.data == nil) return;
    306 	for(line = scroll;; line++){
    307 		/*
    308 		 * n in stringn() counts characters, not bytes.
    309 		 * To prevent unicode from messsing with character count,
    310 		 * we copy line into new variable buf and draw it, instead of
    311 		 * drawing s directly.
    312 		 */
    313 		s = getlinep(&text, line);
    314 		if (s == nil) break;
    315 		n = strchr(s, '\t')  - (s + 1);
    316 		while (dp.x + stringnwidth(font, s+1, n) > rw.max.x) n--;
    317 		buf = mallocz(sizeof(char) * (n + 1), 1);
    318 		if (buf == nil) break;
    319 		memcpy(buf, s + 1, n);
    320 		if (*s == 'i') Ifg = Iwfg;
    321 		else Ifg = Iwfgl;
    322 		string(screen, dp, Ifg, ZP, font, buf);
    323 		free(buf);
    324 		dp.y += font->height;
    325 		if (dp.y + font->height > rw.max.y) break;
    326 	}
    327 }
    328 
    329 char*
    330 getlinep(Text *text, ulong ln)
    331 {
    332 	ulong lcount;
    333 	char *lp;
    334 	assert(text != nil);
    335 	if (text->data == nil) return nil;
    336 	lp = text->data;
    337 	for (lcount = 0; lcount < ln; lp++){
    338 		if (*lp == '\n') lcount++;
    339 		if (lp > text->data + text->size) return nil;
    340 	}
    341 	return lp;
    342 }
    343 
    344 URI
    345 extracturi(char *s)
    346 {
    347 	URI uri;
    348 	char *sp, *ep;
    349 	char *val[3];
    350 	int i;
    351 	char *sep = "\t\t\r";
    352 	uri.scheme = 0;
    353 	uri.user = 0;
    354 	uri.host = 0;
    355 	uri.port = 0;
    356 	uri.path = 0;
    357 	uri.query = 0;
    358 	uri.fragment = 0;
    359 	ep = strchr(s, sep[0]);
    360 	for (i = 0; i < 3; i++){
    361 		sp = ep + 1;
    362 		ep = strchr(sp, sep[i]);
    363 		val[i] = mallocz(ep - sp + 1, 1);
    364 		memcpy(val[i], sp, ep - sp);
    365 	}
    366 	uri.path = smprint("%c%s", s[0], val[0]);
    367 	uri.host = val[1];
    368 	uri.port = val[2];
    369 	free(val[0]);
    370 	return uri;
    371 }
    372 
    373 void
    374 handlelink(char *addr, char type, char *path)
    375 {
    376 	char buf[256];
    377 	History *hnew;
    378 	switch (type) {
    379 	case '0':
    380 		if (save(addr, path, tmpfile) == 0) proccreate(runhold, 0, 1024 * 8);
    381 		break;
    382 	case '1':
    383 		hnew = malloc(sizeof(History));
    384 		hnew->prev = hp;
    385 		hp = hnew;
    386 		strncpy(hp->addr, addr, 256);
    387 		strncpy(hp->path, path, 256);
    388 		loadtext(addr, path);		
    389 		break;
    390 	case '9':
    391 		buf[0] = 0;
    392 		if (enter("save as:", buf, 255, mc, kc, nil) > 0) {
    393 			snprint(status, 255, "downloading");
    394 			drawstatus();
    395 			flushimage(display, 1);
    396 			save(addr, path, buf);
    397 		};
    398 		break;
    399 	case 'I':
    400 		if (save(addr, path, tmpfile) == 0) proccreate(runpage, 0, 1024 * 8);
    401 		break;
    402 	default:
    403 		snprint(status, 255, "unknown type - %c", type);
    404 		drawstatus();
    405 		flushimage(display, 1);
    406 	}
    407 }
    408 
    409 void
    410 loadtext(char *addr, char *path)
    411 {
    412 	Loadctl *lctl;
    413 	lctl = malloc(sizeof(Loadctl));
    414 	lctl->c = lc;
    415 	lctl->addr = strdup(addr);
    416 	lctl->path = strdup(path);
    417 	lctl->query = nil;
    418 	lctl->sc = sc;
    419 	threadcreate(threadload, lctl, 64 * 1024);
    420 	return;
    421 }
    422 
    423 void
    424 runhold(void*)
    425 {
    426 	procexecl(nil, "/bin/window", "window", "-m", "hold", tmpfile, nil);
    427 }
    428 
    429 void
    430 runpage(void*)
    431 {
    432 	procexecl(nil, "/bin/window", "window", "-m", "page", tmpfile, nil);
    433 }
    434 
    435 int
    436 save(char *addr, char *path, char *fname)
    437 {
    438 	int dcfd, wfd;
    439 	long n;
    440 	char buf[1024];
    441 	
    442 	dcfd = dial(addr, 0, 0, 0);
    443 	if (dcfd > 0) {
    444 		write(dcfd, path, strlen(path));
    445 		write(dcfd, "\n", 1);
    446 	} else {
    447 		snprint(status, 255, "failed to dial %s", addr);
    448 		drawstatus();
    449 		flushimage(display, 1);
    450 		return -1;
    451 	}
    452 
    453 	wfd = create(fname, OWRITE, 0666);
    454 	if (wfd <= 0) {
    455 		snprint(status, 255, "failed to create file");
    456 		fprint(2, "%r\n");
    457 		drawstatus();
    458 		flushimage(display, 1);
    459 		return -1;
    460 	}
    461 	while ((n = read(dcfd, buf, 1024)) > 0) {
    462 		write(wfd, buf, n);
    463 	}
    464 	close(dcfd);
    465 	close(wfd);
    466 	snprint(status, 255, "saved");
    467 	drawstatus();
    468 	flushimage(display, 1);
    469 	return 0;
    470 }
    471 
    472 
    473 int
    474 runuri(char *s)
    475 {
    476 	URI *uri;
    477 	char *addr, *port, type, *path;
    478 	uri = chewuri(s);
    479 	if (uri == nil) {
    480 		snprint(status, 255, "invalid URI: %s", s);
    481 		drawstatus();
    482 		flushimage(display, 1);
    483 		return -1;
    484 	}
    485 	if (strcmp("gopher", uri->scheme) == 0){
    486 		port = (uri->port == nil) ? "gopher" : uri->port;
    487 		addr = netmkaddr(uri->host, "tcp", port);
    488 		type = '1';
    489 		path = "/";
    490 		if (strlen(uri->path) >=2) type = uri->path[1];
    491 		if (strlen(uri->path) > 2) path = uri->path + 2;
    492 		handlelink(addr, type, path);
    493 	} else {
    494 		snprint(status, 255, "Unknown scheme: %s", uri->scheme);
    495 		drawstatus();
    496 		flushimage(display, 1);
    497 		freeuri(uri);
    498 		free(uri);
    499 		return -1;
    500 	}		
    501 	freeuri(uri);
    502 	free(uri);
    503 	return 0;
    504 }