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