ui_ti.c - sacc - sacc(omys), simple console gopher client
 (HTM) git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) LICENSE
       ---
       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 }