sacc

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

ui_ti.c (11696B)


      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(tiparm(clear_screen));
     44 	putp(tiparm(save_cursor));
     45 	putp(tiparm(change_scroll_region, 0, lines-2));
     46 	putp(tiparm(restore_cursor, 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(tiparm(change_scroll_region, 0, lines-1));
     59 	putp(tiparm(clear_screen));
     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(tiparm(save_cursor));
     72 
     73 	putp(tiparm(cursor_address, lines-1, 0));
     74 	putp(tiparm(clr_eol));
     75 	putp(tiparm(enter_standout_mode));
     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(tiparm(exit_standout_mode));
     84 	putp(tiparm(clr_eol));
     85 
     86 	putp(tiparm(cursor_address, lines-1, n));
     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(tiparm(restore_cursor));
     98 	fflush(stdout);
     99 
    100 	if (r == -1 || feof(stdin)) {
    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(tiparm(save_cursor));
    159 
    160 	putp(tiparm(cursor_address, lines-1, 0));
    161 	putp(tiparm(enter_standout_mode));
    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(tiparm(exit_standout_mode));
    175 	putp(tiparm(clr_eol));
    176 
    177 	putp(tiparm(restore_cursor));
    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(tiparm(save_cursor));
    192 
    193 	putp(tiparm(cursor_address, lines-1, 0));
    194 	putp(tiparm(enter_standout_mode));
    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(tiparm(exit_standout_mode));
    206 	putp(tiparm(clr_eol));
    207 
    208 	putp(tiparm(restore_cursor));
    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(tiparm(save_cursor));
    219 
    220 	putp(tiparm(cursor_address, lines-1, 0));
    221 	putp(tiparm(enter_standout_mode));
    222 
    223 	itemuri(item, bufout, sizeof(bufout));
    224 
    225 	mbsprint(bufout, columns);
    226 
    227 	putp(tiparm(exit_standout_mode));
    228 	putp(tiparm(clr_eol));
    229 
    230 	putp(tiparm(restore_cursor));
    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(tiparm(clear_screen));
    248 	displaystatus(entry);
    249 
    250 	if (!(dir = entry->dat))
    251 		return;
    252 
    253 	putp(tiparm(save_cursor));
    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(tiparm(cursor_down));
    264 		if (i == curln) {
    265 			putp(tiparm(save_cursor));
    266 			putp(tiparm(enter_standout_mode));
    267 		}
    268 		printitem(&items[i]);
    269 		putp(tiparm(column_address, 0));
    270 		if (i == curln)
    271 			putp(tiparm(exit_standout_mode));
    272 	}
    273 
    274 	putp(tiparm(restore_cursor));
    275 	fflush(stdout);
    276 }
    277 
    278 static void
    279 movecurline(Item *item, int l)
    280 {
    281 	Dir *dir = item->dat;
    282 	size_t nitems;
    283 	ssize_t curline, offline;
    284 	int plines = lines-2;
    285 
    286 	if (dir == NULL)
    287 		return;
    288 
    289 	curline = dir->curline + l;
    290 	nitems = dir->nitems;
    291 	if (curline < 0 || curline >= nitems)
    292 		return;
    293 
    294 	printitem(&dir->items[dir->curline]);
    295 	dir->curline = curline;
    296 
    297 	if (l > 0) {
    298 		offline = dir->printoff + lines-1;
    299 		if (curline - dir->printoff >= plines / 2 && offline < nitems) {
    300 			putp(tiparm(save_cursor));
    301 
    302 			putp(tiparm(cursor_address, plines, 0));
    303 			putp(tiparm(scroll_forward));
    304 			printitem(&dir->items[offline]);
    305 
    306 			putp(tiparm(restore_cursor));
    307 			dir->printoff += l;
    308 		}
    309 	} else {
    310 		offline = dir->printoff + l;
    311 		if (curline - offline <= plines / 2 && offline >= 0) {
    312 			putp(tiparm(save_cursor));
    313 
    314 			putp(tiparm(cursor_address, 0, 0));
    315 			putp(tiparm(scroll_reverse));
    316 			printitem(&dir->items[offline]);
    317 			putchar('\n');
    318 
    319 			putp(tiparm(restore_cursor));
    320 			dir->printoff += l;
    321 		}
    322 	}
    323 
    324 	putp(tiparm(cursor_address, curline - dir->printoff, 0));
    325 	putp(tiparm(enter_standout_mode));
    326 	printitem(&dir->items[curline]);
    327 	putp(tiparm(exit_standout_mode));
    328 	displaystatus(item);
    329 	fflush(stdout);
    330 }
    331 
    332 static void
    333 jumptoline(Item *entry, ssize_t line, int absolute)
    334 {
    335 	Dir *dir = entry->dat;
    336 	size_t lastitem;
    337 	int lastpagetop, plines = lines-2;
    338 
    339 	if (!dir)
    340 		return;
    341 	lastitem = dir->nitems-1;
    342 
    343 	if (line < 0)
    344 		line = 0;
    345 	if (line > lastitem)
    346 		line = lastitem;
    347 
    348 	if (dir->curline == line)
    349 		return;
    350 
    351 	if (lastitem <= plines) {              /* all items fit on one page */
    352 		dir->curline = line;
    353 	} else if (line == 0) {                /* jump to top */
    354 		if (absolute || dir->curline > plines || dir->printoff == 0)
    355 			dir->curline = 0;
    356 		dir->printoff = 0;
    357 	} else if (line + plines < lastitem) { /* jump before last page */
    358 		dir->curline = line;
    359 		dir->printoff = line;
    360 	} else {                               /* jump within the last page */
    361 		lastpagetop = lastitem - plines;
    362 		if (dir->printoff == lastpagetop || absolute)
    363 			dir->curline = line;
    364 		else if (dir->curline < lastpagetop)
    365 			dir->curline = lastpagetop;
    366 		dir->printoff = lastpagetop;
    367 	}
    368 
    369 	uidisplay(entry);
    370 	return;
    371 }
    372 
    373 static void
    374 searchinline(const char *searchstr, Item *entry, int pos)
    375 {
    376 	Dir *dir;
    377 	int i;
    378 
    379 	if (!searchstr || !(dir = entry->dat))
    380 		return;
    381 
    382 	if (pos > 0) {
    383 		for (i = dir->curline + 1; i < dir->nitems; ++i) {
    384 			if (strcasestr(dir->items[i].username, searchstr)) {
    385 				jumptoline(entry, i, 1);
    386 				break;
    387 			}
    388 		}
    389 	} else {
    390 		for (i = dir->curline - 1; i > -1; --i) {
    391 			if (strcasestr(dir->items[i].username, searchstr)) {
    392 				jumptoline(entry, i, 1);
    393 				break;
    394 			}
    395 		}
    396 	}
    397 }
    398 
    399 static ssize_t
    400 nearentry(Item *entry, int direction)
    401 {
    402 	Dir *dir = entry->dat;
    403 	size_t item, lastitem;
    404 
    405 	if (!dir)
    406 		return -1;
    407 	lastitem = dir->nitems;
    408 	item = dir->curline + direction;
    409 
    410 	for (; item < lastitem; item += direction) {
    411 		if (dir->items[item].type != 'i')
    412 			return item;
    413 	}
    414 
    415 	return dir->curline;
    416 }
    417 
    418 Item *
    419 uiselectitem(Item *entry)
    420 {
    421 	Dir *dir;
    422 	char *searchstr = NULL;
    423 	int c, plines = lines-2;
    424 
    425 	if (!entry || !(dir = entry->dat))
    426 		return NULL;
    427 
    428 	for (;;) {
    429 		switch (getchar()) {
    430 		case 0x1b: /* ESC */
    431 			switch (getchar()) {
    432 			case 0x1b:
    433 				goto quit;
    434 			case 'O': /* application key */
    435 			case '[': /* DEC */
    436 				break;
    437 			default:
    438 				continue;
    439 			}
    440 			c = getchar();
    441 			switch (c) {
    442 			case '1':
    443 			case '4':
    444 			case '5':
    445 			case '6':
    446 			case '7': /* urxvt */
    447 			case '8': /* urxvt */
    448 				if (getchar() != '~')
    449 					continue;
    450 				switch (c) {
    451 				case '1':
    452 					goto home;
    453 				case '4':
    454 					goto end;
    455 				case '5':
    456 					goto pgup;
    457 				case '6':
    458 					goto pgdown;
    459 				case '7':
    460 					goto home;
    461 				case '8':
    462 					goto end;
    463 				}
    464 			case 'A':
    465 				goto lnup;
    466 			case 'B':
    467 				goto lndown;
    468 			case 'C':
    469 				goto pgnext;
    470 			case 'D':
    471 				goto pgprev;
    472 			case 'H':
    473 				goto home;
    474 			case 0x1b:
    475 				goto quit;
    476 			}
    477 			continue;
    478 		case _key_pgprev:
    479 		pgprev:
    480 			return entry->entry;
    481 		case _key_pgnext:
    482 		case '\n':
    483 		pgnext:
    484 			if (dir)
    485 				return &dir->items[dir->curline];
    486 			continue;
    487 		case _key_lndown:
    488 		lndown:
    489 			movecurline(entry, 1);
    490 			continue;
    491 		case _key_entrydown:
    492 			jumptoline(entry, nearentry(entry, 1), 1);
    493 			continue;
    494 		case _key_pgdown:
    495 		pgdown:
    496 			jumptoline(entry, dir->printoff + plines, 0);
    497 			continue;
    498 		case _key_end:
    499 		end:
    500 			jumptoline(entry, dir->nitems, 0);
    501 			continue;
    502 		case _key_lnup:
    503 		lnup:
    504 			movecurline(entry, -1);
    505 			continue;
    506 		case _key_entryup:
    507 			jumptoline(entry, nearentry(entry, -1), 1);
    508 			continue;
    509 		case _key_pgup:
    510 		pgup:
    511 			jumptoline(entry, dir->printoff - plines, 0);
    512 			continue;
    513 		case _key_home:
    514 		home:
    515 			jumptoline(entry, 0, 0);
    516 			continue;
    517 		case _key_search:
    518 			free(searchstr);
    519 			if (!((searchstr = uiprompt("Search for: ")) &&
    520 			    searchstr[0])) {
    521 				clear(&searchstr);
    522 				continue;
    523 			}
    524 		case _key_searchnext:
    525 			searchinline(searchstr, entry, +1);
    526 			continue;
    527 		case _key_searchprev:
    528 			searchinline(searchstr, entry, -1);
    529 			continue;
    530 		case EOF:
    531 		case 0x04:
    532 		case _key_quit:
    533 		quit:
    534 			return NULL;
    535 		case _key_fetch:
    536 			if (entry->raw)
    537 				continue;
    538 			return entry;
    539 		case _key_cururi:
    540 			if (dir)
    541 				displayuri(entry);
    542 			continue;
    543 		case _key_seluri:
    544 			if (dir)
    545 				displayuri(&dir->items[dir->curline]);
    546 			continue;
    547 		case _key_yankcur:
    548 			if (dir)
    549 				yankitem(entry);
    550 			continue;
    551 		case _key_yanksel:
    552 			if (dir)
    553 				yankitem(&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 	if (termset == OK)
    569 		del_curterm(cur_term);
    570 	termset = setupterm(NULL, 1, NULL);
    571 	putp(tiparm(change_scroll_region, 0, lines-2));
    572 
    573 	if (!curentry || !(dir = curentry->dat))
    574 		return;
    575 
    576 	if (dir->curline - dir->printoff > lines-2)
    577 		dir->printoff = dir->curline - (lines-2);
    578 
    579 	uidisplay(curentry);
    580 }