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