Recently Written · git

sacc

Mirror of git://bitreich.org/sacc, the sacc gopher client from bitreich.org.

git clone https://github.com/equwal/sacc

Log | Files | Refs


ui_ti.c (11696 bytes)

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 }