ui_ti.c - sacc - sacc - sacc(omys), simple console gopher client (config) | |
git clone git://git.codemadness.org/sacc | |
Log | |
Files | |
Refs | |
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.\… | |
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(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 ==… | |
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 < n… | |
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 p… | |
352 dir->curline = line; | |
353 } else if (line == 0) { /* jump to top */ | |
354 if (absolute || dir->curline > plines || dir->printoff =… | |
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 p… | |
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 } |