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 }