tui_ti.c - sacc - sacc(omys), simple console gopher client (mirror)
 (HTM) git clone https://git.parazyd.org/sacc
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
       tui_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 }