ui_ti.c (12807B)
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.\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(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 == '7')) 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, 0)); 264 if (i == curln) { 265 putp(tparm(save_cursor, 0, 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 < nitems) { 302 putp(tparm(save_cursor, 0, 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, 0, 0)); 307 printitem(&dir->items[offline]); 308 309 putp(tparm(restore_cursor, 0, 0, 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, 0)); 316 317 putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 318 putp(tparm(scroll_reverse, 0, 0, 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, 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 page */ 356 dir->curline = line; 357 } else if (line == 0) { /* jump to top */ 358 if (absolute || dir->curline > plines || dir->printoff == 0) 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 page */ 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 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 '[': 439 break; 440 default: 441 continue; 442 } 443 switch (getchar()) { 444 case '4': 445 if (getchar() != '~') 446 continue; 447 goto end; 448 case '5': 449 if (getchar() != '~') 450 continue; 451 goto pgup; 452 case '6': 453 if (getchar() != '~') 454 continue; 455 goto pgdown; 456 case 'A': 457 goto lnup; 458 case 'B': 459 goto lndown; 460 case 'C': 461 goto pgnext; 462 case 'D': 463 goto pgprev; 464 case 'H': 465 goto home; 466 case 0x1b: 467 goto quit; 468 } 469 continue; 470 case _key_pgprev: 471 pgprev: 472 return entry->entry; 473 case _key_pgnext: 474 case '\n': 475 pgnext: 476 if (dir) 477 return &dir->items[dir->curline]; 478 continue; 479 case _key_lndown: 480 lndown: 481 movecurline(entry, 1); 482 continue; 483 case _key_entrydown: 484 jumptoline(entry, nearentry(entry, 1), 1); 485 continue; 486 case _key_pgdown: 487 pgdown: 488 jumptoline(entry, dir->printoff + plines, 0); 489 continue; 490 case _key_end: 491 end: 492 jumptoline(entry, dir->nitems, 0); 493 continue; 494 case _key_lnup: 495 lnup: 496 movecurline(entry, -1); 497 continue; 498 case _key_entryup: 499 jumptoline(entry, nearentry(entry, -1), 1); 500 continue; 501 case _key_pgup: 502 pgup: 503 jumptoline(entry, dir->printoff - plines, 0); 504 continue; 505 case _key_home: 506 home: 507 jumptoline(entry, 0, 0); 508 continue; 509 case _key_search: 510 free(searchstr); 511 if (!((searchstr = uiprompt("Search for: ")) && 512 searchstr[0])) { 513 clear(&searchstr); 514 continue; 515 } 516 case _key_searchnext: 517 searchinline(searchstr, entry, +1); 518 continue; 519 case _key_searchprev: 520 searchinline(searchstr, entry, -1); 521 continue; 522 case 0x04: 523 case _key_quit: 524 quit: 525 return NULL; 526 case _key_fetch: 527 if (entry->raw) 528 continue; 529 return entry; 530 case _key_cururi: 531 if (dir) 532 displayuri(entry); 533 continue; 534 case _key_seluri: 535 if (dir) 536 displayuri(&dir->items[dir->curline]); 537 continue; 538 case _key_yankcur: 539 if (dir) 540 yankitem(entry); 541 continue; 542 case _key_yanksel: 543 if (dir) 544 yankitem(&dir->items[dir->curline]); 545 continue; 546 case _key_help: /* FALLTHROUGH */ 547 return help(entry); 548 default: 549 continue; 550 } 551 } 552 } 553 554 void 555 uisigwinch(int signal) 556 { 557 Dir *dir; 558 559 if (termset == OK) 560 del_curterm(cur_term); 561 termset = setupterm(NULL, 1, NULL); 562 putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0)); 563 564 if (!curentry || !(dir = curentry->dat)) 565 return; 566 567 if (dir->curline - dir->printoff > lines-2) 568 dir->printoff = dir->curline - (lines-2); 569 570 uidisplay(curentry); 571 }