sacc

sacc(omys), simple console gopher client (mirror)
git clone https://git.parazyd.org/sacc
Log | Files | Refs | LICENSE

ui_ti.c (13759B)


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