pax_global_header00006660000000000000000000000064132443016730014515gustar00rootroot0000000000000052 comment=16ab639d2e55e741c2e7028057a2f55876ad3db5 sacc-1.00/000077500000000000000000000000001324430167300123465ustar00rootroot00000000000000sacc-1.00/LICENSE000066400000000000000000000020311324430167300133470ustar00rootroot00000000000000Copyright (c) 2017-2018 Quentin Rameau Copyright (c) 2017-2018 Hiltjo Posthuma Copyright (c) 2017-2018 Christoph Lohmann <20h@r-36.net> Copyright (c) 2017 sin Copyright (c) 2017 trqx@goat.si Copyright (c) 2017 kroovy Copyright (c) 2018 Ivan J. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. sacc-1.00/Makefile000066400000000000000000000013051324430167300140050ustar00rootroot00000000000000# sacc: saccomys gopher client # See LICENSE file for copyright and license details. .POSIX: include config.mk BIN = sacc MAN = $(BIN).1 OBJ = $(BIN:=.o) ui_$(UI).o all: $(BIN) config.h: cp config.def.h config.h $(BIN): $(OBJ) $(CC) $(OBJ) $(LDFLAGS) $(LIBS) -o $@ $(OBJ): config.h config.mk common.h clean: rm -f $(BIN) $(OBJ) install: $(BIN) mkdir -p $(DESTDIR)$(PREFIX)/bin/ cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin/ chmod 555 $(DESTDIR)$(PREFIX)/bin/$(BIN) mkdir -p $(DESTDIR)$(MANDIR) cp -f $(MAN) $(DESTDIR)$(MANDIR) uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) # Stock FLAGS SACCCFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE $(CFLAGS) .c.o: $(CC) $(SACCCFLAGS) -c $< sacc-1.00/common.h000066400000000000000000000013531324430167300140110ustar00rootroot00000000000000#define clear(p) do { void **_p = (void **)(p); free(*_p); *_p = NULL; } while (0); typedef struct item Item; typedef struct dir Dir; struct item { char type; char *username; char *selector; char *host; char *port; char *raw; char *tag; void *dat; Item *entry; }; struct dir { Item *items; size_t nitems; size_t printoff; size_t curline; }; #ifndef asprintf int asprintf(char **s, const char *fmt, ...); #endif /* asprintf */ void die(const char *fmt, ...); size_t mbsprint(const char *s, size_t len); const char *typedisplay(char t); void uidisplay(Item *item); Item *uiselectitem(Item *entry); void uistatus(char *fmt, ...); void uicleanup(void); char *uiprompt(char *fmt, ...); void uisetup(void); void uisigwinch(int signal); sacc-1.00/config.def.h000066400000000000000000000020211324430167300145140ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ /* Screen UI navigation keys */ #define _key_lndown 'j' /* move one line down */ #define _key_entrydown 'J' /* move to next link */ #define _key_lnup 'k' /* move one line up */ #define _key_entryup 'K' /* move to next link */ #define _key_pgdown ' ' /* move one screen down */ #define _key_pgup 'b' /* move one screen up */ #define _key_home 'g' /* move to the top of page */ #define _key_end 'G' /* move to the bottom of page */ #define _key_pgnext 'l' /* view highlighted item */ #define _key_pgprev 'h' /* view previous item */ #define _key_uri 'u' /* print item uri */ #define _key_fetch 'L' /* refetch current item */ #define _key_help '?' /* display help */ #define _key_quit 'q' /* exit sacc */ #define _key_search '/' /* search */ #define _key_searchnext 'n' /* search same string forward */ #define _key_searchprev 'N' /* search same string backward */ /* default plumber */ static char *plumber = "xdg-open"; /* temporary directory */ static char *tmpdir = "/tmp/sacc"; sacc-1.00/config.mk000066400000000000000000000002241324430167300141420ustar00rootroot00000000000000# Install paths PREFIX = /usr/local MANDIR = $(PREFIX)/share/man/man1 # UI type # txt (textual) #UI=txt # ti (screen-oriented) UI=ti LIBS=-lcurses sacc-1.00/sacc.1000066400000000000000000000031131324430167300133370ustar00rootroot00000000000000.TH SACC 1 2018-02-24 .SH NAME sacc \- a terminal gopher client .SH SYNOPSIS .B sacc .IR URL .PP .SH DESCRIPTION .B sacc is a terminal gopher client. Gopher is a simple protocol to retreive hierarchical information. The protocol is defined in .I RFC 1436 (Gopher). .SH SHORTCUTS Shortcuts can be redefined in the .I config.h. The manpage is showing the default shortcuts. .TP .B Down or j Move one line down. .TP .B J Move to the next link item downwards. .TP .B Up or k Move one line up. .TP .B K Move to the next link item upwards. .TP .B PgDown or Space Move one page down. .TP .B PgUp or b Move one page up. .TP .B Home or g Move to the top of the page. .TP .B End or G Move to the end of the page. .TP .B Right or l View highlighted item. .TP .B Left or h View previous item. .TP .B L Refetch currently viewed item. .TP .B / Search in the current page. .TP .B n Search the same string forward. .TP .B N Search the same string backwards. .TP .B u Print URI of the highlighted item. .TP .B ? Show the help message of shortcuts. .TP .B ^D or q Exit sacc. .SH PLUMBER When some file is opened .I sacc cannot open natively, it will run .I xdg-open. This can be configured in the .I config.h to run some other plumber. .SH CUSTOMIZATION .B sacc can be customized by creating a custom config.h and (re)compiling the source code. This keeps it fast, secure and simple. .SH AUTHORS See the LICENSE file for the authors. .SH LICENSE See the LICENSE file for the terms of redistribution. .SH SEE ALSO .BR geomyidae (8), .BR clic (1), .BR xdg-open (1) .SH BUGS Please report them to Quentin Rameau sacc-1.00/sacc.c000066400000000000000000000350401324430167300134250ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "config.h" static char *mainurl; static Item *mainentry; static int devnullfd; static int parent = 1; static int interactive; void die(const char *fmt, ...) { va_list arg; va_start(arg, fmt); vfprintf(stderr, fmt, arg); va_end(arg); fputc('\n', stderr); exit(1); } #ifndef asprintf int asprintf(char **s, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (n == INT_MAX || !(*s = malloc(++n))) return -1; va_start(ap, fmt); vsnprintf(*s, n, fmt, ap); va_end(ap); return n; } #endif /* asprintf */ /* print `len' columns of characters. */ size_t mbsprint(const char *s, size_t len) { wchar_t wc; size_t col = 0, i, slen; int rl, w; if (!len) return col; slen = strlen(s); for (i = 0; i < slen; i += rl) { if ((rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4)) <= 0) break; if ((w = wcwidth(wc)) == -1) continue; if (col + w > len || (col + w == len && s[i + rl])) { fputs("\xe2\x80\xa6", stdout); col++; break; } fwrite(s + i, 1, rl, stdout); col += w; } return col; } static void * xreallocarray(void *m, const size_t n, const size_t s) { void *nm; if (n == 0 || s == 0) { free(m); return NULL; } if (s && n > (size_t)-1/s) die("realloc: overflow"); if (!(nm = realloc(m, n * s))) die("realloc: %s", strerror(errno)); return nm; } static void * xmalloc(const size_t n) { void *m = malloc(n); if (!m) die("malloc: %s\n", strerror(errno)); return m; } static void * xcalloc(size_t n) { char *m = xmalloc(n); while (n) m[--n] = 0; return m; } static char * xstrdup(const char *str) { char *s; if (!(s = strdup(str))) die("strdup: %s\n", strerror(errno)); return s; } static void usage(void) { die("usage: sacc URL"); } static void clearitem(Item *item) { Dir *dir; Item *items; char *tag; size_t i; if (!item) return; if (dir = item->dat) { items = dir->items; for (i = 0; i < dir->nitems; ++i) clearitem(&items[i]); free(items); clear(&item->dat); } if (parent && (tag = item->tag) && !strncmp(tag, tmpdir, strlen(tmpdir))) unlink(tag); clear(&item->tag); clear(&item->raw); } const char * typedisplay(char t) { switch (t) { case '0': return "Text+"; case '1': return "Dir +"; case '2': return "CSO |"; case '3': return "Err |"; case '4': return "Macf+"; case '5': return "DOSf+"; case '6': return "UUEf+"; case '7': return "Find+"; case '8': return "Tlnt|"; case '9': return "Binf+"; case '+': return "Mirr+"; case 'T': return "IBMt|"; case 'g': return "GIF +"; case 'I': return "Img +"; case 'h': return "HTML+"; case 'i': return " |"; case 's': return "Snd |"; default: return "! |"; } } static void printdir(Item *item) { Dir *dir; Item *items; size_t i, nitems; if (!item || !(dir = item->dat)) return; items = dir->items; nitems = dir->nitems; for (i = 0; i < nitems; ++i) { printf("%s%s\n", typedisplay(items[i].type), items[i].username); } } static void displaytextitem(Item *item) { FILE *pagerin; int pid, wpid; uicleanup(); switch (pid = fork()) { case -1: uistatus("Couldn't fork."); return; case 0: parent = 0; pagerin = popen("$PAGER", "we"); fputs(item->raw, pagerin); exit(pclose(pagerin)); default: while ((wpid = wait(NULL)) >= 0 && wpid != pid) ; } uisetup(); } static char * pickfield(char **raw, char sep) { char *c, *f = *raw; for (c = *raw; *c && *c != sep; ++c) ; *c = '\0'; *raw = c+1; return f; } static char * invaliditem(char *raw) { char c; int tabs; for (tabs = 0; (c = *raw) && c != '\n'; ++raw) { if (c == '\t') ++tabs; } if (c) *raw++ = '\0'; return (tabs == 3) ? NULL : raw; } static void molditem(Item *item, char **raw) { char *next; if (!*raw) return; if ((next = invaliditem(*raw))) { item->username = *raw; *raw = next; return; } item->type = *raw[0]++; item->username = pickfield(raw, '\t'); item->selector = pickfield(raw, '\t'); item->host = pickfield(raw, '\t'); item->port = pickfield(raw, '\r'); if (!*raw[0]) ++*raw; } static Dir * molddiritem(char *raw) { Item *items = NULL; char *s, *nl, *p; Dir *dir; size_t i, nitems; for (s = nl = raw, nitems = 0; p = strchr(nl, '\n'); ++nitems) { s = nl; nl = p+1; } if (!strcmp(s, ".\r\n") || !strcmp(s, ".\n")) --nitems; if (!nitems) { uistatus("Couldn't parse dir item"); return NULL; } dir = xmalloc(sizeof(Dir)); items = xreallocarray(items, nitems, sizeof(Item)); memset(items, 0, nitems * sizeof(Item)); for (i = 0; i < nitems; ++i) molditem(&items[i], &raw); dir->items = items; dir->nitems = nitems; dir->printoff = dir->curline = 0; return dir; } static char * getrawitem(int sock) { char *raw, *buf; size_t bn, bs; ssize_t n; raw = buf = NULL; bn = bs = n = 0; do { bs -= n; buf += n; if (bs < 1) { raw = xreallocarray(raw, ++bn, BUFSIZ); buf = raw + (bn-1) * BUFSIZ; bs = BUFSIZ; } } while ((n = read(sock, buf, bs)) > 0); *buf = '\0'; if (n < 0) { uistatus("Can't read socket: %s", strerror(errno)); clear(&raw); } return raw; } static int sendselector(int sock, const char *selector) { char *msg, *p; size_t ln; ssize_t n; ln = strlen(selector) + 3; msg = p = xmalloc(ln); snprintf(msg, ln--, "%s\r\n", selector); while ((n = write(sock, p, ln)) != -1 && n != 0) { ln -= n; p += n; } free(msg); if (n == -1) uistatus("Can't send message: %s", strerror(errno)); return n; } static int connectto(const char *host, const char *port) { static const struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, }; struct addrinfo *addrs, *addr; int sock, r; if (r = getaddrinfo(host, port, &hints, &addrs)) { uistatus("Can't resolve hostname \"%s\": %s", host, gai_strerror(r)); return -1; } for (addr = addrs; addr; addr = addr->ai_next) { if ((sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0) continue; if ((r = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0) { close(sock); continue; } break; } if (sock < 0) { uistatus("Can't open socket: %s", strerror(errno)); return -1; } if (r < 0) { uistatus("Can't connect to: %s:%s: %s", host, port, strerror(errno)); return -1; } freeaddrinfo(addrs); return sock; } static int download(Item *item, int dest) { char buf[BUFSIZ]; ssize_t r, w; int src; if (!item->tag) { if ((src = connectto(item->host, item->port)) < 0 || sendselector(src, item->selector) < 0) return 0; } else if ((src = open(item->tag, O_RDONLY)) < 0) { printf("Can't open source file %s: %s", item->tag, strerror(errno)); errno = 0; return 0; } while ((r = read(src, buf, BUFSIZ)) > 0) { while ((w = write(dest, buf, r)) > 0) r -= w; } if (r < 0 || w < 0) { printf("Error downloading file %s: %s", item->selector, strerror(errno)); errno = 0; } close(src); return (r == 0 && w == 0); } static void downloaditem(Item *item) { char *file, *path, *tag; mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; int dest; if (file = strrchr(item->selector, '/')) ++file; else file = item->selector; if (!(path = uiprompt("Download to [%s] (^D cancel): ", file))) return; if (!path[0]) path = xstrdup(file); if (tag = item->tag) { if (access(tag, R_OK) < 0) { clear(&item->tag); } else if (!strcmp(tag, path)) { goto cleanup; } } if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) { uistatus("Can't open destination file %s: %s", path, strerror(errno)); errno = 0; goto cleanup; } if (!download(item, dest)) goto cleanup; if (!item->tag) item->tag = path; return; cleanup: free(path); return; } static int fetchitem(Item *item) { int sock; if ((sock = connectto(item->host, item->port)) < 0 || sendselector(sock, item->selector) < 0) return 0; item->raw = getrawitem(sock); close(sock); if (item->raw && !*item->raw) { uistatus("Empty response from server"); clear(&item->raw); } return (item->raw != NULL); } static void plumb(char *url) { switch (fork()) { case -1: uistatus("Couldn't fork."); return; case 0: parent = 0; dup2(devnullfd, 1); dup2(devnullfd, 2); if (execlp(plumber, plumber, url, NULL) < 0) uistatus("execlp: plumb(%s): %s", url, strerror(errno)); } uistatus("Plumbed \"%s\"", url); } static void plumbitem(Item *item) { char *file, *path, *tag; mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; int n, dest, plumbitem; if (file = strrchr(item->selector, '/')) ++file; else file = item->selector; path = uiprompt("Download %s to (^D cancel, plumb): ", file); if (!path) return; if ((tag = item->tag) && access(tag, R_OK) < 0) { clear(&item->tag); tag = NULL; } plumbitem = path[0] ? 0 : 1; if (!path[0]) { clear(&path); if (!tag) { if (asprintf(&path, "%s/%s", tmpdir, file) < 0) die("Can't generate tmpdir path: ", strerror(errno)); } } if (path && (!tag || strcmp(tag, path))) { if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) { uistatus("Can't open destination file %s: %s", path, strerror(errno)); errno = 0; goto cleanup; } if (!download(item, dest) || tag) goto cleanup; } if (!tag) item->tag = path; if (plumbitem) plumb(item->tag); return; cleanup: free(path); return; } static int dig(Item *entry, Item *item) { char *plumburi = NULL; if (item->raw) /* already in cache */ return item->type; if (!item->entry) item->entry = entry ? entry : item; switch (item->type) { case 'h': /* fallthrough */ if (!strncmp(item->selector, "URL:", 4)) { plumb(item->selector+4); return 0; } case '0': if (!fetchitem(item)) return 0; break; case '1': case '+': case '7': if (!fetchitem(item) || !(item->dat = molddiritem(item->raw))) return 0; break; case '4': case '5': case '6': case '9': downloaditem(item); return 0; case '8': if (asprintf(&plumburi, "telnet://%s@%s:%s", item->selector, item->host, item->port) < 0) return 0; plumb(plumburi); free(plumburi); return 0; case 'g': case 'I': plumbitem(item); return 0; case 'T': if (asprintf(&plumburi, "tn3270://%s@%s:%s", item->selector, item->host, item->port) < 0) return 0; plumb(plumburi); free(plumburi); return 0; default: uistatus("Type %c (%s) not supported", item->type, typedisplay(item->type)); return 0; } return item->type; } static char * searchselector(Item *item) { char *pexp, *exp, *tag, *selector = item->selector; size_t n = strlen(selector); if ((tag = item->tag) && !strncmp(tag, selector, n)) pexp = tag + n+1; else pexp = ""; if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp))) return NULL; if (exp[0] && strcmp(exp, pexp)) { n += strlen(exp) + 2; tag = xmalloc(n); snprintf(tag, n, "%s\t%s", selector, exp); } free(exp); return tag; } static int searchitem(Item *entry, Item *item) { char *sel, *selector; if (!(sel = searchselector(item))) return 0; if (sel != item->tag) { clearitem(item); selector = item->selector; item->selector = item->tag = sel; dig(entry, item); item->selector = selector; } return (item->dat != NULL); } static void printout(Item *hole) { if (!hole) return; switch (hole->type) { case '0': if (dig(hole, hole)) fputs(hole->raw, stdout); return; case '1': case '+': if (dig(hole, hole)) printdir(hole); default: return; } } static void delve(Item *hole) { Item *entry = NULL; while (hole) { switch (hole->type) { case 'h': case '0': if (dig(entry, hole)) displaytextitem(hole); break; case '1': case '+': if (dig(entry, hole) && hole->dat) entry = hole; break; case '7': if (searchitem(entry, hole)) entry = hole; break; case '4': case '5': case '6': /* TODO decode? */ case '8': case '9': case 'g': case 'I': case 'T': dig(entry, hole); break; case 0: uistatus("Couldn't get %s:%s/%c%s", hole->host, hole->port, hole->type, hole->selector); } if (!entry) return; do { uidisplay(entry); hole = uiselectitem(entry); } while (hole == entry); } } static Item * moldentry(char *url) { Item *entry; char *p, *host = url, *port = "70", *gopherpath = "1"; int parsed, ipv6; if (p = strstr(url, "://")) { if (strncmp(url, "gopher", p - url)) die("Protocol not supported: %.*s", p - url, url); host = p + 3; } if (*host == '[') { ipv6 = 1; ++host; } else { ipv6 = 0; } for (parsed = 0, p = host; !parsed && *p; ++p) { switch (*p) { case ']': if (ipv6) { *p = '\0'; ipv6 = 0; } continue; case ':': if (!ipv6) { *p = '\0'; port = p+1; } continue; case '/': *p = '\0'; parsed = 1; continue; } } if (*host == '\0' || *port == '\0' || ipv6) die("Can't parse url"); if (*p != '\0') gopherpath = p; entry = xcalloc(sizeof(Item)); entry->type = gopherpath[0]; entry->username = entry->selector = ++gopherpath; entry->host = host; entry->port = port; entry->entry = entry; return entry; } static void cleanup(void) { clearitem(mainentry); if (parent) rmdir(tmpdir); free(mainentry); free(mainurl); if (interactive) uicleanup(); } static void setup(void) { struct sigaction sa; int fd; setlocale(LC_CTYPE, ""); setenv("PAGER", "more", 0); atexit(cleanup); /* reopen stdin in case we're reading from a pipe */ if ((fd = open("/dev/tty", O_RDONLY)) < 0) die("open: /dev/tty: %s", strerror(errno)); if (dup2(fd, 0) < 0) die("dup2: /dev/tty, stdin: %s", strerror(errno)); close(fd); if ((devnullfd = open("/dev/null", O_WRONLY)) < 0) die("open: /dev/null: %s", strerror(errno)); if (mkdir(tmpdir, S_IRWXU) < 0 && errno != EEXIST) die("mkdir: %s: %s", tmpdir, strerror(errno)); if(interactive = isatty(1)) { uisetup(); sigemptyset(&sa.sa_mask); sa.sa_handler = uisigwinch; sa.sa_flags = SA_RESTART; sigaction(SIGWINCH, &sa, NULL); } } int main(int argc, char *argv[]) { if (argc != 2) usage(); setup(); mainurl = xstrdup(argv[1]); mainentry = moldentry(mainurl); if (interactive) delve(mainentry); else printout(mainentry); exit(0); } sacc-1.00/ui_ti.c000066400000000000000000000267651324430167300136430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "config.h" #include "common.h" #define C(c) #c #define S(c) C(c) static char bufout[256]; static struct termios tsave; static struct termios tsacc; static Item *curentry; #if defined(__NetBSD__) #undef tparm #define tparm tiparm #endif void uisetup(void) { tcgetattr(0, &tsave); tsacc = tsave; tsacc.c_lflag &= ~(ECHO|ICANON); tcsetattr(0, TCSANOW, &tsacc); setupterm(NULL, 1, NULL); putp(tparm(clear_screen)); putp(tparm(save_cursor)); putp(tparm(change_scroll_region, 0, lines-2)); putp(tparm(restore_cursor)); fflush(stdout); } void uicleanup(void) { putp(tparm(change_scroll_region, 0, lines-1)); putp(tparm(clear_screen)); tcsetattr(0, TCSANOW, &tsave); fflush(stdout); } char * uiprompt(char *fmt, ...) { va_list ap; char *input = NULL; size_t n; ssize_t r; putp(tparm(save_cursor)); putp(tparm(cursor_address, lines-1, 0)); putp(tparm(clr_eol)); putp(tparm(enter_standout_mode)); va_start(ap, fmt); if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; va_end(ap); n = mbsprint(bufout, columns); putp(tparm(exit_standout_mode)); if (n < columns) printf("%*s", columns - n, " "); putp(tparm(cursor_address, lines-1, n)); tsacc.c_lflag |= (ECHO|ICANON); tcsetattr(0, TCSANOW, &tsacc); fflush(stdout); n = 0; r = getline(&input, &n, stdin); tsacc.c_lflag &= ~(ECHO|ICANON); tcsetattr(0, TCSANOW, &tsacc); putp(tparm(restore_cursor)); fflush(stdout); if (r < 0) { clearerr(stdin); clear(&input); } else if (input[r - 1] == '\n') { input[--r] = '\0'; } return input; } static void printitem(Item *item) { if (snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type), item->username) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; mbsprint(bufout, columns); putchar('\r'); } static Item * help(Item *entry) { static Item item = { .type = '0', .raw = "Commands:\n" "Down, " S(_key_lndown) ": move one line down.\n" "Up, " S(_key_lnup) ": move one line up.\n" "PgDown, " S(_key_pgdown) ": move one page down.\n" "PgUp, " S(_key_pgup) ": move one page up.\n" "Home, " S(_key_home) ": move to top of the page.\n" "End, " S(_key_end) ": move to end of the page.\n" "Right, " S(_key_pgnext) ": view highlighted item.\n" "Left, " S(_key_pgprev) ": view previous item.\n" S(_key_search) ": search current page.\n" S(_key_search_next) ": search string forward.\n" S(_key_search_prev) ": search string backward.\n" S(_key_uri) ": print item uri.\n" S(_key_help) ": show this help.\n" "^D, " S(_key_quit) ": exit sacc.\n" }; item.entry = entry; return &item; } void uistatus(char *fmt, ...) { va_list ap; size_t n; putp(tparm(save_cursor)); putp(tparm(cursor_address, lines-1, 0)); putp(tparm(enter_standout_mode)); va_start(ap, fmt); n = vsnprintf(bufout, sizeof(bufout), fmt, ap); va_end(ap); if (n < sizeof(bufout)-1) { n += snprintf(bufout + n, sizeof(bufout) - n, " [Press a key to continue \xe2\x98\x83]"); } if (n >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; n = mbsprint(bufout, columns); putp(tparm(exit_standout_mode)); if (n < columns) printf("%*s", columns - n, " "); putp(tparm(restore_cursor)); fflush(stdout); getchar(); } static void displaystatus(Item *item) { Dir *dir = item->dat; char *fmt; size_t n, nitems = dir ? dir->nitems : 0; unsigned long long printoff = dir ? dir->printoff : 0; putp(tparm(save_cursor)); putp(tparm(cursor_address, lines-1, 0)); putp(tparm(enter_standout_mode)); fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ? "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s"; if (snprintf(bufout, sizeof(bufout), fmt, (printoff + lines-1 >= nitems) ? 100 : (printoff + lines-1) * 100 / nitems, item->host, item->type, item->selector, item->port) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; n = mbsprint(bufout, columns); putp(tparm(exit_standout_mode)); if (n < columns) printf("%*s", columns - n, " "); putp(tparm(restore_cursor)); fflush(stdout); } static void displayuri(Item *item) { char *fmt; size_t n; if (item->type == 0 || item->type == 'i') return; putp(tparm(save_cursor)); putp(tparm(cursor_address, lines-1, 0)); putp(tparm(enter_standout_mode)); switch (item->type) { case '8': n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s", item->selector, item->host, item->port); break; case 'h': n = snprintf(bufout, sizeof(bufout), "%s: %s", item->username, item->selector); break; case 'T': n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s", item->selector, item->host, item->port); break; default: fmt = strcmp(item->port, "70") ? "%1$s: gopher://%2$s:%5$s/%3$c%4$s" : "%s: gopher://%s/%c%s"; n = snprintf(bufout, sizeof(bufout), fmt, item->username, item->host, item->type, item->selector, item->port); break; } if (n >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; n = mbsprint(bufout, columns); putp(tparm(exit_standout_mode)); if (n < columns) printf("%*s", columns - n, " "); putp(tparm(restore_cursor)); fflush(stdout); } void uidisplay(Item *entry) { Item *items; Dir *dir; size_t i, curln, lastln, nitems, printoff; if (!entry || !(entry->type == '1' || entry->type == '+' || entry->type == '7')) return; curentry = entry; putp(tparm(clear_screen)); displaystatus(entry); if (!(dir = entry->dat)) return; putp(tparm(save_cursor)); items = dir->items; nitems = dir->nitems; printoff = dir->printoff; curln = dir->curline; lastln = printoff + lines-1; /* one off for status bar */ for (i = printoff; i < nitems && i < lastln; ++i) { if (i != printoff) putp(tparm(cursor_down)); if (i == curln) { putp(tparm(save_cursor)); putp(tparm(enter_standout_mode)); } printitem(&items[i]); putp(tparm(column_address, 0)); if (i == curln) putp(tparm(exit_standout_mode)); } putp(tparm(restore_cursor)); fflush(stdout); } static void movecurline(Item *item, int l) { Dir *dir = item->dat; size_t nitems; ssize_t curline, offline; int plines = lines-2; if (dir == NULL) return; curline = dir->curline + l; nitems = dir->nitems; if (curline < 0 || curline >= nitems) return; printitem(&dir->items[dir->curline]); dir->curline = curline; if (l > 0) { offline = dir->printoff + lines-1; if (curline - dir->printoff >= plines / 2 && offline < nitems) { putp(tparm(save_cursor)); putp(tparm(cursor_address, plines, 0)); putp(tparm(scroll_forward)); printitem(&dir->items[offline]); putp(tparm(restore_cursor)); dir->printoff += l; } } else { offline = dir->printoff + l; if (curline - offline <= plines / 2 && offline >= 0) { putp(tparm(save_cursor)); putp(tparm(cursor_address, 0, 0)); putp(tparm(scroll_reverse)); printitem(&dir->items[offline]); putchar('\n'); putp(tparm(restore_cursor)); dir->printoff += l; } } putp(tparm(cursor_address, curline - dir->printoff, 0)); putp(tparm(enter_standout_mode)); printitem(&dir->items[curline]); putp(tparm(exit_standout_mode)); displaystatus(item); fflush(stdout); } static void jumptoline(Item *entry, ssize_t line, int absolute) { Dir *dir = entry->dat; size_t lastitem; int lastpagetop, plines = lines-2; if (!dir) return; lastitem = dir->nitems-1; if (line < 0) line = 0; if (line > lastitem) line = lastitem; if (dir->curline == line) return; if (lastitem <= plines) { /* all items fit on one page */ dir->curline = line; } else if (line == 0) { /* jump to top */ if (absolute || dir->curline > plines || dir->printoff == 0) dir->curline = 0; dir->printoff = 0; } else if (line + plines < lastitem) { /* jump before last page */ dir->curline = line; dir->printoff = line; } else { /* jump within the last page */ lastpagetop = lastitem - plines; if (dir->printoff == lastpagetop || absolute) dir->curline = line; else if (dir->curline < lastpagetop) dir->curline = lastpagetop; dir->printoff = lastpagetop; } uidisplay(entry); return; } void searchinline(const char *searchstr, Item *entry, int pos) { Dir *dir; int i; if (!searchstr || !(dir = entry->dat)) return; if (pos > 0) { for (i = dir->curline + 1; i < dir->nitems; ++i) { if (strstr(dir->items[i].username, searchstr)) { jumptoline(entry, i, 1); break; } } } else { for (i = dir->curline - 1; i > -1; --i) { if (strstr(dir->items[i].username, searchstr)) { jumptoline(entry, i, 1); break; } } } } static ssize_t nearentry(Item *entry, int direction) { Dir *dir = entry->dat; size_t item, lastitem; if (!dir) return -1; lastitem = dir->nitems; item = dir->curline + direction; for (; item >= 0 && item < lastitem; item += direction) { if (dir->items[item].type != 'i') return item; } return dir->curline; } Item * uiselectitem(Item *entry) { Dir *dir; char *searchstr = NULL; int plines = lines-2; if (!entry || !(dir = entry->dat)) return NULL; for (;;) { switch (getchar()) { case 0x1b: /* ESC */ switch (getchar()) { case 0x1b: goto quit; case '[': break; default: continue; } switch (getchar()) { case '4': if (getchar() != '~') continue; goto end; case '5': if (getchar() != '~') continue; goto pgup; case '6': if (getchar() != '~') continue; goto pgdown; case 'A': goto lnup; case 'B': goto lndown; case 'C': goto pgnext; case 'D': goto pgprev; case 'H': goto home; case 0x1b: goto quit; } continue; case _key_pgprev: pgprev: return entry->entry; case _key_pgnext: case '\n': pgnext: if (dir) return &dir->items[dir->curline]; continue; case _key_lndown: lndown: movecurline(entry, 1); continue; case _key_entrydown: jumptoline(entry, nearentry(entry, 1), 1); continue; case _key_pgdown: pgdown: jumptoline(entry, dir->printoff + plines, 0); continue; case _key_end: end: jumptoline(entry, dir->nitems, 0); continue; case _key_lnup: lnup: movecurline(entry, -1); continue; case _key_entryup: jumptoline(entry, nearentry(entry, -1), 1); continue; case _key_pgup: pgup: jumptoline(entry, dir->printoff - plines, 0); continue; case _key_home: home: jumptoline(entry, 0, 0); continue; case _key_search: search: free(searchstr); if ((searchstr = uiprompt("Search for: ")) && searchstr[0]) goto searchnext; clear(&searchstr); continue; case _key_searchnext: searchnext: searchinline(searchstr, entry, +1); continue; case _key_searchprev: searchinline(searchstr, entry, -1); continue; case _key_quit: quit: return NULL; case _key_fetch: fetch: if (entry->raw) continue; return entry; case _key_uri: if (dir) displayuri(&dir->items[dir->curline]); continue; case _key_help: /* FALLTHROUGH */ return help(entry); default: continue; } } } void uisigwinch(int signal) { Dir *dir; setupterm(NULL, 1, NULL); putp(tparm(change_scroll_region, 0, lines-2)); if (!curentry || !(dir = curentry->dat)) return; if (dir->curline - dir->printoff > lines-2) dir->curline = dir->printoff + lines-2; uidisplay(curentry); } sacc-1.00/ui_txt.c000066400000000000000000000145731324430167300140400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "common.h" static char bufout[256]; static Item *curentry; static char cmd; int lines, columns; static void viewsize(int *ln, int *col) { struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) < 0) { die("Could not get terminal resolution: %s", strerror(errno)); } if (ln) *ln = ws.ws_row-1; /* one off for status bar */ if (col) *col = ws.ws_col; } void uisetup(void) { viewsize(&lines, &columns); } void uicleanup(void) { return; } void help(void) { puts("Commands:\n" "N = [1-9]...: browse item N.\n" "uN...: print item N uri.\n" "0: browse previous item.\n" "n: show next page.\n" "p: show previous page.\n" "t: go to the top of the page\n" "b: go to the bottom of the page\n" "/foo: search for string \"foo\"\n" "!: refetch failed item.\n" "^D, q: quit.\n" "h, ?: this help."); } static int ndigits(size_t n) { return (n < 10) ? 1 : (n < 100) ? 2 : 3; } void uistatus(char *fmt, ...) { va_list arg; int n; va_start(arg, fmt); n = vsnprintf(bufout, sizeof(bufout), fmt, arg); va_end(arg); if (n < sizeof(bufout)-1) { n += snprintf(bufout + n, sizeof(bufout) - n, " [Press Enter to continue \xe2\x98\x83]"); } if (n >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; mbsprint(bufout, columns); fflush(stdout); getchar(); } static void printstatus(Item *item, char c) { Dir *dir = item->dat; char *fmt; size_t nitems = dir ? dir->nitems : 0; unsigned long long printoff = dir ? dir->printoff : 0; fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ? "%1$3lld%%%*2$3$c %4$s:%8$s/%5$c%6$s [%7$c]: " : "%3lld%%%*c %s/%c%s [%c]: "; if (snprintf(bufout, sizeof(bufout), fmt, (printoff + lines-1 >= nitems) ? 100 : (printoff + lines) * 100 / nitems, ndigits(nitems)+2, '|', item->host, item->type, item->selector, c, item->port) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; mbsprint(bufout, columns); } char * uiprompt(char *fmt, ...) { va_list ap; char *input = NULL; size_t n = 0; ssize_t r; va_start(ap, fmt); if (vsnprintf(bufout, sizeof(bufout), fmt, ap) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; va_end(ap); mbsprint(bufout, columns); fflush(stdout); if ((r = getline(&input, &n, stdin)) < 0) { clearerr(stdin); clear(&input); putchar('\n'); } else if (input[r - 1] == '\n') { input[--r] = '\0'; } return input; } void uidisplay(Item *entry) { Item *items; Dir *dir; size_t i, nlines, nitems; int nd; if (!entry || !(entry->type == '1' || entry->type == '+' || entry->type == '7') || !(dir = entry->dat)) return; curentry = entry; items = dir->items; nitems = dir->nitems; nlines = dir->printoff + lines; nd = ndigits(nitems); for (i = dir->printoff; i < nitems && i < nlines; ++i) { if (snprintf(bufout, sizeof(bufout), "%*zu %s %s", nd, i+1, typedisplay(items[i].type), items[i].username) >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; mbsprint(bufout, columns); putchar('\n'); } fflush(stdout); } void printuri(Item *item, size_t i) { char *fmt; int n; if (!item) return; switch (item->type) { case 0: return; case '8': n = snprintf(bufout, sizeof(bufout), "telnet://%s@%s:%s", item->selector, item->host, item->port); break; case 'i': n = snprintf(bufout, sizeof(bufout), "%zu: %s", i, item->username); break; case 'h': n = snprintf(bufout, sizeof(bufout), "%zu: %s: %s", i, item->username, item->selector); break; case 'T': n = snprintf(bufout, sizeof(bufout), "tn3270://%s@%s:%s", item->selector, item->host, item->port); break; default: fmt = strcmp(item->port, "70") ? "%1$zu: %2$s: gopher://%3$s:%6$s/%4$c%5$s" : "%zu: %s: gopher://%s/%c%s"; n = snprintf(bufout, sizeof(bufout), fmt, i, item->username, item->host, item->type, item->selector, item->port); break; } if (n >= sizeof(bufout)) bufout[sizeof(bufout)-1] = '\0'; mbsprint(bufout, columns); putchar('\n'); } void searchinline(const char *searchstr, Item *entry) { Dir *dir; size_t i; if (!searchstr || !*searchstr || !(dir = entry->dat)) return; for (i = 0; i < dir->nitems; ++i) if (strstr(dir->items[i].username, searchstr)) printuri(&(dir->items[i]), i + 1); } Item * uiselectitem(Item *entry) { Dir *dir; char buf[BUFSIZ], *sstr, nl; int item, nitems; if (!entry || !(dir = entry->dat)) return NULL; nitems = dir ? dir->nitems : 0; for (;;) { if (!cmd) cmd = 'h'; printstatus(entry, cmd); fflush(stdout); if (!fgets(buf, sizeof(buf), stdin)) { putchar('\n'); return NULL; } if (isdigit(*buf)) { cmd = '\0'; nl = '\0'; if (sscanf(buf, "%d%c", &item, &nl) != 2 || nl != '\n') item = -1; } else if (!strcmp(buf+1, "\n")) { item = -1; cmd = *buf; } else if (*buf == '/') { for (sstr = buf+1; *sstr && *sstr != '\n'; ++sstr) ; *sstr = '\0'; sstr = buf+1; cmd = *buf; } else if (isdigit(*(buf+1))) { nl = '\0'; if (sscanf(buf+1, "%d%c", &item, &nl) != 2 || nl != '\n') item = -1; else cmd = *buf; } switch (cmd) { case '\0': break; case 'q': return NULL; case 'n': if (lines < nitems - dir->printoff && lines < (size_t)-1 - dir->printoff) dir->printoff += lines; return entry; case 'p': if (lines <= dir->printoff) dir->printoff -= lines; else dir->printoff = 0; return entry; case 'b': if (nitems > lines) dir->printoff = nitems - lines; else dir->printoff = 0; return entry; case 't': dir->printoff = 0; return entry; case '!': if (entry->raw) continue; return entry; case 'u': if (item > 0 && item <= nitems) printuri(&dir->items[item-1], item); continue; case '/': if (*sstr) searchinline(sstr, entry); continue; case 'h': case '?': help(); continue; default: cmd = 'h'; continue; } if (item >= 0 && item <= nitems) break; } if (item > 0) return &dir->items[item-1]; return entry->entry; } void uisigwinch(int signal) { uisetup(); putchar('\n'); uidisplay(curentry); printstatus(curentry, cmd); fflush(stdout); }