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.\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 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 }