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