tui_ti.c - sacc - [fork] customized build of sacc, the simple console gopher client
 (HTM) git clone git://src.adamsgaard.dk/sacc
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
       tui_ti.c (14102B)
       ---
            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 static char bufout[256];
           17 static struct termios tsave;
           18 static struct termios tsacc;
           19 static Item *curentry;
           20 
           21 void
           22 uisetup(void)
           23 {
           24         tcgetattr(0, &tsave);
           25         tsacc = tsave;
           26         tsacc.c_lflag &= ~(ECHO|ICANON);
           27         tsacc.c_cc[VMIN] = 1;
           28         tsacc.c_cc[VTIME] = 0;
           29         tcsetattr(0, TCSANOW, &tsacc);
           30 
           31         setupterm(NULL, 1, NULL);
           32         putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           33         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           34         putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
           35         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           36         fflush(stdout);
           37 }
           38 
           39 void
           40 uicleanup(void)
           41 {
           42         putp(tparm(change_scroll_region, 0, lines-1, 0, 0, 0, 0, 0, 0, 0));
           43         putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           44         tcsetattr(0, TCSANOW, &tsave);
           45         fflush(stdout);
           46 }
           47 
           48 char *
           49 uiprompt(char *fmt, ...)
           50 {
           51         va_list ap;
           52         char *input = NULL;
           53         size_t n;
           54         ssize_t r;
           55 
           56         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           57 
           58         putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
           59         putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           60         putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           61 
           62         va_start(ap, fmt);
           63         if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout))
           64                 bufout[sizeof(bufout)-1] = '\0';
           65         va_end(ap);
           66         n = mbsprint(bufout, columns);
           67 
           68         putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           69         putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           70 
           71         putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0));
           72 
           73         tsacc.c_lflag |= (ECHO|ICANON);
           74         tcsetattr(0, TCSANOW, &tsacc);
           75         fflush(stdout);
           76 
           77         n = 0;
           78         r = getline(&input, &n, stdin);
           79 
           80         tsacc.c_lflag &= ~(ECHO|ICANON);
           81         tcsetattr(0, TCSANOW, &tsacc);
           82         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
           83         fflush(stdout);
           84 
           85         if (r < 0) {
           86                 clearerr(stdin);
           87                 clear(&input);
           88         } else if (input[r - 1] == '\n') {
           89                 input[--r] = '\0';
           90         }
           91 
           92         return input;
           93 }
           94 
           95 static void
           96 printitem(Item *item)
           97 {
           98         if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type),
           99             item->username) >= sizeof(bufout))
          100                 bufout[sizeof(bufout)-1] = '\0';
          101         mbsprint(bufout, columns);
          102         putchar('\r');
          103 }
          104 
          105 static Item *
          106 help(Item *entry)
          107 {
          108         static Item item = {
          109                 .type = '0',
          110                 .raw = "Commands:\n"
          111                        "Down, " S(_key_lndown) ": move one line down.\n"
          112                         S(_key_entrydown) ": move to next link.\n"
          113                        "Up, " S(_key_lnup) ": move one line up.\n"
          114                         S(_key_entryup) ": move to previous link.\n"
          115                        "PgDown, " S(_key_pgdown) ": move one page down.\n"
          116                        "PgUp, " S(_key_pgup) ": move one page up.\n"
          117                        "Home, " S(_key_home) ": move to top of the page.\n"
          118                        "End, " S(_key_end) ": move to end of the page.\n"
          119                        "Right, " S(_key_pgnext) ": view highlighted item.\n"
          120                        "Left, " S(_key_pgprev) ": view previous item.\n"
          121                        S(_key_search) ": search current page.\n"
          122                        S(_key_searchnext) ": search string forward.\n"
          123                        S(_key_searchprev) ": search string backward.\n"
          124                        S(_key_cururi) ": print page URI.\n"
          125                        S(_key_seluri) ": print item URI.\n"
          126                        S(_key_yankcur) ": yank page URI to X selection.\n"
          127                        S(_key_yanksel) ": yank item URI to X selection.\n"
          128                        S(_key_help) ": show this help.\n"
          129                        "^D, " S(_key_quit) ": exit sacc.\n"
          130         };
          131 
          132         item.entry = entry;
          133 
          134         return &item;
          135 }
          136 
          137 void
          138 uistatus(char *fmt, ...)
          139 {
          140         va_list ap;
          141         size_t n;
          142 
          143         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          144 
          145         putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
          146         putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          147 
          148         va_start(ap, fmt);
          149         n = vsnprintf(bufout, sizeof(bufout), fmt, ap);
          150         va_end(ap);
          151 
          152         if (n < sizeof(bufout)-1) {
          153                 n += snprintf(bufout + n, sizeof(bufout) - n,
          154                               " [Press a key to continue \xe2\x98\x83]");
          155         }
          156         if (n >= sizeof(bufout))
          157                 bufout[sizeof(bufout)-1] = '\0';
          158 
          159         n = mbsprint(bufout, columns);
          160         putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          161         putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          162 
          163         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          164         fflush(stdout);
          165 
          166         sleep(1);
          167 }
          168 
          169 static void
          170 displaystatus(Item *item)
          171 {
          172         Dir *dir = item->dat;
          173         char *fmt;
          174         size_t n, nitems = dir ? dir->nitems : 0;
          175         unsigned long long printoff = dir ? dir->printoff : 0;
          176 
          177         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          178 
          179         putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
          180         putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          181         fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ?
          182               "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s";
          183         if (snprintf(bufout, sizeof(bufout), fmt,
          184                      (printoff + lines-1 >= nitems) ? 100 :
          185                      (printoff + lines-1) * 100 / nitems,
          186                      item->host, item->type, item->selector, item->port)
          187             >= sizeof(bufout))
          188                 bufout[sizeof(bufout)-1] = '\0';
          189         n = mbsprint(bufout, columns);
          190         putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          191         putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          192 
          193         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          194         fflush(stdout);
          195 }
          196 
          197 static void
          198 itemuri(Item *item)
          199 {
          200         size_t n;
          201 
          202         switch (item->type) {
          203         case '8':
          204                 n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s",
          205                              item->selector, item->host, item->port);
          206                 break;
          207         case 'h':
          208                 n = snprintf(bufout, sizeof(bufout), "%s",
          209                              item->selector+4);
          210                 break;
          211         case 'T':
          212                 n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s",
          213                              item->selector, item->host, item->port);
          214                 break;
          215         default:
          216                 n = snprintf(bufout, sizeof(bufout), "gopher://%s", item->host);
          217 
          218                 if (n < sizeof(bufout) && strcmp(item->port, "70")) {
          219                         n += snprintf(bufout+n, sizeof(bufout)-n, ":%s",
          220                                       item->port);
          221                 }
          222                 if (n < sizeof(bufout)) {
          223                         n += snprintf(bufout+n, sizeof(bufout)-n, "/%c%s",
          224                                       item->type, item->selector);
          225                 }
          226                 if (n < sizeof(bufout) && item->type == '7' && item->tag) {
          227                         n += snprintf(bufout+n, sizeof(bufout)-n, "%%09%s",
          228                                       item->tag + strlen(item->selector));
          229                 }
          230                 break;
          231         }
          232 
          233         if (n >= sizeof(bufout))
          234                 bufout[sizeof(bufout)-1] = '\0';
          235 }
          236 
          237 static void
          238 displayuri(Item *item)
          239 {
          240         size_t n;
          241 
          242         if (item->type == 0 || item->type == 'i')
          243                 return;
          244 
          245         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          246 
          247         putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0));
          248         putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          249 
          250         itemuri(item);
          251         n = mbsprint(bufout, columns);
          252         putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          253         putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          254 
          255         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          256         fflush(stdout);
          257 }
          258 
          259 static void
          260 yankuri(Item *item)
          261 {
          262         FILE *clip;
          263         char xselcmd[128];
          264         snprintf(xselcmd, sizeof(xselcmd), "%s 2>/dev/null", xselwrite);
          265 
          266         itemuri(item);
          267 
          268         if ((clip = popen(xselcmd, "w"))) {
          269                 if (!strncmp(bufout, "URL:", 4))
          270                         fprintf(clip, "%s", bufout + 4);
          271                 else
          272                         fprintf(clip, "%s", bufout);
          273                 pclose(clip);
          274         }
          275 }
          276 
          277 void
          278 uidisplay(Item *entry)
          279 {
          280         Item *items;
          281         Dir *dir;
          282         size_t i, curln, lastln, nitems, printoff;
          283 
          284         if (!entry ||
          285             !(entry->type == '1' || entry->type == '+' || entry->type == '7'))
          286                 return;
          287 
          288         curentry = entry;
          289 
          290         putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          291         displaystatus(entry);
          292 
          293         if (!(dir = entry->dat))
          294                 return;
          295 
          296         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          297 
          298         items = dir->items;
          299         nitems = dir->nitems;
          300         printoff = dir->printoff;
          301         curln = dir->curline;
          302         lastln = printoff + lines-1; /* one off for status bar */
          303 
          304         for (i = printoff; i < nitems && i < lastln; ++i) {
          305                 if (i != printoff)
          306                         putp(tparm(cursor_down, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          307                 if (i == curln) {
          308                         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          309                         putp(tparm(enter_standout_mode,
          310                                    0, 0, 0, 0, 0, 0, 0, 0, 0));
          311                 }
          312                 printitem(&items[i]);
          313                 putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          314                 if (i == curln)
          315                         putp(tparm(exit_standout_mode,
          316                                    0, 0, 0, 0, 0, 0, 0, 0, 0));
          317         }
          318 
          319         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          320         fflush(stdout);
          321 }
          322 
          323 static void
          324 movecurline(Item *item, int l)
          325 {
          326         Dir *dir = item->dat;
          327         size_t nitems;
          328         ssize_t curline, offline;
          329         int plines = lines-2;
          330 
          331         if (dir == NULL)
          332                 return;
          333 
          334         curline = dir->curline + l;
          335         nitems = dir->nitems;
          336         if (curline < 0 || curline >= nitems)
          337                 return;
          338 
          339         printitem(&dir->items[dir->curline]);
          340         dir->curline = curline;
          341 
          342         if (l > 0) {
          343                 offline = dir->printoff + lines-1;
          344                 if (curline - dir->printoff >= plines / 2 && offline < nitems) {
          345                         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          346 
          347                         putp(tparm(cursor_address, plines,
          348                                    0, 0, 0, 0, 0, 0, 0, 0));
          349                         putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          350                         printitem(&dir->items[offline]);
          351 
          352                         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          353                         dir->printoff += l;
          354                 }
          355         } else {
          356                 offline = dir->printoff + l;
          357                 if (curline - offline <= plines / 2 && offline >= 0) {
          358                         putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          359 
          360                         putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          361                         putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          362                         printitem(&dir->items[offline]);
          363                         putchar('\n');
          364 
          365                         putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          366                         dir->printoff += l;
          367                 }
          368         }
          369 
          370         putp(tparm(cursor_address, curline - dir->printoff,
          371                    0, 0, 0, 0, 0, 0, 0, 0));
          372         putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          373         printitem(&dir->items[curline]);
          374         putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0));
          375         displaystatus(item);
          376         fflush(stdout);
          377 }
          378 
          379 static void
          380 jumptoline(Item *entry, ssize_t line, int absolute)
          381 {
          382         Dir *dir = entry->dat;
          383         size_t lastitem;
          384         int lastpagetop, plines = lines-2;
          385 
          386         if (!dir)
          387                 return;
          388         lastitem = dir->nitems-1;
          389 
          390         if (line < 0)
          391                 line = 0;
          392         if (line > lastitem)
          393                 line = lastitem;
          394 
          395         if (dir->curline == line)
          396                 return;
          397 
          398         if (lastitem <= plines) {              /* all items fit on one page */
          399                 dir->curline = line;
          400         } else if (line == 0) {                /* jump to top */
          401                 if (absolute || dir->curline > plines || dir->printoff == 0)
          402                         dir->curline = 0;
          403                 dir->printoff = 0;
          404         } else if (line + plines < lastitem) { /* jump before last page */
          405                 dir->curline = line;
          406                 dir->printoff = line;
          407         } else {                               /* jump within the last page */
          408                 lastpagetop = lastitem - plines;
          409                 if (dir->printoff == lastpagetop || absolute)
          410                         dir->curline = line;
          411                 else if (dir->curline < lastpagetop)
          412                         dir->curline = lastpagetop;
          413                 dir->printoff = lastpagetop;
          414         }
          415 
          416         uidisplay(entry);
          417         return;
          418 }
          419 
          420 void
          421 searchinline(const char *searchstr, Item *entry, int pos)
          422 {
          423         Dir *dir;
          424         int i;
          425 
          426         if (!searchstr || !(dir = entry->dat))
          427                 return;
          428 
          429         if (pos > 0) {
          430                 for (i = dir->curline + 1; i < dir->nitems; ++i) {
          431                         if (strcasestr(dir->items[i].username, searchstr)) {
          432                                 jumptoline(entry, i, 1);
          433                                 break;
          434                         }
          435                 }
          436         } else {
          437                 for (i = dir->curline - 1; i > -1; --i) {
          438                         if (strcasestr(dir->items[i].username, searchstr)) {
          439                                 jumptoline(entry, i, 1);
          440                                 break;
          441                         }
          442                 }
          443         }
          444 }
          445 
          446 static ssize_t
          447 nearentry(Item *entry, int direction)
          448 {
          449         Dir *dir = entry->dat;
          450         size_t item, lastitem;
          451 
          452         if (!dir)
          453                 return -1;
          454         lastitem = dir->nitems;
          455         item = dir->curline + direction;
          456 
          457         for (; item < lastitem; item += direction) {
          458                 if (dir->items[item].type != 'i')
          459                         return item;
          460         }
          461 
          462         return dir->curline;
          463 }
          464 
          465 Item *
          466 uiselectitem(Item *entry)
          467 {
          468         Dir *dir;
          469         char *searchstr = NULL;
          470         int plines = lines-2;
          471 
          472         if (!entry || !(dir = entry->dat))
          473                 return NULL;
          474 
          475         for (;;) {
          476                 switch (getchar()) {
          477                 case 0x1b: /* ESC */
          478                         switch (getchar()) {
          479                         case 0x1b:
          480                                 goto quit;
          481                         case '[':
          482                                 break;
          483                         default:
          484                                 continue;
          485                         }
          486                         switch (getchar()) {
          487                         case '4':
          488                                 if (getchar() != '~')
          489                                         continue;
          490                                 goto end;
          491                         case '5':
          492                                 if (getchar() != '~')
          493                                         continue;
          494                                 goto pgup;
          495                         case '6':
          496                                 if (getchar() != '~')
          497                                         continue;
          498                                 goto pgdown;
          499                         case 'A':
          500                                 goto lnup;
          501                         case 'B':
          502                                 goto lndown;
          503                         case 'C':
          504                                 goto pgnext;
          505                         case 'D':
          506                                 goto pgprev;
          507                         case 'H':
          508                                 goto home;
          509                         case 0x1b:
          510                                 goto quit;
          511                         }
          512                         continue;
          513                 case _key_pgprev:
          514                 pgprev:
          515                         return entry->entry;
          516                 case _key_pgnext:
          517                 case '\n':
          518                 pgnext:
          519                         if (dir)
          520                                 return &dir->items[dir->curline];
          521                         continue;
          522                 case _key_lndown:
          523                 lndown:
          524                         movecurline(entry, 1);
          525                         continue;
          526                 case _key_entrydown:
          527                         jumptoline(entry, nearentry(entry, 1), 1);
          528                         continue;
          529                 case _key_pgdown:
          530                 pgdown:
          531                         jumptoline(entry, dir->printoff + plines, 0);
          532                         continue;
          533                 case _key_end:
          534                 end:
          535                         jumptoline(entry, dir->nitems, 0);
          536                         continue;
          537                 case _key_lnup:
          538                 lnup:
          539                         movecurline(entry, -1);
          540                         continue;
          541                 case _key_entryup:
          542                         jumptoline(entry, nearentry(entry, -1), 1);
          543                         continue;
          544                 case _key_pgup:
          545                 pgup:
          546                         jumptoline(entry, dir->printoff - plines, 0);
          547                         continue;
          548                 case _key_home:
          549                 home:
          550                         jumptoline(entry, 0, 0);
          551                         continue;
          552                 case _key_search:
          553                 search:
          554                         free(searchstr);
          555                         if (!((searchstr = uiprompt("Search for: ")) &&
          556                             searchstr[0])) {
          557                                 clear(&searchstr);
          558                                 continue;
          559                         }
          560                 case _key_searchnext:
          561                         searchinline(searchstr, entry, +1);
          562                         continue;
          563                 case _key_searchprev:
          564                         searchinline(searchstr, entry, -1);
          565                         continue;
          566                 case 0x04:
          567                 case _key_quit:
          568                 quit:
          569                         return NULL;
          570                 case _key_fetch:
          571                 fetch:
          572                         if (entry->raw)
          573                                 continue;
          574                         return entry;
          575                 case _key_cururi:
          576                         if (dir)
          577                                 displayuri(entry);
          578                         continue;
          579                 case _key_seluri:
          580                         if (dir)
          581                                 displayuri(&dir->items[dir->curline]);
          582                         continue;
          583                 case _key_yankcur:
          584                         if (dir)
          585                                 yankuri(entry);
          586                         continue;
          587                 case _key_yanksel:
          588                         if (dir)
          589                                 yankuri(&dir->items[dir->curline]);
          590                         continue;
          591                 case _key_help: /* FALLTHROUGH */
          592                         return help(entry);
          593                 default:
          594                         continue;
          595                 }
          596         }
          597 }
          598 
          599 void
          600 uisigwinch(int signal)
          601 {
          602         Dir *dir;
          603 
          604         setupterm(NULL, 1, NULL);
          605         putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0));
          606 
          607         if (!curentry || !(dir = curentry->dat))
          608                 return;
          609 
          610         if (dir->curline - dir->printoff > lines-2)
          611                 dir->curline = dir->printoff + lines-2;
          612 
          613         uidisplay(curentry);
          614 }