ui_ti.c - sacc - sacc(omys), simple console gopher client
HTML git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR LICENSE
---
ui_ti.c (11696B)
---
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 }