ui_ti.c - sacc - sacc(omys), simple console gopher client | |
git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65… | |
Log | |
Files | |
Refs | |
Tags | |
LICENSE | |
--- | |
ui_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.\… | |
131 "PgUp, " S(_key_pgup) ": move one page up.\n" | |
132 "Home, " S(_key_home) ": move to top of the page.… | |
133 "End, " S(_key_end) ": move to end of the page.\n" | |
134 "Right, " S(_key_pgnext) ": view highlighted item… | |
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 prog… | |
142 S(_key_yanksel) ": yank item URI to external prog… | |
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 ==… | |
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, … | |
264 if (i == curln) { | |
265 putp(tparm(save_cursor, 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 < n… | |
302 putp(tparm(save_cursor, 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, … | |
307 printitem(&dir->items[offline]); | |
308 | |
309 putp(tparm(restore_cursor, 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, … | |
316 | |
317 putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, … | |
318 putp(tparm(scroll_reverse, 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, … | |
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 p… | |
356 dir->curline = line; | |
357 } else if (line == 0) { /* jump to top */ | |
358 if (absolute || dir->curline > plines || dir->printoff =… | |
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 p… | |
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 } |