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 }