sacc

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

ui_ti.c (11340B)


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