tui_ti.c - sacc - sacc(omys), simple console gopher client (mirror)
HTML git clone https://git.parazyd.org/sacc
DIR Log
DIR Files
DIR Refs
DIR LICENSE
---
tui_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 }