pax_global_header00006660000000000000000000000064141507110710014507gustar00rootroot0000000000000052 comment=edbf8409a1e281d17422e7b12b5edd3832b4429d sacc-1.05/000077500000000000000000000000001415071107100123455ustar00rootroot00000000000000sacc-1.05/LICENSE000066400000000000000000000023601415071107100133530ustar00rootroot00000000000000Copyright (c) 2017-2021 Quentin Rameau Copyright (c) 2017-2021 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. Copyright (c) 2018 Leonardo Taccari Copyright (c) 2018 Stefan Hagen Copyright (c) 2021 escapeinsert Copyright (c) 2021 Anders Damsgaard 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.05/Makefile000066400000000000000000000014731415071107100140120ustar00rootroot00000000000000# 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 io_$(IO).o all: $(BIN) config.h: cp config.def.h config.h $(BIN): $(OBJ) $(CC) $(OBJ) $(LDFLAGS) $(IOLIBS) $(LIBS) -o $@ $(OBJ): config.h config.mk common.h io.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) $(DESTDIR)$(MANDIR)/$(MAN) # Stock FLAGS SACCCFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -D_GNU_SOURCE \ -DVERSION=\"$(GETVER)\" $(IOCFLAGS) $(CFLAGS) .c.o: $(CC) $(SACCCFLAGS) -c $< sacc-1.05/common.h000066400000000000000000000020301415071107100140010ustar00rootroot00000000000000#define clear(p) do { void **_p = (void **)(p); free(*_p); *_p = NULL; } while (0); typedef struct item Item; typedef struct dir Dir; enum { TXT, DIR, CSO, ERR, MAC, DOS, UUE, IND, TLN, BIN, MIR, IBM, GIF, IMG, URL, INF, UNK, BRK, }; struct item { char type; char redtype; 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; }; extern void (*diag)(char *, ...); extern void die(const char *, ...); extern size_t mbsprint(const char *, size_t); #ifdef NEED_STRCASESTR extern char *strcasestr(const char *, const char *); #endif /* NEED_STRCASESTR */ extern const char *typedisplay(char); extern int itemuri(Item *, char *, size_t); extern void yankitem(Item *); extern void uicleanup(void); extern void uidisplay(Item *); extern char *uiprompt(char *, ...); extern Item *uiselectitem(Item *); extern void uisetup(void); extern void uisigwinch(int); extern void uistatus(char *, ...); sacc-1.05/config.def.h000066400000000000000000000034221415071107100145210ustar00rootroot00000000000000/* 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 previous 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_cururi 'U' /* print page uri */ #define _key_seluri 'u' /* print item uri */ #define _key_yankcur 'Y' /* yank page uri */ #define _key_yanksel 'y' /* yank 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 */ #ifdef NEED_CONF /* default yanker */ static char *yanker = "xclip"; /* default plumber */ static char *plumber = "xdg-open"; /* modal plumber will make sacc wait for the plumber to return */ static int modalplumber = 0; /* temporary directory template (must end with six 'X' characters) */ static char tmpdir[] = "/tmp/sacc-XXXXXX"; /* menu items strings */ static char *typestr[] = { [TXT] = "Txt+", [DIR] = "Dir+", [CSO] = "CSO|", [ERR] = "Err|", [MAC] = "Mac+", [DOS] = "DOS+", [UUE] = "UUE+", [IND] = "Ind+", [TLN] = "Tln|", [BIN] = "Bin+", [MIR] = "Mir+", [IBM] = "IBM|", [GIF] = "GIF+", [IMG] = "Img+", [URL] = "URL+", [INF] = " |", [UNK] = " ? +", [BRK] = "! |", /* malformed entry */ }; #endif /* NEED_CONF */ sacc-1.05/config.mk000066400000000000000000000011441415071107100141430ustar00rootroot00000000000000# Install paths PREFIX = /usr/local MANDIR = $(PREFIX)/share/man/man1 # Version to put in the ident string VERSION = "1.05" GETVER = $$(git rev-parse --is-inside-work-tree >/dev/null 2>&1 && \ git describe --tags || echo $(VERSION)) # UI type # txt (textual) #UI=txt # ti (screen-oriented) UI=ti LIBS=-lcurses # IO type # clr (clear) #IO = clr # tls (Transport Layer Security) IO = tls IOLIBS = -ltls IOCFLAGS = -DUSE_TLS # Define NEED_ASPRINTF and/or NEED_STRCASESTR in your cflags if your system does # not provide asprintf() or strcasestr(), respectively. #CFLAGS = -DNEED_ASPRINTF -DNEED_STRCASESTR sacc-1.05/io.h000066400000000000000000000006641415071107100131330ustar00rootroot00000000000000#include struct cnx { #ifdef USE_TLS struct tls *tls; #endif int sock; }; extern int tls; extern int (*ioclose)(struct cnx *); extern int (*ioconnect)(struct cnx *, struct addrinfo *, const char *); extern void (*ioconnerr)(struct cnx *, const char *, const char *, int); extern char *(*ioparseurl)(char *); extern ssize_t (*ioread)(struct cnx *, void *, size_t); extern ssize_t (*iowrite)(struct cnx *, void *, size_t); sacc-1.05/io_clr.c000066400000000000000000000024611415071107100137630ustar00rootroot00000000000000#include #include #include #include #include "common.h" #include "io.h" static int close_clr(struct cnx *c) { return close(c->sock); } static int connect_clr(struct cnx *c, struct addrinfo *ai, const char *host) { return connect(c->sock, ai->ai_addr, ai->ai_addrlen); } static void connerr_clr(struct cnx *c, const char *host, const char *port, int err) { if (c->sock == -1) diag("Can't open socket: %s", strerror(err)); else diag("Can't connect to: %s:%s: %s", host, port, strerror(err)); } static char * parseurl_clr(char *url) { char *p; if (p = strstr(url, "://")) { if (strncmp(url, "gopher", p - url)) die("Protocol not supported: %.*s", p - url, url); url = p + 3; } return url; } static ssize_t read_clr(struct cnx *c, void *buf, size_t bs) { return read(c->sock, buf, bs); } static ssize_t write_clr(struct cnx *c, void *buf, size_t bs) { return write(c->sock, buf, bs); } int (*ioclose)(struct cnx *) = close_clr; int (*ioconnect)(struct cnx *, struct addrinfo *, const char *) = connect_clr; void (*ioconnerr)(struct cnx *, const char *, const char *, int) = connerr_clr; char *(*ioparseurl)(char *) = parseurl_clr; ssize_t (*ioread)(struct cnx *, void *, size_t) = read_clr; ssize_t (*iowrite)(struct cnx *, void *, size_t) = write_clr; sacc-1.05/io_tls.c000066400000000000000000000053201415071107100140020ustar00rootroot00000000000000#include #include #include #include #include #include #include "common.h" #include "io.h" int tls; static int close_tls(struct cnx *c) { int r; if (tls) { if (c->tls) { do { r = tls_close(c->tls); } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); } tls_free(c->tls); } return close(c->sock); } static int connect_tls(struct cnx *c, struct addrinfo *ai, const char *host) { struct tls *t; char *r; int s; c->tls = NULL; s = c->sock; if (connect(s, ai->ai_addr, ai->ai_addrlen) == -1) return -1; if (tls) { if ((t = tls_client()) == NULL) return -1; if (tls_connect_socket(t, s, host) == 0) { do { s = tls_handshake(t); } while (s == TLS_WANT_POLLIN || s == TLS_WANT_POLLOUT); if (s == 0) { c->tls = t; } else { diag("Can't establish TLS with \"%s\": %s", host, tls_error(t)); r = uiprompt("Retry on cleartext? [Yn]: "); switch (*r) { case 'Y': case 'y': case '\0': tls = 0; s = -2; break; default: s = -3; } free(r); } } else { s = -1; } } return s; } static void connerr_tls(struct cnx *c, const char *host, const char *port, int err) { if (c->sock == -1) { diag("Can't open socket: %s", strerror(err)); } else { if (tls && c->tls) { diag("Can't establish TLS with \"%s\": %s", host, tls_error(c->tls)); } else { diag("Can't connect to: %s:%s: %s", host, port, strerror(err)); } } } static char * parseurl_tls(char *url) { char *p; if (p = strstr(url, "://")) { if (!strncmp(url, "gopher", p - url)) { if (tls) diag("Switching from gophers to gopher"); tls = 0; } else if (!strncmp(url, "gophers", p - url)) { tls = 1; } else { die("Protocol not supported: %.*s", p - url, url); } url = p + 3; } return url; } static ssize_t read_tls(struct cnx *c, void *buf, size_t bs) { ssize_t n; if (tls && c->tls) { do { n = tls_read(c->tls, buf, bs); } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); } else { n = read(c->sock, buf, bs); } return n; } static ssize_t write_tls(struct cnx *c, void *buf, size_t bs) { ssize_t n; if (tls) { do { n = tls_write(c->tls, buf, bs); } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); } else { n = write(c->sock, buf, bs); } return n; } int (*ioclose)(struct cnx *) = close_tls; int (*ioconnect)(struct cnx *, struct addrinfo *, const char *) = connect_tls; void (*ioconnerr)(struct cnx *, const char *, const char *, int) = connerr_tls; char *(*ioparseurl)(char *) = parseurl_tls; ssize_t (*ioread)(struct cnx *, void *, size_t) = read_tls; ssize_t (*iowrite)(struct cnx *, void *, size_t) = write_tls; sacc-1.05/sacc.1000066400000000000000000000040571415071107100133460ustar00rootroot00000000000000.TH SACC 1 2018-02-24 .SH NAME sacc \- a terminal gopher client .SH SYNOPSIS .B sacc .IR URL .PP .SH DESCRIPTION .B sacc (short for saccomys) is a terminal gopher client. Gopher is a simple protocol to retrieve hierarchical information. The protocol is defined in .I RFC 1436 (Gopher). .SH SHORTCUTS Shortcuts can be redefined in the .BR 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 the URI of the current page. .TP .B u Print the URI of the highlighted item. .TP .B Y Yank the current page URI to an external program. .TP .B y Yank the highlighted item URI to an external program. .TP .B ? Show the help message of shortcuts. .TP .B ^D or q Exit sacc. .SH ENVIRONMENT .B sacc will use the pager set in the .B PAGER variable to display textual content. If unset, it'll try to use .BR more (1). Note that sacc takes back control of the screen as soon as the pager finishes. In case of doubt, read your pager documentation. .SH PLUMBER When some file is opened .B sacc cannot open natively, it will run .BR xdg-open . This can be configured in the .B config.h to run some other plumber. .SH CUSTOMIZATION .B sacc can be customized by creating a custom .B config.h and (re)compiling the source code. This keeps it fast, secure and simple. .SH AUTHORS See the .B LICENSE file for the authors. .SH LICENSE See the .B LICENSE file for the terms of redistribution. .SH SEE ALSO .BR more (1) .BR xdg-open (1) .BR geomyidae (8) .BR clic (1) .SH BUGS Please report them to Quentin Rameau sacc-1.05/sacc.c000066400000000000000000000450351415071107100134310ustar00rootroot00000000000000/* 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 #include "common.h" #include "io.h" #define NEED_CONF #include "config.h" #undef NEED_CONF void (*diag)(char *, ...); static const char *ident = "@(#) sacc(omys): " VERSION; static char intbuf[256]; /* 256B ought to be enough for any URI */ static char *mainurl; static Item *mainentry; static int devnullfd; static int parent = 1; static int interactive; static void stddiag(char *fmt, ...) { va_list arg; va_start(arg, fmt); vfprintf(stderr, fmt, arg); va_end(arg); fputc('\n', stderr); } void die(const char *fmt, ...) { va_list arg; va_start(arg, fmt); vfprintf(stderr, fmt, arg); va_end(arg); fputc('\n', stderr); exit(1); } #ifdef NEED_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 /* NEED_ASPRINTF */ #ifdef NEED_STRCASESTR char * strcasestr(const char *h, const char *n) { size_t i; if (!n[0]) return (char *)h; for (; *h; ++h) { for (i = 0; n[i] && tolower((unsigned char)n[i]) == tolower((unsigned char)h[i]); ++i) ; if (n[i] == '\0') return (char *)h; } return NULL; } #endif /* NEED_STRCASESTR */ /* print `len' columns of characters. */ size_t mbsprint(const char *s, size_t len) { wchar_t wc; size_t col = 0, i, slen; const char *p; int rl, pl, w; if (!len) return col; slen = strlen(s); for (i = 0; i < slen; i += rl) { rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4); if (rl == -1) { /* reset state */ mbtowc(NULL, NULL, 0); p = "\xef\xbf\xbd"; /* replacement character */ pl = 3; rl = w = 1; } else { if ((w = wcwidth(wc)) == -1) continue; pl = rl; p = s + i; } if (col + w > len || (col + w == len && s[i + rl])) { fputs("\xe2\x80\xa6", stdout); /* ellipsis */ col++; break; } fwrite(p, 1, pl, stdout); col += w; } return col; } static void * xreallocarray(void *m, size_t n, 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", strerror(errno)); return m; } static void * xcalloc(size_t n) { char *m = calloc(1, n); if (!m) die("calloc: %s", strerror(errno)); return m; } static char * xstrdup(const char *str) { char *s; if (!(s = strdup(str))) die("strdup: %s", 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 typestr[TXT]; case '1': return typestr[DIR]; case '2': return typestr[CSO]; case '3': return typestr[ERR]; case '4': return typestr[MAC]; case '5': return typestr[DOS]; case '6': return typestr[UUE]; case '7': return typestr[IND]; case '8': return typestr[TLN]; case '9': return typestr[BIN]; case '+': return typestr[MIR]; case 'T': return typestr[IBM]; case 'g': return typestr[GIF]; case 'I': return typestr[IMG]; case 'h': return typestr[URL]; case 'i': return typestr[INF]; default: /* "Characters '0' through 'Z' are reserved." (ASCII) */ if (t >= '0' && t <= 'Z') return typestr[BRK]; else return typestr[UNK]; } } int itemuri(Item *item, char *buf, size_t bsz) { int n; switch (item->type) { case '8': n = snprintf(buf, bsz, "telnet://%s@%s:%s", item->selector, item->host, item->port); break; case 'T': n = snprintf(buf, bsz, "tn3270://%s@%s:%s", item->selector, item->host, item->port); break; case 'h': n = snprintf(buf, bsz, "%s", item->selector + (strncmp(item->selector, "URL:", 4) ? 0 : 4)); break; default: n = snprintf(buf, bsz, "gopher://%s", item->host); if (n < bsz-1 && strcmp(item->port, "70")) n += snprintf(buf+n, bsz-n, ":%s", item->port); if (n < bsz-1) { n += snprintf(buf+n, bsz-n, "/%c%s", item->type, item->selector); } if (n < bsz-1 && item->type == '7' && item->tag) { n += snprintf(buf+n, bsz-n, "%%09%s", item->tag + strlen(item->selector)); } break; } return n; } 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) { struct sigaction sa; FILE *pagerin; int pid, wpid; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; sigaction(SIGWINCH, &sa, NULL); uicleanup(); switch (pid = fork()) { case -1: diag("Couldn't fork."); return; case 0: parent = 0; if (!(pagerin = popen("$PAGER", "w"))) _exit(1); fputs(item->raw, pagerin); exit(pclose(pagerin)); default: while ((wpid = wait(NULL)) >= 0 && wpid != pid) ; } uisetup(); sa.sa_handler = uisigwinch; sigaction(SIGWINCH, &sa, NULL); uisigwinch(SIGWINCH); /* force redraw */ } static char * pickfield(char **raw, const char *sep) { char c, *r, *f = *raw; for (r = *raw; (c = *r) && !strchr(sep, c); ++r) { if (c == '\n') goto skipsep; } *r++ = '\0'; skipsep: *raw = r; return f; } static char * invaliditem(char *raw) { char c; int tabs; for (tabs = 0; (c = *raw) && c != '\n'; ++raw) { if (c == '\t') ++tabs; } if (tabs < 3) { *raw++ = '\0'; return raw; } return NULL; } 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, "\t\r"); while (*raw[0] != '\n') ++*raw; *raw[0]++ = '\0'; } static Dir * molddiritem(char *raw) { Item *item, *items = NULL; char *nl, *p; Dir *dir; size_t i, n, nitems; for (nl = raw, nitems = 0; p = strchr(nl, '\n'); nl = p+1) ++nitems; if (!nitems) { diag("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) { item = &items[i]; molditem(item, &raw); if (item->type == '+') { for (n = i - 1; n < (size_t)-1; --n) { if (items[n].type != '+') { item->redtype = items[n].type; break; } } } } dir->items = items; dir->nitems = nitems; dir->printoff = dir->curline = 0; return dir; } static char * getrawitem(struct cnx *c) { char *raw, *buf; size_t bn, bs; ssize_t n; raw = buf = NULL; bn = bs = n = 0; do { bs -= n; buf += n; if (buf - raw >= 5) { if (strncmp(buf-5, "\r\n.\r\n", 5) == 0) { buf[-3] = '\0'; break; } } else if (buf - raw == 3 && strncmp(buf-3, ".\r\n", 3)) { buf[-3] = '\0'; break; } if (bs < 1) { raw = xreallocarray(raw, ++bn, BUFSIZ); buf = raw + (bn-1) * BUFSIZ; bs = BUFSIZ; } } while ((n = ioread(c, buf, bs)) > 0); *buf = '\0'; if (n == -1) { diag("Can't read socket: %s", strerror(errno)); clear(&raw); } return raw; } static int sendselector(struct cnx *c, 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 = iowrite(c, p, ln)) > 0) { ln -= n; p += n; }; free(msg); if (n == -1) diag("Can't send message: %s", strerror(errno)); return n; } static int connectto(const char *host, const char *port, struct cnx *c) { sigset_t set, oset; static const struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, }; struct addrinfo *addrs, *ai; int r, err; sigemptyset(&set); sigaddset(&set, SIGWINCH); sigprocmask(SIG_BLOCK, &set, &oset); if (r = getaddrinfo(host, port, &hints, &addrs)) { diag("Can't resolve hostname \"%s\": %s", host, gai_strerror(r)); goto err; } r = -1; for (ai = addrs; ai && r == -1; ai = ai->ai_next) { do { if ((c->sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) { err = errno; break; } if ((r = ioconnect(c, ai, host)) < 0) { err = errno; ioclose(c); } /* retry on cleartext */ } while (r == -2); } freeaddrinfo(addrs); if (r == -1) ioconnerr(c, host, port, err); err: sigprocmask(SIG_SETMASK, &oset, NULL); return r; } static int download(Item *item, int dest) { char buf[BUFSIZ]; struct cnx c; ssize_t r, w; if (item->tag == NULL) { if (connectto(item->host, item->port, &c) < 0 || sendselector(&c, item->selector) == -1) return 0; } else { if ((c.sock = open(item->tag, O_RDONLY)) == -1) { printf("Can't open source file %s: %s", item->tag, strerror(errno)); errno = 0; return 0; } c.tls = NULL; } w = 0; while ((r = ioread(&c, buf, BUFSIZ)) > 0) { while ((w = write(dest, buf, r)) > 0) r -= w; } if (r == -1 || w == -1) { printf("Error downloading file %s: %s", item->selector, strerror(errno)); errno = 0; } close(dest); ioclose(&c); 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) == -1) { clear(&item->tag); } else if (!strcmp(tag, path)) { goto cleanup; } } if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { diag("Can't open destination file %s: %s", path, strerror(errno)); errno = 0; goto cleanup; } if (!download(item, dest)) goto cleanup; if (item->tag) goto cleanup; item->tag = path; return; cleanup: free(path); return; } static int fetchitem(Item *item) { struct cnx c; char *raw; if (connectto(item->host, item->port, &c) < 0 || sendselector(&c, item->selector) == -1) return 0; raw = getrawitem(&c); ioclose(&c); if (raw == NULL || !*raw) { diag("Empty response from server"); clear(&raw); } return ((item->raw = raw) != NULL); } static void pipeuri(char *cmd, char *msg, char *uri) { FILE *sel; if ((sel = popen(cmd, "w")) == NULL) { diag("URI not %s\n", msg); return; } fputs(uri, sel); pclose(sel); diag("%s \"%s\"", msg, uri); } static void execuri(char *cmd, char *msg, char *uri) { switch (fork()) { case -1: diag("Couldn't fork."); return; case 0: parent = 0; dup2(devnullfd, 1); dup2(devnullfd, 2); if (execlp(cmd, cmd, uri, NULL) == -1) _exit(1); default: if (modalplumber) { while (waitpid(-1, NULL, 0) != -1) ; } } diag("%s \"%s\"", msg, uri); } static void plumbitem(Item *item) { char *file, *path, *tag; mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP; int 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) == -1) { clear(&item->tag); tag = NULL; } plumbitem = path[0] ? 0 : 1; if (!path[0]) { clear(&path); if (!tag) { if (asprintf(&path, "%s/%s", tmpdir, file) == -1) die("Can't generate tmpdir path: %s/%s: %s", tmpdir, file, strerror(errno)); } } if (path && (!tag || strcmp(tag, path))) { if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) == -1) { diag("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) execuri(plumber, "Plumbed", item->tag); return; cleanup: free(path); return; } void yankitem(Item *item) { itemuri(item, intbuf, sizeof(intbuf)); pipeuri(yanker, "Yanked", intbuf); } static int dig(Item *entry, Item *item) { char *plumburi = NULL; int t; if (item->raw) /* already in cache */ return item->type; if (!item->entry) item->entry = entry ? entry : item; t = item->redtype ? item->redtype : item->type; switch (t) { case 'h': /* fallthrough */ if (!strncmp(item->selector, "URL:", 4)) { execuri(plumber, "Plumbed", item->selector+4); return 0; } case '0': if (!fetchitem(item)) return 0; break; case '1': 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:%s", item->selector, item->selector ? "@" : "", item->host, item->port) == -1) return 0; execuri(plumber, "Plumbed", plumburi); free(plumburi); return 0; case 'T': if (asprintf(&plumburi, "tn3270://%s%s%s:%s", item->selector, item->selector ? "@" : "", item->host, item->port) == -1) return 0; execuri(plumburi, "Plumbed", plumburi); free(plumburi); return 0; default: if (t >= '0' && t <= 'Z') { diag("Type %c (%s) not supported", t, typedisplay(t)); return 0; } case 'g': case 'I': plumbitem(item); case 'i': 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); if (!item->dat) { selector = item->selector; item->selector = item->tag = sel; dig(entry, item); item->selector = selector; } return (item->dat != NULL); } static void printout(Item *hole) { char t = 0; if (!hole) return; switch (hole->redtype ? hole->redtype : (t = hole->type)) { case '0': if (dig(hole, hole)) fputs(hole->raw, stdout); return; case '1': case '7': if (dig(hole, hole)) printdir(hole); return; default: if (t >= '0' && t <= 'Z') { diag("Type %c (%s) not supported", t, typedisplay(t)); return; } case '4': case '5': case '6': case '9': case 'g': case 'I': download(hole, 1); case '2': case '3': case '8': case 'T': return; } } static void delve(Item *hole) { Item *entry = NULL; while (hole) { switch (hole->redtype ? hole->redtype : 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 0: diag("Couldn't get %s:%s/%c%s", hole->host, hole->port, hole->type, hole->selector); break; case '4': case '5': case '6': /* TODO decode? */ case '8': case '9': case 'g': case 'I': case 'T': default: dig(entry, hole); break; } 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; host = ioparseurl(url); 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; if (entry->type == '7') { if (p = strstr(gopherpath, "%09")) { memmove(p+1, p+3, strlen(p+3)+1); *p = '\t'; } if (p || (p = strchr(gopherpath, '\t'))) { asprintf(&entry->tag, "%s", gopherpath); *p = '\0'; } } 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 sighandler(int signo) { exit(128 + signo); } 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)) == -1) die("open: /dev/tty: %s", strerror(errno)); if (dup2(fd, 0) == -1) die("dup2: /dev/tty, stdin: %s", strerror(errno)); close(fd); if ((devnullfd = open("/dev/null", O_WRONLY)) == -1) die("open: /dev/null: %s", strerror(errno)); sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = sighandler; sigaction(SIGINT, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGTERM, &sa, NULL); if (!mkdtemp(tmpdir)) die("mkdir: %s: %s", tmpdir, strerror(errno)); if (interactive = isatty(1)) { uisetup(); diag = uistatus; sa.sa_handler = uisigwinch; sigaction(SIGWINCH, &sa, NULL); } else { diag = stddiag; } } 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.05/ui_ti.c000066400000000000000000000310071415071107100136230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "common.h" #include "config.h" #define C(c) #c #define S(c) C(c) /* ncurses doesn't define those in term.h, where they're used */ #ifndef OK #define OK (0) #endif #ifndef ERR #define ERR (-1) #endif static char bufout[256]; static struct termios tsave; static struct termios tsacc; static Item *curentry; static int termset = ERR; void uisetup(void) { tcgetattr(0, &tsave); tsacc = tsave; tsacc.c_lflag &= ~(ECHO|ICANON); tsacc.c_cc[VMIN] = 1; tsacc.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &tsacc); if (termset != OK) /* setupterm call exits on error */ termset = setupterm(NULL, 1, NULL); putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fflush(stdout); } void uicleanup(void) { tcsetattr(0, TCSANOW, &tsave); if (termset != OK) return; putp(tparm(change_scroll_region, 0, lines-1, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clear_screen, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fflush(stdout); } char * uiprompt(char *fmt, ...) { va_list ap; char *input = NULL; size_t n; ssize_t r; putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); va_start(ap, fmt); vsnprintf(bufout, sizeof(bufout), fmt, ap); va_end(ap); n = mbsprint(bufout, columns); putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, lines-1, n, 0, 0, 0, 0, 0, 0, 0)); 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, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fflush(stdout); if (r == -1) { clearerr(stdin); clear(&input); } else if (input[r - 1] == '\n') { input[--r] = '\0'; } return input; } static void printitem(Item *item) { snprintf(bufout, sizeof(bufout), "%s %s", typedisplay(item->type), item->username); 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" S(_key_entrydown) ": move to next link.\n" "Up, " S(_key_lnup) ": move one line up.\n" S(_key_entryup) ": move to previous link.\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_searchnext) ": search string forward.\n" S(_key_searchprev) ": search string backward.\n" S(_key_cururi) ": print page URI.\n" S(_key_seluri) ": print item URI.\n" S(_key_yankcur) ": yank page URI to external program.\n" S(_key_yanksel) ": yank item URI to external program.\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, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); va_start(ap, fmt); n = vsnprintf(bufout, sizeof(bufout), fmt, ap); va_end(ap); if (n < sizeof(bufout)-1) { snprintf(bufout+n, sizeof(bufout)-n, " [Press a key to continue \xe2\x98\x83]"); } mbsprint(bufout, columns); putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fflush(stdout); getchar(); } static void displaystatus(Item *item) { Dir *dir = item->dat; char *fmt; size_t nitems = dir ? dir->nitems : 0; unsigned long long printoff = dir ? dir->printoff : 0; putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fmt = (strcmp(item->port, "70") && strcmp(item->port, "gopher")) ? "%1$3lld%%| %2$s:%5$s/%3$c%4$s" : "%3lld%%| %s/%c%s"; snprintf(bufout, sizeof(bufout), fmt, (printoff + lines-1 >= nitems) ? 100 : (printoff + lines-1) * 100 / nitems, item->host, item->type, item->selector, item->port); mbsprint(bufout, columns); putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); fflush(stdout); } static void displayuri(Item *item) { if (item->type == 0 || item->type == 'i') return; putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, lines-1, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); itemuri(item, bufout, sizeof(bufout)); mbsprint(bufout, columns); putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(clr_eol, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 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, 0, 0, 0, 0, 0, 0, 0, 0, 0)); displaystatus(entry); if (!(dir = entry->dat)) return; putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 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, 0, 0, 0, 0, 0, 0, 0, 0, 0)); if (i == curln) { putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } printitem(&items[i]); putp(tparm(column_address, 0, 0, 0, 0, 0, 0, 0, 0, 0)); if (i == curln) putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 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, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, plines, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(scroll_forward, 0, 0, 0, 0, 0, 0, 0, 0, 0)); printitem(&dir->items[offline]); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); dir->printoff += l; } } else { offline = dir->printoff + l; if (curline - offline <= plines / 2 && offline >= 0) { putp(tparm(save_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(cursor_address, 0, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(scroll_reverse, 0, 0, 0, 0, 0, 0, 0, 0, 0)); printitem(&dir->items[offline]); putchar('\n'); putp(tparm(restore_cursor, 0, 0, 0, 0, 0, 0, 0, 0, 0)); dir->printoff += l; } } putp(tparm(cursor_address, curline - dir->printoff, 0, 0, 0, 0, 0, 0, 0, 0)); putp(tparm(enter_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); printitem(&dir->items[curline]); putp(tparm(exit_standout_mode, 0, 0, 0, 0, 0, 0, 0, 0, 0)); 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; } static 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 (strcasestr(dir->items[i].username, searchstr)) { jumptoline(entry, i, 1); break; } } } else { for (i = dir->curline - 1; i > -1; --i) { if (strcasestr(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 < 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: free(searchstr); if (!((searchstr = uiprompt("Search for: ")) && searchstr[0])) { clear(&searchstr); continue; } case _key_searchnext: searchinline(searchstr, entry, +1); continue; case _key_searchprev: searchinline(searchstr, entry, -1); continue; case 0x04: case _key_quit: quit: return NULL; case _key_fetch: if (entry->raw) continue; return entry; case _key_cururi: if (dir) displayuri(entry); continue; case _key_seluri: if (dir) displayuri(&dir->items[dir->curline]); continue; case _key_yankcur: if (dir) yankitem(entry); continue; case _key_yanksel: if (dir) yankitem(&dir->items[dir->curline]); continue; case _key_help: /* FALLTHROUGH */ return help(entry); default: continue; } } } void uisigwinch(int signal) { Dir *dir; if (termset == OK) del_curterm(cur_term); termset = setupterm(NULL, 1, NULL); putp(tparm(change_scroll_region, 0, lines-2, 0, 0, 0, 0, 0, 0, 0)); if (!curentry || !(dir = curentry->dat)) return; if (dir->curline - dir->printoff > lines-2) dir->printoff = dir->curline - (lines-2); uidisplay(curentry); } sacc-1.05/ui_txt.c000066400000000000000000000132021415071107100140230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "common.h" static char bufout[256]; static Item *curentry; static int lines, columns; static char cmd; static void viewsize(int *ln, int *col) { struct winsize ws; if (ioctl(1, TIOCGWINSZ, &ws) == -1) { 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; } static void help(void) { puts("Commands:\n" "#: browse item number #.\n" "U: print page URI.\n" "u#: print item number # 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" "Y: yank page URI.\n" "y#: yank item number # URI.\n" "/str: search for string \"str\"\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) { snprintf(bufout+n, sizeof(bufout)-n, " [Press Enter to continue \xe2\x98\x83]"); } 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%% %s/%c%s [%c]: "; snprintf(bufout, sizeof(bufout), fmt, (printoff + lines-1 >= nitems) ? 100 : (printoff + lines) * 100 / nitems, item->host, item->type, item->selector, c, item->port); mbsprint(bufout, columns); } char * uiprompt(char *fmt, ...) { va_list ap; char *input = NULL; size_t n = 0; ssize_t r; va_start(ap, fmt); vsnprintf(bufout, sizeof(bufout), fmt, ap); va_end(ap); mbsprint(bufout, columns); fflush(stdout); if ((r = getline(&input, &n, stdin)) == -1) { 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) { snprintf(bufout, sizeof(bufout), "%*zu %s %s", nd, i+1,typedisplay(items[i].type), items[i].username); mbsprint(bufout, columns); putchar('\n'); } fflush(stdout); } static void printuri(Item *item, size_t i) { if (!item || item->type == 0 || item->type == 'i') return; itemuri(item, bufout, sizeof(bufout)); mbsprint(bufout, columns); putchar('\n'); } static 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 (strcasestr(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((unsigned char)*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((unsigned char)*(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': printuri(entry, 0); continue; case 'u': if (item > 0 && item <= nitems) printuri(&dir->items[item-1], item); continue; case 'Y': yankitem(entry); continue; case 'y': if (item > 0 && item <= nitems) yankitem(&dir->items[item-1]); 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(); if (!curentry) return; putchar('\n'); uidisplay(curentry); printstatus(curentry, cmd); fflush(stdout); }