sacc

sacc (saccomys): simple gopher client.
Log | Files | Refs | LICENSE

ui_ti.c (13442B)


      1 #include <stdarg.h>
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 #include <term.h>
      6 #include <termios.h>
      7 #include <unistd.h>
      8 #include <sys/types.h>
      9 
     10 #include "config.h"
     11 #include "common.h"
     12 
     13 #define C(c) #c
     14 #define S(c) C(c)
     15 
     16 static char bufout[256];
     17 static struct termios tsave;
     18 static struct termios tsacc;
     19 static Item *curentry;
     20 
     21 void
     22 uisetup(void)
     23 {
     24 	tcgetattr(0, &tsave);
     25 	tsacc = tsave;
     26 	tsacc.c_lflag &= ~(ECHO|ICANON);
     27 	tsacc.c_cc[VMIN] = 1;
     28 	tsacc.c_cc[VTIME] = 0;
     29 	tcsetattr(0, TCSANOW, &tsacc);
     30 
     31 	setupterm(NULL, 1, NULL);
     32 	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     33 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     34 	putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
     35 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     36 	fflush(stdout);
     37 }
     38 
     39 void
     40 uicleanup(void)
     41 {
     42 	putp(tparm(change_scroll_region, 0, lines-1, 0, 0, 0, 0, 0, 0, 0));
     43 	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     44 	tcsetattr(0, TCSANOW, &tsave);
     45 	fflush(stdout);
     46 }
     47 
     48 char *
     49 uiprompt(char *fmt, ...)
     50 {
     51 	va_list ap;
     52 	char *input = NULL;
     53 	size_t n;
     54 	ssize_t r;
     55 
     56 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     57 
     58 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
     59 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     60 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     61 
     62 	va_start(ap, fmt);
     63 	if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout))
     64 		bufout[sizeof(bufout)-1] = '\0';
     65 	va_end(ap);
     66 	n = mbsprint(bufout, columns);
     67 
     68 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     69 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     70 
     71 	putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0));
     72 
     73 	tsacc.c_lflag |= (ECHO|ICANON);
     74 	tcsetattr(0, TCSANOW, &tsacc);
     75 	fflush(stdout);
     76 
     77 	n = 0;
     78 	r = getline(&input, &n, stdin);
     79 
     80 	tsacc.c_lflag &= ~(ECHO|ICANON);
     81 	tcsetattr(0, TCSANOW, &tsacc);
     82 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
     83 	fflush(stdout);
     84 
     85 	if (r < 0) {
     86 		clearerr(stdin);
     87 		clear(&input);
     88 	} else if (input[r - 1] == '\n') {
     89 		input[--r] = '\0';
     90 	}
     91 
     92 	return input;
     93 }
     94 
     95 static void
     96 printitem(Item *item)
     97 {
     98 	if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type),
     99 	    item->username) >= sizeof(bufout))
    100 		bufout[sizeof(bufout)-1] = '\0';
    101 	mbsprint(bufout, columns);
    102 	putchar('\r');
    103 }
    104 
    105 static Item *
    106 help(Item *entry)
    107 {
    108 	static Item item = {
    109 		.type = '0',
    110 		.raw = "Commands:\n"
    111 		       "Down, " S(_key_lndown) ": move one line down.\n"
    112 			S(_key_entrydown) ": move to next link.\n"
    113 		       "Up, " S(_key_lnup) ": move one line up.\n"
    114 			S(_key_entryup) ": move to previous link.\n"
    115 		       "PgDown, " S(_key_pgdown) ": move one page down.\n"
    116 		       "PgUp, " S(_key_pgup) ": move one page up.\n"
    117 		       "Home, " S(_key_home) ": move to top of the page.\n"
    118 		       "End, " S(_key_end) ": move to end of the page.\n"
    119 		       "Right, " S(_key_pgnext) ": view highlighted item.\n"
    120 		       "Left, " S(_key_pgprev) ": view previous item.\n"
    121 		       S(_key_search) ": search current page.\n"
    122 		       S(_key_searchnext) ": search string forward.\n"
    123 		       S(_key_searchprev) ": search string backward.\n"
    124 		       S(_key_cururi) ": print page URI.\n"
    125 		       S(_key_seluri) ": print item URI.\n"
    126 		       S(_key_help) ": show this help.\n"
    127 		       "^D, " S(_key_quit) ": exit sacc.\n"
    128 	};
    129 
    130 	item.entry = entry;
    131 
    132 	return &item;
    133 }
    134 
    135 void
    136 uistatus(char *fmt, ...)
    137 {
    138 	va_list ap;
    139 	size_t n;
    140 
    141 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    142 
    143 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
    144 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    145 
    146 	va_start(ap, fmt);
    147 	n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
    148 	va_end(ap);
    149 
    150 	if (n < sizeof(bufout)-1) {
    151 		n += snprintf(bufout + n, sizeof(bufout) - n,
    152 		              " [Press a key to continue \xe2\x98\x83]");
    153 	}
    154 	if (n >= sizeof(bufout))
    155 		bufout[sizeof(bufout)-1] = '\0';
    156 
    157 	n = mbsprint(bufout, columns);
    158 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    159 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    160 
    161 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    162 	fflush(stdout);
    163 
    164 	getchar();
    165 }
    166 
    167 static void
    168 displaystatus(Item *item)
    169 {
    170 	Dir *dir = item->dat;
    171 	char *fmt;
    172 	size_t n, nitems = dir ? dir->nitems : 0;
    173 	unsigned long long printoff = dir ? dir->printoff : 0;
    174 
    175 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    176 
    177 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
    178 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    179 	fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ?
    180 	      "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s";
    181 	if (snprintf(bufout, sizeof(bufout), fmt,
    182 	             (printoff + lines-1 >= nitems) ? 100 :
    183 	             (printoff + lines-1) * 100 / nitems,
    184 	             item->host, item->type, item->selector, item->port)
    185 	    >= sizeof(bufout))
    186 		bufout[sizeof(bufout)-1] = '\0';
    187 	n = mbsprint(bufout, columns);
    188 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    189 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    190 
    191 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    192 	fflush(stdout);
    193 }
    194 
    195 static void
    196 displayuri(Item *item)
    197 {
    198 	size_t n;
    199 
    200 	if (item->type == 0 || item->type == 'i')
    201 		return;
    202 
    203 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    204 
    205 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
    206 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    207 	switch (item->type) {
    208 	case '8':
    209 		n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s",
    210 		             item->selector, item->host, item->port);
    211 		break;
    212 	case 'h':
    213 		n = snprintf(bufout, sizeof(bufout), "%s",
    214 		             item->selector);
    215 		break;
    216 	case 'T':
    217 		n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s",
    218 		             item->selector, item->host, item->port);
    219 		break;
    220 	default:
    221 		n = snprintf(bufout, sizeof(bufout), "gopher://%s", item->host);
    222 
    223 		if (n < sizeof(bufout) && strcmp(item->port, "70")) {
    224 			n += snprintf(bufout+n, sizeof(bufout)-n, ":%s",
    225 			              item->port);
    226 		}
    227 		if (n < sizeof(bufout)) {
    228 			n += snprintf(bufout+n, sizeof(bufout)-n, "/%c%s",
    229 			              item->type, item->selector);
    230 		}
    231 		if (n < sizeof(bufout) && item->type == '7' && item->tag) {
    232 			n += snprintf(bufout+n, sizeof(bufout)-n, "%%09%s",
    233 			              item->tag + strlen(item->selector));
    234 		}
    235 		break;
    236 	}
    237 
    238 	if (n >= sizeof(bufout))
    239 		bufout[sizeof(bufout)-1] = '\0';
    240 
    241 	n = mbsprint(bufout, columns);
    242 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    243 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    244 
    245 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    246 	fflush(stdout);
    247 }
    248 
    249 void
    250 uidisplay(Item *entry)
    251 {
    252 	Item *items;
    253 	Dir *dir;
    254 	size_t i, curln, lastln, nitems, printoff;
    255 
    256 	if (!entry ||
    257 	    !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
    258 		return;
    259 
    260 	curentry = entry;
    261 
    262 	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    263 	displaystatus(entry);
    264 
    265 	if (!(dir = entry->dat))
    266 		return;
    267 
    268 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    269 
    270 	items = dir->items;
    271 	nitems = dir->nitems;
    272 	printoff = dir->printoff;
    273 	curln = dir->curline;
    274 	lastln = printoff + lines-1; /* one off for status bar */
    275 
    276 	for (i = printoff; i < nitems && i < lastln; ++i) {
    277 		if (i != printoff)
    278 			putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    279 		if (i == curln) {
    280 			putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    281 			putp(tparm(enter_standout_mode,
    282 			           0, 0, 0, 0, 0, 0, 0, 0, 0));
    283 		}
    284 		printitem(&items[i]);
    285 		putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    286 		if (i == curln)
    287 			putp(tparm(exit_standout_mode,
    288 			           0, 0, 0, 0, 0, 0, 0, 0, 0));
    289 	}
    290 
    291 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    292 	fflush(stdout);
    293 }
    294 
    295 static void
    296 movecurline(Item *item, int l)
    297 {
    298 	Dir *dir = item->dat;
    299 	size_t nitems;
    300 	ssize_t curline, offline;
    301 	int plines = lines-2;
    302 
    303 	if (dir == NULL)
    304 		return;
    305 
    306 	curline = dir->curline + l;
    307 	nitems = dir->nitems;
    308 	if (curline < 0 || curline >= nitems)
    309 		return;
    310 
    311 	printitem(&dir->items[dir->curline]);
    312 	dir->curline = curline;
    313 
    314 	if (l > 0) {
    315 		offline = dir->printoff + lines-1;
    316 		if (curline - dir->printoff >= plines / 2 && offline < nitems) {
    317 			putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    318 
    319 			putp(tparm(cursor_address, plines,
    320 			           0, 0, 0, 0, 0, 0, 0, 0));
    321 			putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    322 			printitem(&dir->items[offline]);
    323 
    324 			putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    325 			dir->printoff += l;
    326 		}
    327 	} else {
    328 		offline = dir->printoff + l;
    329 		if (curline - offline <= plines / 2 && offline >= 0) {
    330 			putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    331 
    332 			putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    333 			putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    334 			printitem(&dir->items[offline]);
    335 			putchar('\n');
    336 
    337 			putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    338 			dir->printoff += l;
    339 		}
    340 	}
    341 
    342 	putp(tparm(cursor_address, curline - dir->printoff,
    343 	           0, 0, 0, 0, 0, 0, 0, 0));
    344 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    345 	printitem(&dir->items[curline]);
    346 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    347 	displaystatus(item);
    348 	fflush(stdout);
    349 }
    350 
    351 static void
    352 jumptoline(Item *entry, ssize_t line, int absolute)
    353 {
    354 	Dir *dir = entry->dat;
    355 	size_t lastitem;
    356 	int lastpagetop, plines = lines-2;
    357 
    358 	if (!dir)
    359 		return;
    360 	lastitem = dir->nitems-1;
    361 
    362 	if (line < 0)
    363 		line = 0;
    364 	if (line > lastitem)
    365 		line = lastitem;
    366 
    367 	if (dir->curline == line)
    368 		return;
    369 
    370 	if (lastitem <= plines) {              /* all items fit on one page */
    371 		dir->curline = line;
    372 	} else if (line == 0) {                /* jump to top */
    373 		if (absolute || dir->curline > plines || dir->printoff == 0)
    374 			dir->curline = 0;
    375 		dir->printoff = 0;
    376 	} else if (line + plines < lastitem) { /* jump before last page */
    377 		dir->curline = line;
    378 		dir->printoff = line;
    379 	} else {                               /* jump within the last page */
    380 		lastpagetop = lastitem - plines;
    381 		if (dir->printoff == lastpagetop || absolute)
    382 			dir->curline = line;
    383 		else if (dir->curline < lastpagetop)
    384 			dir->curline = lastpagetop;
    385 		dir->printoff = lastpagetop;
    386 	}
    387 
    388 	uidisplay(entry);
    389 	return;
    390 }
    391 
    392 void
    393 searchinline(const char *searchstr, Item *entry, int pos)
    394 {
    395 	Dir *dir;
    396 	int i;
    397 
    398 	if (!searchstr || !(dir = entry->dat))
    399 		return;
    400 
    401 	if (pos > 0) {
    402 		for (i = dir->curline + 1; i < dir->nitems; ++i) {
    403 			if (strcasestr(dir->items[i].username, searchstr)) {
    404 				jumptoline(entry, i, 1);
    405 				break;
    406 			}
    407 		}
    408 	} else {
    409 		for (i = dir->curline - 1; i > -1; --i) {
    410 			if (strcasestr(dir->items[i].username, searchstr)) {
    411 				jumptoline(entry, i, 1);
    412 				break;
    413 			}
    414 		}
    415 	}
    416 }
    417 
    418 static ssize_t
    419 nearentry(Item *entry, int direction)
    420 {
    421 	Dir *dir = entry->dat;
    422 	size_t item, lastitem;
    423 
    424 	if (!dir)
    425 		return -1;
    426 	lastitem = dir->nitems;
    427 	item = dir->curline + direction;
    428 
    429 	for (; item < lastitem; item += direction) {
    430 		if (dir->items[item].type != 'i')
    431 			return item;
    432 	}
    433 
    434 	return dir->curline;
    435 }
    436 
    437 Item *
    438 uiselectitem(Item *entry)
    439 {
    440 	Dir *dir;
    441 	char *searchstr = NULL;
    442 	int plines = lines-2;
    443 
    444 	if (!entry || !(dir = entry->dat))
    445 		return NULL;
    446 
    447 	for (;;) {
    448 		switch (getchar()) {
    449 		case 0x1b: /* ESC */
    450 			switch (getchar()) {
    451 			case 0x1b:
    452 				goto quit;
    453 			case '[':
    454 				break;
    455 			default:
    456 				continue;
    457 			}
    458 			switch (getchar()) {
    459 			case '4':
    460 				if (getchar() != '~')
    461 					continue;
    462 				goto end;
    463 			case '5':
    464 				if (getchar() != '~')
    465 					continue;
    466 				goto pgup;
    467 			case '6':
    468 				if (getchar() != '~')
    469 					continue;
    470 				goto pgdown;
    471 			case 'A':
    472 				goto lnup;
    473 			case 'B':
    474 				goto lndown;
    475 			case 'C':
    476 				goto pgnext;
    477 			case 'D':
    478 				goto pgprev;
    479 			case 'H':
    480 				goto home;
    481 			case 0x1b:
    482 				goto quit;
    483 			}
    484 			continue;
    485 		case _key_pgprev:
    486 		pgprev:
    487 			return entry->entry;
    488 		case _key_pgnext:
    489 		case '\n':
    490 		pgnext:
    491 			if (dir)
    492 				return &dir->items[dir->curline];
    493 			continue;
    494 		case _key_lndown:
    495 		lndown:
    496 			movecurline(entry, 1);
    497 			continue;
    498 		case _key_entrydown:
    499 			jumptoline(entry, nearentry(entry, 1), 1);
    500 			continue;
    501 		case _key_pgdown:
    502 		pgdown:
    503 			jumptoline(entry, dir->printoff + plines, 0);
    504 			continue;
    505 		case _key_end:
    506 		end:
    507 			jumptoline(entry, dir->nitems, 0);
    508 			continue;
    509 		case _key_lnup:
    510 		lnup:
    511 			movecurline(entry, -1);
    512 			continue;
    513 		case _key_entryup:
    514 			jumptoline(entry, nearentry(entry, -1), 1);
    515 			continue;
    516 		case _key_pgup:
    517 		pgup:
    518 			jumptoline(entry, dir->printoff - plines, 0);
    519 			continue;
    520 		case _key_home:
    521 		home:
    522 			jumptoline(entry, 0, 0);
    523 			continue;
    524 		case _key_search:
    525 		search:
    526 			free(searchstr);
    527 			if (!((searchstr = uiprompt("Search for: ")) &&
    528 			    searchstr[0])) {
    529 				clear(&searchstr);
    530 				continue;
    531 			}
    532 		case _key_searchnext:
    533 			searchinline(searchstr, entry, +1);
    534 			continue;
    535 		case _key_searchprev:
    536 			searchinline(searchstr, entry, -1);
    537 			continue;
    538 		case 0x04:
    539 		case _key_quit:
    540 		quit:
    541 			return NULL;
    542 		case _key_fetch:
    543 		fetch:
    544 			if (entry->raw)
    545 				continue;
    546 			return entry;
    547 		case _key_cururi:
    548 			if (dir)
    549 				displayuri(entry);
    550 			continue;
    551 		case _key_seluri:
    552 			if (dir)
    553 				displayuri(&dir->items[dir->curline]);
    554 			continue;
    555 		case _key_help: /* FALLTHROUGH */
    556 			return help(entry);
    557 		default:
    558 			continue;
    559 		}
    560 	}
    561 }
    562 
    563 void
    564 uisigwinch(int signal)
    565 {
    566 	Dir *dir;
    567 
    568 	setupterm(NULL, 1, NULL);
    569 	putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
    570 
    571 	if (!curentry || !(dir = curentry->dat))
    572 		return;
    573 
    574 	if (dir->curline - dir->printoff > lines-2)
    575 		dir->curline = dir->printoff + lines-2;
    576 
    577 	uidisplay(curentry);
    578 }