tui_ti.c - sacc - [fork] customized build of sacc, the simple console gopher client
(HTM) git clone git://src.adamsgaard.dk/sacc
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
tui_ti.c (14102B)
---
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
22 uisetup(void)
23 {
24 tcgetattr(0, &tsave);
25 tsacc = tsave;
26 tsacc.c_lflag &= ~(ECHO|ICANON);
27 tsacc.c_cc[VMIN] = 1;
28 tsacc.c_cc[VTIME] = 0;
29 tcsetattr(0, TCSANOW, &tsacc);
30
31 setupterm(NULL, 1, NULL);
32 putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
33 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
34 putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
35 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
36 fflush(stdout);
37 }
38
39 void
40 uicleanup(void)
41 {
42 putp(tparm(change_scroll_region, 0, lines-1, 0, 0, 0, 0, 0, 0, 0));
43 putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
44 tcsetattr(0, TCSANOW, &tsave);
45 fflush(stdout);
46 }
47
48 char *
49 uiprompt(char *fmt, ...)
50 {
51 va_list ap;
52 char *input = NULL;
53 size_t n;
54 ssize_t r;
55
56 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
57
58 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
59 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
60 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
61
62 va_start(ap, fmt);
63 if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout))
64 bufout[sizeof(bufout)-1] = '\0';
65 va_end(ap);
66 n = mbsprint(bufout, columns);
67
68 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
69 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
70
71 putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0));
72
73 tsacc.c_lflag |= (ECHO|ICANON);
74 tcsetattr(0, TCSANOW, &tsacc);
75 fflush(stdout);
76
77 n = 0;
78 r = getline(&input, &n, stdin);
79
80 tsacc.c_lflag &= ~(ECHO|ICANON);
81 tcsetattr(0, TCSANOW, &tsacc);
82 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
83 fflush(stdout);
84
85 if (r < 0) {
86 clearerr(stdin);
87 clear(&input);
88 } else if (input[r - 1] == '\n') {
89 input[--r] = '\0';
90 }
91
92 return input;
93 }
94
95 static void
96 printitem(Item *item)
97 {
98 if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type),
99 item->username) >= sizeof(bufout))
100 bufout[sizeof(bufout)-1] = '\0';
101 mbsprint(bufout, columns);
102 putchar('\r');
103 }
104
105 static Item *
106 help(Item *entry)
107 {
108 static Item item = {
109 .type = '0',
110 .raw = "Commands:\n"
111 "Down, " S(_key_lndown) ": move one line down.\n"
112 S(_key_entrydown) ": move to next link.\n"
113 "Up, " S(_key_lnup) ": move one line up.\n"
114 S(_key_entryup) ": move to previous link.\n"
115 "PgDown, " S(_key_pgdown) ": move one page down.\n"
116 "PgUp, " S(_key_pgup) ": move one page up.\n"
117 "Home, " S(_key_home) ": move to top of the page.\n"
118 "End, " S(_key_end) ": move to end of the page.\n"
119 "Right, " S(_key_pgnext) ": view highlighted item.\n"
120 "Left, " S(_key_pgprev) ": view previous item.\n"
121 S(_key_search) ": search current page.\n"
122 S(_key_searchnext) ": search string forward.\n"
123 S(_key_searchprev) ": search string backward.\n"
124 S(_key_cururi) ": print page URI.\n"
125 S(_key_seluri) ": print item URI.\n"
126 S(_key_yankcur) ": yank page URI to X selection.\n"
127 S(_key_yanksel) ": yank item URI to X selection.\n"
128 S(_key_help) ": show this help.\n"
129 "^D, " S(_key_quit) ": exit sacc.\n"
130 };
131
132 item.entry = entry;
133
134 return &item;
135 }
136
137 void
138 uistatus(char *fmt, ...)
139 {
140 va_list ap;
141 size_t n;
142
143 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
144
145 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
146 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
147
148 va_start(ap, fmt);
149 n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
150 va_end(ap);
151
152 if (n < sizeof(bufout)-1) {
153 n += snprintf(bufout + n, sizeof(bufout) - n,
154 " [Press a key to continue \xe2\x98\x83]");
155 }
156 if (n >= sizeof(bufout))
157 bufout[sizeof(bufout)-1] = '\0';
158
159 n = mbsprint(bufout, columns);
160 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
161 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
162
163 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
164 fflush(stdout);
165
166 sleep(1);
167 }
168
169 static void
170 displaystatus(Item *item)
171 {
172 Dir *dir = item->dat;
173 char *fmt;
174 size_t n, nitems = dir ? dir->nitems : 0;
175 unsigned long long printoff = dir ? dir->printoff : 0;
176
177 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
178
179 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
180 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
181 fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ?
182 "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s";
183 if (snprintf(bufout, sizeof(bufout), fmt,
184 (printoff + lines-1 >= nitems) ? 100 :
185 (printoff + lines-1) * 100 / nitems,
186 item->host, item->type, item->selector, item->port)
187 >= sizeof(bufout))
188 bufout[sizeof(bufout)-1] = '\0';
189 n = mbsprint(bufout, columns);
190 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
191 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
192
193 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
194 fflush(stdout);
195 }
196
197 static void
198 itemuri(Item *item)
199 {
200 size_t n;
201
202 switch (item->type) {
203 case '8':
204 n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s",
205 item->selector, item->host, item->port);
206 break;
207 case 'h':
208 n = snprintf(bufout, sizeof(bufout), "%s",
209 item->selector+4);
210 break;
211 case 'T':
212 n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s",
213 item->selector, item->host, item->port);
214 break;
215 default:
216 n = snprintf(bufout, sizeof(bufout), "gopher://%s", item->host);
217
218 if (n < sizeof(bufout) && strcmp(item->port, "70")) {
219 n += snprintf(bufout+n, sizeof(bufout)-n, ":%s",
220 item->port);
221 }
222 if (n < sizeof(bufout)) {
223 n += snprintf(bufout+n, sizeof(bufout)-n, "/%c%s",
224 item->type, item->selector);
225 }
226 if (n < sizeof(bufout) && item->type == '7' && item->tag) {
227 n += snprintf(bufout+n, sizeof(bufout)-n, "%%09%s",
228 item->tag + strlen(item->selector));
229 }
230 break;
231 }
232
233 if (n >= sizeof(bufout))
234 bufout[sizeof(bufout)-1] = '\0';
235 }
236
237 static void
238 displayuri(Item *item)
239 {
240 size_t n;
241
242 if (item->type == 0 || item->type == 'i')
243 return;
244
245 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
246
247 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
248 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
249
250 itemuri(item);
251 n = mbsprint(bufout, columns);
252 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
253 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
254
255 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
256 fflush(stdout);
257 }
258
259 static void
260 yankuri(Item *item)
261 {
262 FILE *clip;
263 char xselcmd[128];
264 snprintf(xselcmd, sizeof(xselcmd), "%s 2>/dev/null", xselwrite);
265
266 itemuri(item);
267
268 if ((clip = popen(xselcmd, "w"))) {
269 if (!strncmp(bufout, "URL:", 4))
270 fprintf(clip, "%s", bufout + 4);
271 else
272 fprintf(clip, "%s", bufout);
273 pclose(clip);
274 }
275 }
276
277 void
278 uidisplay(Item *entry)
279 {
280 Item *items;
281 Dir *dir;
282 size_t i, curln, lastln, nitems, printoff;
283
284 if (!entry ||
285 !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
286 return;
287
288 curentry = entry;
289
290 putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
291 displaystatus(entry);
292
293 if (!(dir = entry->dat))
294 return;
295
296 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
297
298 items = dir->items;
299 nitems = dir->nitems;
300 printoff = dir->printoff;
301 curln = dir->curline;
302 lastln = printoff + lines-1; /* one off for status bar */
303
304 for (i = printoff; i < nitems && i < lastln; ++i) {
305 if (i != printoff)
306 putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
307 if (i == curln) {
308 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
309 putp(tparm(enter_standout_mode,
310 0, 0, 0, 0, 0, 0, 0, 0, 0));
311 }
312 printitem(&items[i]);
313 putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
314 if (i == curln)
315 putp(tparm(exit_standout_mode,
316 0, 0, 0, 0, 0, 0, 0, 0, 0));
317 }
318
319 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
320 fflush(stdout);
321 }
322
323 static void
324 movecurline(Item *item, int l)
325 {
326 Dir *dir = item->dat;
327 size_t nitems;
328 ssize_t curline, offline;
329 int plines = lines-2;
330
331 if (dir == NULL)
332 return;
333
334 curline = dir->curline + l;
335 nitems = dir->nitems;
336 if (curline < 0 || curline >= nitems)
337 return;
338
339 printitem(&dir->items[dir->curline]);
340 dir->curline = curline;
341
342 if (l > 0) {
343 offline = dir->printoff + lines-1;
344 if (curline - dir->printoff >= plines / 2 && offline < nitems) {
345 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
346
347 putp(tparm(cursor_address, plines,
348 0, 0, 0, 0, 0, 0, 0, 0));
349 putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
350 printitem(&dir->items[offline]);
351
352 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
353 dir->printoff += l;
354 }
355 } else {
356 offline = dir->printoff + l;
357 if (curline - offline <= plines / 2 && offline >= 0) {
358 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
359
360 putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
361 putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
362 printitem(&dir->items[offline]);
363 putchar('\n');
364
365 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
366 dir->printoff += l;
367 }
368 }
369
370 putp(tparm(cursor_address, curline - dir->printoff,
371 0, 0, 0, 0, 0, 0, 0, 0));
372 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
373 printitem(&dir->items[curline]);
374 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
375 displaystatus(item);
376 fflush(stdout);
377 }
378
379 static void
380 jumptoline(Item *entry, ssize_t line, int absolute)
381 {
382 Dir *dir = entry->dat;
383 size_t lastitem;
384 int lastpagetop, plines = lines-2;
385
386 if (!dir)
387 return;
388 lastitem = dir->nitems-1;
389
390 if (line < 0)
391 line = 0;
392 if (line > lastitem)
393 line = lastitem;
394
395 if (dir->curline == line)
396 return;
397
398 if (lastitem <= plines) { /* all items fit on one page */
399 dir->curline = line;
400 } else if (line == 0) { /* jump to top */
401 if (absolute || dir->curline > plines || dir->printoff == 0)
402 dir->curline = 0;
403 dir->printoff = 0;
404 } else if (line + plines < lastitem) { /* jump before last page */
405 dir->curline = line;
406 dir->printoff = line;
407 } else { /* jump within the last page */
408 lastpagetop = lastitem - plines;
409 if (dir->printoff == lastpagetop || absolute)
410 dir->curline = line;
411 else if (dir->curline < lastpagetop)
412 dir->curline = lastpagetop;
413 dir->printoff = lastpagetop;
414 }
415
416 uidisplay(entry);
417 return;
418 }
419
420 void
421 searchinline(const char *searchstr, Item *entry, int pos)
422 {
423 Dir *dir;
424 int i;
425
426 if (!searchstr || !(dir = entry->dat))
427 return;
428
429 if (pos > 0) {
430 for (i = dir->curline + 1; i < dir->nitems; ++i) {
431 if (strcasestr(dir->items[i].username, searchstr)) {
432 jumptoline(entry, i, 1);
433 break;
434 }
435 }
436 } else {
437 for (i = dir->curline - 1; i > -1; --i) {
438 if (strcasestr(dir->items[i].username, searchstr)) {
439 jumptoline(entry, i, 1);
440 break;
441 }
442 }
443 }
444 }
445
446 static ssize_t
447 nearentry(Item *entry, int direction)
448 {
449 Dir *dir = entry->dat;
450 size_t item, lastitem;
451
452 if (!dir)
453 return -1;
454 lastitem = dir->nitems;
455 item = dir->curline + direction;
456
457 for (; item < lastitem; item += direction) {
458 if (dir->items[item].type != 'i')
459 return item;
460 }
461
462 return dir->curline;
463 }
464
465 Item *
466 uiselectitem(Item *entry)
467 {
468 Dir *dir;
469 char *searchstr = NULL;
470 int plines = lines-2;
471
472 if (!entry || !(dir = entry->dat))
473 return NULL;
474
475 for (;;) {
476 switch (getchar()) {
477 case 0x1b: /* ESC */
478 switch (getchar()) {
479 case 0x1b:
480 goto quit;
481 case '[':
482 break;
483 default:
484 continue;
485 }
486 switch (getchar()) {
487 case '4':
488 if (getchar() != '~')
489 continue;
490 goto end;
491 case '5':
492 if (getchar() != '~')
493 continue;
494 goto pgup;
495 case '6':
496 if (getchar() != '~')
497 continue;
498 goto pgdown;
499 case 'A':
500 goto lnup;
501 case 'B':
502 goto lndown;
503 case 'C':
504 goto pgnext;
505 case 'D':
506 goto pgprev;
507 case 'H':
508 goto home;
509 case 0x1b:
510 goto quit;
511 }
512 continue;
513 case _key_pgprev:
514 pgprev:
515 return entry->entry;
516 case _key_pgnext:
517 case '\n':
518 pgnext:
519 if (dir)
520 return &dir->items[dir->curline];
521 continue;
522 case _key_lndown:
523 lndown:
524 movecurline(entry, 1);
525 continue;
526 case _key_entrydown:
527 jumptoline(entry, nearentry(entry, 1), 1);
528 continue;
529 case _key_pgdown:
530 pgdown:
531 jumptoline(entry, dir->printoff + plines, 0);
532 continue;
533 case _key_end:
534 end:
535 jumptoline(entry, dir->nitems, 0);
536 continue;
537 case _key_lnup:
538 lnup:
539 movecurline(entry, -1);
540 continue;
541 case _key_entryup:
542 jumptoline(entry, nearentry(entry, -1), 1);
543 continue;
544 case _key_pgup:
545 pgup:
546 jumptoline(entry, dir->printoff - plines, 0);
547 continue;
548 case _key_home:
549 home:
550 jumptoline(entry, 0, 0);
551 continue;
552 case _key_search:
553 search:
554 free(searchstr);
555 if (!((searchstr = uiprompt("Search for: ")) &&
556 searchstr[0])) {
557 clear(&searchstr);
558 continue;
559 }
560 case _key_searchnext:
561 searchinline(searchstr, entry, +1);
562 continue;
563 case _key_searchprev:
564 searchinline(searchstr, entry, -1);
565 continue;
566 case 0x04:
567 case _key_quit:
568 quit:
569 return NULL;
570 case _key_fetch:
571 fetch:
572 if (entry->raw)
573 continue;
574 return entry;
575 case _key_cururi:
576 if (dir)
577 displayuri(entry);
578 continue;
579 case _key_seluri:
580 if (dir)
581 displayuri(&dir->items[dir->curline]);
582 continue;
583 case _key_yankcur:
584 if (dir)
585 yankuri(entry);
586 continue;
587 case _key_yanksel:
588 if (dir)
589 yankuri(&dir->items[dir->curline]);
590 continue;
591 case _key_help: /* FALLTHROUGH */
592 return help(entry);
593 default:
594 continue;
595 }
596 }
597 }
598
599 void
600 uisigwinch(int signal)
601 {
602 Dir *dir;
603
604 setupterm(NULL, 1, NULL);
605 putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
606
607 if (!curentry || !(dir = curentry->dat))
608 return;
609
610 if (dir->curline - dir->printoff > lines-2)
611 dir->curline = dir->printoff + lines-2;
612
613 uidisplay(curentry);
614 }