zygo

ncurses gopher client
Log | Files | Refs

commit f4e2bfb9a68e07b798d9d6d78c53d5b98fbcde6a
parent de10ba07b7b39bb3e042d064f02105103cfb4627
Author: hhvn <dev@hhvn.uk>
Date:   Sun, 16 Jan 2022 16:32:29 +0000

Makefile zygo.* config.h: basic ui

Diffstat:
MMakefile | 2++
Aconfig.h | 28++++++++++++++++++++++++++++
Mzygo.c | 285++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mzygo.h | 39+++++++++++++++++++++++++++++----------
4 files changed, 321 insertions(+), 33 deletions(-)

diff --git a/Makefile b/Makefile @@ -17,6 +17,7 @@ BIN = zygo SRC += zygo.c OBJ = $(SRC:.c=.o) +LDFLAGS = -lncurses include config.mk @@ -24,6 +25,7 @@ $(BIN): $(OBJ) $(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJ) $(OBJ): Makefile config.mk zygo.h +zygo.o: config.h .c.o: $(CC) $(CFLAGS) -c $< -o $@ diff --git a/config.h b/config.h @@ -0,0 +1,28 @@ +static char *starturi = "gopher://hhvn.uk"; + +static short bar_pair[2] = {-1, 0}; +static short uri_pair[2] = {0, 7}; +static short cmd_pair[2] = {7, 0}; +static short arg_pair[2] = {-1, 0}; +static short err_pair[2] = {0, 88}; + +static Scheme scheme[] = { + {'i', " ", -1 }, + {'0', "Text", -1 }, + {'1', "Dir ", -1 }, + {'2', "CCSO", -1 }, + {'4', "Bin ", -1 }, + {'5', "Bin ", -1 }, + {'9', "Bin ", -1 }, + {'7', "Srch", -1 }, + {'8', "Teln", -1 }, + {'T', "Teln", -1 }, + {'+', "Alt ", -1 }, + {'I', "Img ", -1 }, + {'g', "Img ", -1 }, + {'h', "HTML", -1 }, + {'s', "Snd ", -1 }, + {'d', "Doc ", -1 }, + {'3', "ERR ", -1 }, + {'\0', NULL, -1, 0 }, +}; diff --git a/zygo.c b/zygo.c @@ -17,12 +17,16 @@ * */ +#include <ncurses.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> +#include <libgen.h> #include <assert.h> +#include <locale.h> #include <stdio.h> #include "zygo.h" +#include "config.h" List *history = NULL; List *page = NULL; @@ -32,14 +36,13 @@ int config[] = { [CONF_TLS_VERIFY] = 1, }; -void -error(char *format, ...) { - va_list ap; - - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); -} +struct { + int scroll; + int wantinput; + wint_t input[BUFLEN]; + char cmd; + char *arg; +} ui = {0, 0}; /* * Memory functions @@ -104,11 +107,12 @@ elem_free(Elem *e) { } Elem * -elem_create(char type, char *desc, char *selector, char *server, char *port) { +elem_create(int tls, char type, char *desc, char *selector, char *server, char *port) { Elem *ret; #define DUP(str) str ? estrdup(str) : NULL ret = emalloc(sizeof(Elem)); + ret->tls = tls; ret->type = type; ret->desc = DUP(desc); ret->selector = DUP(selector); @@ -122,7 +126,7 @@ elem_create(char type, char *desc, char *selector, char *server, char *port) { Elem * elem_dup(Elem *e) { if (e) - return elem_create(e->type, e->desc, e->selector, e->server, e->port); + return elem_create(e->tls, e->type, e->desc, e->selector, e->server, e->port); else return NULL; } @@ -151,8 +155,8 @@ elemtouri(Elem *e) { break; } - assert(e->server); - assert(e->port); + zygo_assert(e->server); + zygo_assert(e->port); estrappend(&ret, e->server); if (strcmp(e->port, "70") != 0) { @@ -180,8 +184,9 @@ uritoelem(const char *uri) { int seg; ret = emalloc(sizeof(Elem)); - ret->tls = ret->type = 0; - ret->desc = ret->selector = ret->server = ret->port; + ret->tls = 0; + ret->type = '1'; + ret->desc = ret->selector = ret->server = ret->port = NULL; if (strncmp(tmp, "gopher://", strlen("gopher://")) == 0) { tmp += strlen("gopher://"); @@ -231,8 +236,11 @@ uritoelem(const char *uri) { } } - ret->type = ret->type ? ret->type : '1'; - ret->server = ret->server ? ret->server : estrdup(tmp); + if (!ret->server) { + ret->server = estrdup(tmp); + tmp += strlen(tmp); + } + ret->port = ret->port ? ret->port : estrdup("70"); ret->selector = ret->selector ? ret->selector : estrdup(tmp); @@ -271,9 +279,11 @@ gophertoelem(Elem *from, const char *line) { } ret->port = estrdup(tmp); + free(dup); return ret; invalid: + free(dup); free(ret->desc); free(ret->selector); free(ret->server); @@ -293,7 +303,7 @@ void list_free(List **l) { size_t i; - assert(l); + zygo_assert(l); if ((*l)) { for (i = 0; i < (*l)->len; i++) free(*((*l)->elems + i)); @@ -305,11 +315,12 @@ list_free(List **l) { void list_append(List **l, Elem *e) { - assert(l); + zygo_assert(l); if (!(*l)) { (*l) = emalloc(sizeof(List)); (*l)->len = 0; + (*l)->elems = NULL; } if (!(*l)->elems) { @@ -359,8 +370,6 @@ go(Elem *e) { char line[BUFLEN]; Elem *elem; - list_free(&page); - if (e->type != '1' && e->type != '7' && e->type != '+') { /* TODO: call plumber */ return -1; @@ -372,15 +381,245 @@ go(Elem *e) { net_write(e->selector, strlen(e->selector)); net_write("\r\n", 2); + list_free(&page); while (readline(line, sizeof(line))) { elem = gophertoelem(e, line); list_append(&page, elem); elem_free(elem); } + + elem_free(current); + current = elem_dup(e); + list_append(&history, current); + ui.scroll = 0; + return 0; +} + +/* + * UI functions + */ +void +error(char *format, ...) { + va_list ap; + + move(LINES - 1, 0); + clrtoeol(); + attron(COLOR_PAIR(PAIR_ERR)); + addstr(" error: "); + + va_start(ap, format); + vw_printw(stdscr, format, ap); + va_end(ap); + + addstr(" "); + getch(); +} + +Scheme * +getscheme(char type) { + int i; + + for (i = 0; ; i++) + if (scheme[i].type == type || scheme[i].type == '\0') + return &scheme[i]; } int -main(void) { - Elem *s = uritoelem("gophers://hhvn.uk/1/"); - go(s); +draw_line(Elem *e, int maxlines) { + int lc, cc; + + printw("%s | ", getscheme(e->type)->name); + attron(COLOR_PAIR(getscheme(e->type)->pair)); + printw("%s\n", e->desc); + attroff(A_COLOR); + return 1; +} + +void +draw_page(void) { + int y = 0, i; + + move(0, 0); + zygo_assert(ui.scroll < list_len(&page)); + for (i = ui.scroll; i < list_len(&page) - 1 && y < LINES - 1; i++) + y += draw_line(list_get(&page, i), 1); + for (; y < LINES - 1; y++) { + move(y, 0); + clrtoeol(); + } +} + +void +draw_bar(void) { + int savey, savex, x; + + move(LINES - 1, 0); + clrtoeol(); + attron(COLOR_PAIR(PAIR_URI)); + printw(" %s ", elemtouri(current)); + attron(COLOR_PAIR(PAIR_BAR)); + printw(" "); + if (ui.wantinput) { + curs_set(1); + attron(COLOR_PAIR(PAIR_CMD)); + printw("%c", ui.cmd); + attron(COLOR_PAIR(PAIR_ARG)); + printw("%s", ui.arg); + } else curs_set(0); + + attron(COLOR_PAIR(PAIR_BAR)); + getyx(stdscr, savey, savex); + for (x = savex; x < COLS; x++) + addch(' '); + move(savey, savex); +} + +void +syncinput(void) { + int len; + free(ui.arg); + len = wcstombs(NULL, ui.input, 0) + 1; + ui.arg = emalloc(len); + wcstombs(ui.arg, ui.input, len); +} + +/* + * Main loop + */ +void +run(void) { + wint_t c; + int ret; + size_t il; + Elem *e; + + draw_page(); + draw_bar(); + + /* get_wch does refresh() for us */ + while ((ret = get_wch(&c)) != ERR) { + if (c == KEY_RESIZE) { + draw_page(); + draw_bar(); + } else if (ui.wantinput) { + if (c == 27 /* escape */) { + ui.wantinput = 0; + } else if (c == '\n') { + switch (ui.cmd) { + case ':': + e = uritoelem(ui.arg); + go(e); + elem_free(e); + break; + } + ui.wantinput = 0; + draw_page(); + } else if (c == KEY_BACKSPACE || c == 127) { + if (il == 0) { + ui.wantinput = 0; + } else { + ui.input[il--] = '\0'; + syncinput(); + } + } else if (c >= 32 && c < KEY_CODE_YES) { + ui.input[il++] = c; + ui.input[il] = '\0'; + syncinput(); + } + draw_bar(); + } else { + switch (c) { + case KEY_DOWN: + case 'j': + if (list_len(&page) - ui.scroll > LINES) + ui.scroll++; + draw_page(); + break; + case 4: /* ^D */ + case 6: /* ^F */ + if (list_len(&page) - ui.scroll > ((int)LINES * 1.5)) + ui.scroll += ((int)LINES / 2); + else if (list_len(&page) > LINES) + ui.scroll = list_len(&page) - LINES; + draw_page(); + break; + case KEY_UP: + case 'k': + if (ui.scroll > 0) + ui.scroll--; + draw_page(); + break; + case 21: /* ^U */ + case 2: /* ^B */ + ui.scroll -= ((int)LINES / 2); + if (ui.scroll < 0) + ui.scroll = 0; + draw_page(); + break; + case 3: /* ^C */ + case 'q': + endwin(); + exit(EXIT_SUCCESS); + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* TODO: numbered link handling */ + break; + case ':': + ui.cmd = (char)c; + ui.wantinput = 1; + ui.input[0] = '\0'; + ui.arg = estrdup(""); + il = 0; + draw_bar(); + break; + default: + /* TODO: some sort of indicator */ + break; + } + } + } +} + +int +main(int argc, char *argv[]) { + Elem *target; + char *targeturi; + int i; + + switch (argc) { + case 2: + targeturi = argv[1]; + break; + case 1: + targeturi = starturi; + break; + default: + fprintf(stderr, "usage: %s [uri]\n", basename(argv[0])); + exit(EXIT_FAILURE); + } + + target = uritoelem(targeturi); + go(target); + + setlocale(LC_ALL, ""); + initscr(); + noecho(); + start_color(); + use_default_colors(); + keypad(stdscr, TRUE); + set_escdelay(10); + + init_pair(PAIR_BAR, bar_pair[0], bar_pair[1]); + init_pair(PAIR_URI, uri_pair[0], uri_pair[1]); + init_pair(PAIR_CMD, cmd_pair[0], cmd_pair[1]); + init_pair(PAIR_ARG, arg_pair[0], arg_pair[1]); + init_pair(PAIR_ERR, err_pair[0], err_pair[1]); + for (i = 0; scheme[i].type; i++) { + scheme[i].pair = i + PAIR_SCHEME; + init_pair(scheme[i].pair, scheme[i].fg, -1); + } + + run(); + + endwin(); } diff --git a/zygo.h b/zygo.h @@ -18,6 +18,7 @@ */ #define BUFLEN 2048 +#define zygo_assert(expr) (expr ? ((void)0) : (endwin(), assert(expr))) typedef struct Elem Elem; struct Elem { @@ -35,17 +36,32 @@ struct List { size_t len; }; +typedef struct Scheme Scheme; +struct Scheme { + char type; + char *name; + short fg; + short pair; +}; + enum { CONF_TLS_VERIFY = 'k', }; +enum { + PAIR_BAR = 1, + PAIR_URI = 2, + PAIR_CMD = 3, + PAIR_ARG = 4, + PAIR_ERR = 5, + PAIR_SCHEME = 6, +}; + extern List *history; extern List *page; extern Elem *current; extern int config[]; -void error(char *format, ...); - /* Memory functions */ void *emalloc(size_t size); void *erealloc(void *ptr, size_t size); @@ -54,7 +70,7 @@ void estrappend(char **s1, const char *s2); /* Elem functions */ void elem_free(Elem *e); -Elem *elem_create(char type, char *desc, char *selector, char *server, char *port); +Elem *elem_create(int tls, char type, char *desc, char *selector, char *server, char *port); Elem *elem_dup(Elem *e); Elem *uritoelem(const char *uri); Elem *gophertoelem(Elem *from, const char *line); @@ -77,10 +93,13 @@ int net_read(void *buf, size_t count); int net_write(void *buf, size_t count); int net_close(void); -#ifdef DEBUG -void *elem_put(Elem *e); /* debug */ -void *list_put(List **l); -#else -#define elem_put(a) ((void)0) -#define list_put(a) ((void)0) -#endif /* DEBUG */ +/* UI functions */ +void error(char *format, ...); +Scheme *getscheme(char type); +int draw_line(Elem *e, int maxlines); +void draw_page(void); +void draw_bar(void); +void syncinput(void); + +/* Main loop */ +void run(void);