ui_ti.c (13759B)
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 /* 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 if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout)) 79 bufout[sizeof(bufout)-1] = '\0'; 80 va_end(ap); 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 < 0) { 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 if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type), 114 item->username) >= sizeof(bufout)) 115 bufout[sizeof(bufout)-1] = '\0'; 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_help) ": show this help.\n" 142 "^D, " S(_key_quit) ": exit sacc.\n" 143 }; 144 145 item.entry = entry; 146 147 return &item; 148 } 149 150 void 151 uistatus(char *fmt, ...) 152 { 153 va_list ap; 154 size_t n; 155 156 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 157 158 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); 159 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 160 161 va_start(ap, fmt); 162 n = vsnprintf(bufout, sizeof(bufout), fmt, ap); 163 va_end(ap); 164 165 if (n < sizeof(bufout)-1) { 166 n += snprintf(bufout + n, sizeof(bufout) - n, 167 " [Press a key to continue \xe2\x98\x83]"); 168 } 169 if (n >= sizeof(bufout)) 170 bufout[sizeof(bufout)-1] = '\0'; 171 172 n = mbsprint(bufout, columns); 173 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 174 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 175 176 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 177 fflush(stdout); 178 179 getchar(); 180 } 181 182 static void 183 displaystatus(Item *item) 184 { 185 Dir *dir = item->dat; 186 char *fmt; 187 size_t n, nitems = dir ? dir->nitems : 0; 188 unsigned long long printoff = dir ? dir->printoff : 0; 189 190 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 191 192 putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); 193 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 194 fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ? 195 "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s"; 196 if (snprintf(bufout, sizeof(bufout), fmt, 197 (printoff + lines-1 >= nitems) ? 100 : 198 (printoff + lines-1) * 100 / nitems, 199 item->host, item->type, item->selector, item->port) 200 >= sizeof(bufout)) 201 bufout[sizeof(bufout)-1] = '\0'; 202 n = mbsprint(bufout, columns); 203 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 204 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 205 206 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 207 fflush(stdout); 208 } 209 210 static void 211 displayuri(Item *item) 212 { 213 size_t n; 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 switch (item->type) { 223 case '8': 224 n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s", 225 item->selector, item->host, item->port); 226 break; 227 case 'h': 228 n = snprintf(bufout, sizeof(bufout), "%s", 229 item->selector); 230 break; 231 case 'T': 232 n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s", 233 item->selector, item->host, item->port); 234 break; 235 default: 236 n = snprintf(bufout, sizeof(bufout), "gopher://%s", item->host); 237 238 if (n < sizeof(bufout) && strcmp(item->port, "70")) { 239 n += snprintf(bufout+n, sizeof(bufout)-n, ":%s", 240 item->port); 241 } 242 if (n < sizeof(bufout)) { 243 n += snprintf(bufout+n, sizeof(bufout)-n, "/%c%s", 244 item->type, item->selector); 245 } 246 if (n < sizeof(bufout) && item->type == '7' && item->tag) { 247 n += snprintf(bufout+n, sizeof(bufout)-n, "%%09%s", 248 item->tag + strlen(item->selector)); 249 } 250 break; 251 } 252 253 if (n >= sizeof(bufout)) 254 bufout[sizeof(bufout)-1] = '\0'; 255 256 n = mbsprint(bufout, columns); 257 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 258 putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 259 260 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 261 fflush(stdout); 262 } 263 264 void 265 uidisplay(Item *entry) 266 { 267 Item *items; 268 Dir *dir; 269 size_t i, curln, lastln, nitems, printoff; 270 271 if (!entry || 272 !(entry->type == '1' || entry->type == '+' || entry->type == '7')) 273 return; 274 275 curentry = entry; 276 277 putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 278 displaystatus(entry); 279 280 if (!(dir = entry->dat)) 281 return; 282 283 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 284 285 items = dir->items; 286 nitems = dir->nitems; 287 printoff = dir->printoff; 288 curln = dir->curline; 289 lastln = printoff + lines-1; /* one off for status bar */ 290 291 for (i = printoff; i < nitems && i < lastln; ++i) { 292 if (i != printoff) 293 putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 294 if (i == curln) { 295 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 296 putp(tparm(enter_standout_mode, 297 0, 0, 0, 0, 0, 0, 0, 0, 0)); 298 } 299 printitem(&items[i]); 300 putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 301 if (i == curln) 302 putp(tparm(exit_standout_mode, 303 0, 0, 0, 0, 0, 0, 0, 0, 0)); 304 } 305 306 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 307 fflush(stdout); 308 } 309 310 static void 311 movecurline(Item *item, int l) 312 { 313 Dir *dir = item->dat; 314 size_t nitems; 315 ssize_t curline, offline; 316 int plines = lines-2; 317 318 if (dir == NULL) 319 return; 320 321 curline = dir->curline + l; 322 nitems = dir->nitems; 323 if (curline < 0 || curline >= nitems) 324 return; 325 326 printitem(&dir->items[dir->curline]); 327 dir->curline = curline; 328 329 if (l > 0) { 330 offline = dir->printoff + lines-1; 331 if (curline - dir->printoff >= plines / 2 && offline < nitems) { 332 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 333 334 putp(tparm(cursor_address, plines, 335 0, 0, 0, 0, 0, 0, 0, 0)); 336 putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 337 printitem(&dir->items[offline]); 338 339 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 340 dir->printoff += l; 341 } 342 } else { 343 offline = dir->printoff + l; 344 if (curline - offline <= plines / 2 && offline >= 0) { 345 putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 346 347 putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 348 putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 349 printitem(&dir->items[offline]); 350 putchar('\n'); 351 352 putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 353 dir->printoff += l; 354 } 355 } 356 357 putp(tparm(cursor_address, curline - dir->printoff, 358 0, 0, 0, 0, 0, 0, 0, 0)); 359 putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 360 printitem(&dir->items[curline]); 361 putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 362 displaystatus(item); 363 fflush(stdout); 364 } 365 366 static void 367 jumptoline(Item *entry, ssize_t line, int absolute) 368 { 369 Dir *dir = entry->dat; 370 size_t lastitem; 371 int lastpagetop, plines = lines-2; 372 373 if (!dir) 374 return; 375 lastitem = dir->nitems-1; 376 377 if (line < 0) 378 line = 0; 379 if (line > lastitem) 380 line = lastitem; 381 382 if (dir->curline == line) 383 return; 384 385 if (lastitem <= plines) { /* all items fit on one page */ 386 dir->curline = line; 387 } else if (line == 0) { /* jump to top */ 388 if (absolute || dir->curline > plines || dir->printoff == 0) 389 dir->curline = 0; 390 dir->printoff = 0; 391 } else if (line + plines < lastitem) { /* jump before last page */ 392 dir->curline = line; 393 dir->printoff = line; 394 } else { /* jump within the last page */ 395 lastpagetop = lastitem - plines; 396 if (dir->printoff == lastpagetop || absolute) 397 dir->curline = line; 398 else if (dir->curline < lastpagetop) 399 dir->curline = lastpagetop; 400 dir->printoff = lastpagetop; 401 } 402 403 uidisplay(entry); 404 return; 405 } 406 407 void 408 searchinline(const char *searchstr, Item *entry, int pos) 409 { 410 Dir *dir; 411 int i; 412 413 if (!searchstr || !(dir = entry->dat)) 414 return; 415 416 if (pos > 0) { 417 for (i = dir->curline + 1; i < dir->nitems; ++i) { 418 if (strcasestr(dir->items[i].username, searchstr)) { 419 jumptoline(entry, i, 1); 420 break; 421 } 422 } 423 } else { 424 for (i = dir->curline - 1; i > -1; --i) { 425 if (strcasestr(dir->items[i].username, searchstr)) { 426 jumptoline(entry, i, 1); 427 break; 428 } 429 } 430 } 431 } 432 433 static ssize_t 434 nearentry(Item *entry, int direction) 435 { 436 Dir *dir = entry->dat; 437 size_t item, lastitem; 438 439 if (!dir) 440 return -1; 441 lastitem = dir->nitems; 442 item = dir->curline + direction; 443 444 for (; item < lastitem; item += direction) { 445 if (dir->items[item].type != 'i') 446 return item; 447 } 448 449 return dir->curline; 450 } 451 452 Item * 453 uiselectitem(Item *entry) 454 { 455 Dir *dir; 456 char *searchstr = NULL; 457 int plines = lines-2; 458 459 if (!entry || !(dir = entry->dat)) 460 return NULL; 461 462 for (;;) { 463 switch (getchar()) { 464 case 0x1b: /* ESC */ 465 switch (getchar()) { 466 case 0x1b: 467 goto quit; 468 case '[': 469 break; 470 default: 471 continue; 472 } 473 switch (getchar()) { 474 case '4': 475 if (getchar() != '~') 476 continue; 477 goto end; 478 case '5': 479 if (getchar() != '~') 480 continue; 481 goto pgup; 482 case '6': 483 if (getchar() != '~') 484 continue; 485 goto pgdown; 486 case 'A': 487 goto lnup; 488 case 'B': 489 goto lndown; 490 case 'C': 491 goto pgnext; 492 case 'D': 493 goto pgprev; 494 case 'H': 495 goto home; 496 case 0x1b: 497 goto quit; 498 } 499 continue; 500 case _key_pgprev: 501 pgprev: 502 return entry->entry; 503 case _key_pgnext: 504 case '\n': 505 pgnext: 506 if (dir) 507 return &dir->items[dir->curline]; 508 continue; 509 case _key_lndown: 510 lndown: 511 movecurline(entry, 1); 512 continue; 513 case _key_entrydown: 514 jumptoline(entry, nearentry(entry, 1), 1); 515 continue; 516 case _key_pgdown: 517 pgdown: 518 jumptoline(entry, dir->printoff + plines, 0); 519 continue; 520 case _key_end: 521 end: 522 jumptoline(entry, dir->nitems, 0); 523 continue; 524 case _key_lnup: 525 lnup: 526 movecurline(entry, -1); 527 continue; 528 case _key_entryup: 529 jumptoline(entry, nearentry(entry, -1), 1); 530 continue; 531 case _key_pgup: 532 pgup: 533 jumptoline(entry, dir->printoff - plines, 0); 534 continue; 535 case _key_home: 536 home: 537 jumptoline(entry, 0, 0); 538 continue; 539 case _key_search: 540 search: 541 free(searchstr); 542 if (!((searchstr = uiprompt("Search for: ")) && 543 searchstr[0])) { 544 clear(&searchstr); 545 continue; 546 } 547 case _key_searchnext: 548 searchinline(searchstr, entry, +1); 549 continue; 550 case _key_searchprev: 551 searchinline(searchstr, entry, -1); 552 continue; 553 case 0x04: 554 case _key_quit: 555 quit: 556 return NULL; 557 case _key_fetch: 558 fetch: 559 if (entry->raw) 560 continue; 561 return entry; 562 case _key_cururi: 563 if (dir) 564 displayuri(entry); 565 continue; 566 case _key_seluri: 567 if (dir) 568 displayuri(&dir->items[dir->curline]); 569 continue; 570 case _key_help: /* FALLTHROUGH */ 571 return help(entry); 572 default: 573 continue; 574 } 575 } 576 } 577 578 void 579 uisigwinch(int signal) 580 { 581 Dir *dir; 582 583 if (termset == OK) 584 del_curterm(cur_term); 585 termset = setupterm(NULL, 1, NULL); 586 putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0)); 587 588 if (!curentry || !(dir = curentry->dat)) 589 return; 590 591 if (dir->curline - dir->printoff > lines-2) 592 dir->curline = dir->printoff + lines-2; 593 594 uidisplay(curentry); 595 }