sacc

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

ui_ti.c (12976B)


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