sacc

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

ui_ti.c (12807B)


      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 "common.h"
     11 #include "config.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 	vsnprintf(bufout, sizeof(bufout), fmt, ap);
     79 	va_end(ap);
     80 
     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 == -1) {
    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 	snprintf(bufout, sizeof(bufout), "%s %s",
    114 	         typedisplay(item->type), item->username);
    115 
    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_yankcur) ": yank page URI to external program.\n"
    142 		       S(_key_yanksel) ": yank item URI to external program.\n"
    143 		       S(_key_help) ": show this help.\n"
    144 		       "^D, " S(_key_quit) ": exit sacc.\n"
    145 	};
    146 
    147 	item.entry = entry;
    148 
    149 	return &item;
    150 }
    151 
    152 void
    153 uistatus(char *fmt, ...)
    154 {
    155 	va_list ap;
    156 	size_t n;
    157 
    158 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    159 
    160 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
    161 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    162 
    163 	va_start(ap, fmt);
    164 	n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
    165 	va_end(ap);
    166 
    167 	if (n < sizeof(bufout)-1) {
    168 		snprintf(bufout+n, sizeof(bufout)-n,
    169 		         " [Press a key to continue \xe2\x98\x83]");
    170 	}
    171 
    172 	mbsprint(bufout, columns);
    173 
    174 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    175 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    176 
    177 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    178 	fflush(stdout);
    179 
    180 	getchar();
    181 }
    182 
    183 static void
    184 displaystatus(Item *item)
    185 {
    186 	Dir *dir = item->dat;
    187 	char *fmt;
    188 	size_t nitems = dir ? dir->nitems : 0;
    189 	unsigned long long printoff = dir ? dir->printoff : 0;
    190 
    191 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    192 
    193 	putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
    194 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    195 
    196 	fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ?
    197 	      "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s";
    198 	snprintf(bufout, sizeof(bufout), fmt,
    199 	         (printoff + lines-1 >= nitems) ? 100 :
    200 	         (printoff + lines-1) * 100 / nitems,
    201 	         item->host, item->type, item->selector, item->port);
    202 
    203 	mbsprint(bufout, columns);
    204 
    205 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    206 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    207 
    208 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    209 	fflush(stdout);
    210 }
    211 
    212 static void
    213 displayuri(Item *item)
    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 
    223 	itemuri(item, bufout, sizeof(bufout));
    224 
    225 	mbsprint(bufout, columns);
    226 
    227 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    228 	putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    229 
    230 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    231 	fflush(stdout);
    232 }
    233 
    234 void
    235 uidisplay(Item *entry)
    236 {
    237 	Item *items;
    238 	Dir *dir;
    239 	size_t i, curln, lastln, nitems, printoff;
    240 
    241 	if (!entry ||
    242 	    !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
    243 		return;
    244 
    245 	curentry = entry;
    246 
    247 	putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    248 	displaystatus(entry);
    249 
    250 	if (!(dir = entry->dat))
    251 		return;
    252 
    253 	putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    254 
    255 	items = dir->items;
    256 	nitems = dir->nitems;
    257 	printoff = dir->printoff;
    258 	curln = dir->curline;
    259 	lastln = printoff + lines-1; /* one off for status bar */
    260 
    261 	for (i = printoff; i < nitems && i < lastln; ++i) {
    262 		if (i != printoff)
    263 			putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    264 		if (i == curln) {
    265 			putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    266 			putp(tparm(enter_standout_mode,
    267 			           0, 0, 0, 0, 0, 0, 0, 0, 0));
    268 		}
    269 		printitem(&items[i]);
    270 		putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    271 		if (i == curln)
    272 			putp(tparm(exit_standout_mode,
    273 			           0, 0, 0, 0, 0, 0, 0, 0, 0));
    274 	}
    275 
    276 	putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    277 	fflush(stdout);
    278 }
    279 
    280 static void
    281 movecurline(Item *item, int l)
    282 {
    283 	Dir *dir = item->dat;
    284 	size_t nitems;
    285 	ssize_t curline, offline;
    286 	int plines = lines-2;
    287 
    288 	if (dir == NULL)
    289 		return;
    290 
    291 	curline = dir->curline + l;
    292 	nitems = dir->nitems;
    293 	if (curline < 0 || curline >= nitems)
    294 		return;
    295 
    296 	printitem(&dir->items[dir->curline]);
    297 	dir->curline = curline;
    298 
    299 	if (l > 0) {
    300 		offline = dir->printoff + lines-1;
    301 		if (curline - dir->printoff >= plines / 2 && offline < nitems) {
    302 			putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    303 
    304 			putp(tparm(cursor_address, plines,
    305 			           0, 0, 0, 0, 0, 0, 0, 0));
    306 			putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    307 			printitem(&dir->items[offline]);
    308 
    309 			putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    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, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    316 
    317 			putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    318 			putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    319 			printitem(&dir->items[offline]);
    320 			putchar('\n');
    321 
    322 			putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    323 			dir->printoff += l;
    324 		}
    325 	}
    326 
    327 	putp(tparm(cursor_address, curline - dir->printoff,
    328 	           0, 0, 0, 0, 0, 0, 0, 0));
    329 	putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    330 	printitem(&dir->items[curline]);
    331 	putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
    332 	displaystatus(item);
    333 	fflush(stdout);
    334 }
    335 
    336 static void
    337 jumptoline(Item *entry, ssize_t line, int absolute)
    338 {
    339 	Dir *dir = entry->dat;
    340 	size_t lastitem;
    341 	int lastpagetop, plines = lines-2;
    342 
    343 	if (!dir)
    344 		return;
    345 	lastitem = dir->nitems-1;
    346 
    347 	if (line < 0)
    348 		line = 0;
    349 	if (line > lastitem)
    350 		line = lastitem;
    351 
    352 	if (dir->curline == line)
    353 		return;
    354 
    355 	if (lastitem <= plines) {              /* all items fit on one page */
    356 		dir->curline = line;
    357 	} else if (line == 0) {                /* jump to top */
    358 		if (absolute || dir->curline > plines || dir->printoff == 0)
    359 			dir->curline = 0;
    360 		dir->printoff = 0;
    361 	} else if (line + plines < lastitem) { /* jump before last page */
    362 		dir->curline = line;
    363 		dir->printoff = line;
    364 	} else {                               /* jump within the last page */
    365 		lastpagetop = lastitem - plines;
    366 		if (dir->printoff == lastpagetop || absolute)
    367 			dir->curline = line;
    368 		else if (dir->curline < lastpagetop)
    369 			dir->curline = lastpagetop;
    370 		dir->printoff = lastpagetop;
    371 	}
    372 
    373 	uidisplay(entry);
    374 	return;
    375 }
    376 
    377 static void
    378 searchinline(const char *searchstr, Item *entry, int pos)
    379 {
    380 	Dir *dir;
    381 	int i;
    382 
    383 	if (!searchstr || !(dir = entry->dat))
    384 		return;
    385 
    386 	if (pos > 0) {
    387 		for (i = dir->curline + 1; i < dir->nitems; ++i) {
    388 			if (strcasestr(dir->items[i].username, searchstr)) {
    389 				jumptoline(entry, i, 1);
    390 				break;
    391 			}
    392 		}
    393 	} else {
    394 		for (i = dir->curline - 1; i > -1; --i) {
    395 			if (strcasestr(dir->items[i].username, searchstr)) {
    396 				jumptoline(entry, i, 1);
    397 				break;
    398 			}
    399 		}
    400 	}
    401 }
    402 
    403 static ssize_t
    404 nearentry(Item *entry, int direction)
    405 {
    406 	Dir *dir = entry->dat;
    407 	size_t item, lastitem;
    408 
    409 	if (!dir)
    410 		return -1;
    411 	lastitem = dir->nitems;
    412 	item = dir->curline + direction;
    413 
    414 	for (; item < lastitem; item += direction) {
    415 		if (dir->items[item].type != 'i')
    416 			return item;
    417 	}
    418 
    419 	return dir->curline;
    420 }
    421 
    422 Item *
    423 uiselectitem(Item *entry)
    424 {
    425 	Dir *dir;
    426 	char *searchstr = NULL;
    427 	int plines = lines-2;
    428 
    429 	if (!entry || !(dir = entry->dat))
    430 		return NULL;
    431 
    432 	for (;;) {
    433 		switch (getchar()) {
    434 		case 0x1b: /* ESC */
    435 			switch (getchar()) {
    436 			case 0x1b:
    437 				goto quit;
    438 			case '[':
    439 				break;
    440 			default:
    441 				continue;
    442 			}
    443 			switch (getchar()) {
    444 			case '4':
    445 				if (getchar() != '~')
    446 					continue;
    447 				goto end;
    448 			case '5':
    449 				if (getchar() != '~')
    450 					continue;
    451 				goto pgup;
    452 			case '6':
    453 				if (getchar() != '~')
    454 					continue;
    455 				goto pgdown;
    456 			case 'A':
    457 				goto lnup;
    458 			case 'B':
    459 				goto lndown;
    460 			case 'C':
    461 				goto pgnext;
    462 			case 'D':
    463 				goto pgprev;
    464 			case 'H':
    465 				goto home;
    466 			case 0x1b:
    467 				goto quit;
    468 			}
    469 			continue;
    470 		case _key_pgprev:
    471 		pgprev:
    472 			return entry->entry;
    473 		case _key_pgnext:
    474 		case '\n':
    475 		pgnext:
    476 			if (dir)
    477 				return &dir->items[dir->curline];
    478 			continue;
    479 		case _key_lndown:
    480 		lndown:
    481 			movecurline(entry, 1);
    482 			continue;
    483 		case _key_entrydown:
    484 			jumptoline(entry, nearentry(entry, 1), 1);
    485 			continue;
    486 		case _key_pgdown:
    487 		pgdown:
    488 			jumptoline(entry, dir->printoff + plines, 0);
    489 			continue;
    490 		case _key_end:
    491 		end:
    492 			jumptoline(entry, dir->nitems, 0);
    493 			continue;
    494 		case _key_lnup:
    495 		lnup:
    496 			movecurline(entry, -1);
    497 			continue;
    498 		case _key_entryup:
    499 			jumptoline(entry, nearentry(entry, -1), 1);
    500 			continue;
    501 		case _key_pgup:
    502 		pgup:
    503 			jumptoline(entry, dir->printoff - plines, 0);
    504 			continue;
    505 		case _key_home:
    506 		home:
    507 			jumptoline(entry, 0, 0);
    508 			continue;
    509 		case _key_search:
    510 			free(searchstr);
    511 			if (!((searchstr = uiprompt("Search for: ")) &&
    512 			    searchstr[0])) {
    513 				clear(&searchstr);
    514 				continue;
    515 			}
    516 		case _key_searchnext:
    517 			searchinline(searchstr, entry, +1);
    518 			continue;
    519 		case _key_searchprev:
    520 			searchinline(searchstr, entry, -1);
    521 			continue;
    522 		case 0x04:
    523 		case _key_quit:
    524 		quit:
    525 			return NULL;
    526 		case _key_fetch:
    527 			if (entry->raw)
    528 				continue;
    529 			return entry;
    530 		case _key_cururi:
    531 			if (dir)
    532 				displayuri(entry);
    533 			continue;
    534 		case _key_seluri:
    535 			if (dir)
    536 				displayuri(&dir->items[dir->curline]);
    537 			continue;
    538 		case _key_yankcur:
    539 			if (dir)
    540 				yankitem(entry);
    541 			continue;
    542 		case _key_yanksel:
    543 			if (dir)
    544 				yankitem(&dir->items[dir->curline]);
    545 			continue;
    546 		case _key_help: /* FALLTHROUGH */
    547 			return help(entry);
    548 		default:
    549 			continue;
    550 		}
    551 	}
    552 }
    553 
    554 void
    555 uisigwinch(int signal)
    556 {
    557 	Dir *dir;
    558 
    559 	if (termset == OK)
    560 		del_curterm(cur_term);
    561 	termset = setupterm(NULL, 1, NULL);
    562 	putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
    563 
    564 	if (!curentry || !(dir = curentry->dat))
    565 		return;
    566 
    567 	if (dir->curline - dir->printoff > lines-2)
    568 		dir->printoff = dir->curline - (lines-2);
    569 
    570 	uidisplay(curentry);
    571 }