initial version - sob - simple output bar
 (HTM) git clone git://git.codemadness.org/sob
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 99d263504572720db6a3a78f1d1b7e0b50060ca7
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Wed,  1 Oct 2014 22:45:24 +0000
       
       initial version
       
       Diffstat:
         A .gitignore                          |       3 +++
         A LICENSE                             |      21 +++++++++++++++++++++
         A Makefile                            |      48 +++++++++++++++++++++++++++++++
         A README                              |      25 +++++++++++++++++++++++++
         A TODO                                |      20 ++++++++++++++++++++
         A arg.h                               |      63 +++++++++++++++++++++++++++++++
         A config.def.h                        |      43 ++++++++++++++++++++++++++++++
         A config.mk                           |      25 +++++++++++++++++++++++++
         A scripts/complete_word               |      21 +++++++++++++++++++++
         A scripts/history                     |      18 ++++++++++++++++++
         A sob.1                               |      33 +++++++++++++++++++++++++++++++
         A sob.c                               |     593 +++++++++++++++++++++++++++++++
         A strlcpy.c                           |      47 +++++++++++++++++++++++++++++++
         A util.h                              |       5 +++++
       
       14 files changed, 965 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -0,0 +1,3 @@
       +sob
       +*.o
       +core
 (DIR) diff --git a/LICENSE b/LICENSE
       @@ -0,0 +1,21 @@
       +MIT/X Consortium License
       +
       +(c) 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
       +
       +Permission is hereby granted, free of charge, to any person obtaining a
       +copy of this software and associated documentation files (the "Software"),
       +to deal in the Software without restriction, including without limitation
       +the rights to use, copy, modify, merge, publish, distribute, sublicense,
       +and/or sell copies of the Software, and to permit persons to whom the
       +Software is furnished to do so, subject to the following conditions:
       +
       +The above copyright notice and this permission notice shall be included in
       +all copies or substantial portions of the Software.
       +
       +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
       +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
       +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
       +DEALINGS IN THE SOFTWARE.
 (DIR) diff --git a/Makefile b/Makefile
       @@ -0,0 +1,48 @@
       +include config.mk
       +
       +SRC = sob.c strlcpy.c
       +HDR = arg.h config.def.h util.h
       +OBJ = ${SRC:.c=.o}
       +
       +all: options sob
       +
       +options:
       +        @echo sob build options:
       +        @echo "CFLAGS   = ${CFLAGS}"
       +        @echo "LDFLAGS  = ${LDFLAGS}"
       +        @echo "CC       = ${CC}"
       +
       +.c.o:
       +        @echo CC $<
       +        @${CC} -c ${CFLAGS} $<
       +
       +${OBJ}: config.mk config.h
       +
       +config.h:
       +        @echo creating $@ from config.def.h
       +        @cp config.def.h $@
       +
       +sob: ${OBJ}
       +        @echo CC -o $@
       +        @${CC} -o $@ ${OBJ} ${LDFLAGS}
       +
       +clean:
       +        @echo cleaning
       +        @rm -f sob ${OBJ}
       +
       +install: all
       +        @echo installing executable file to ${DESTDIR}${PREFIX}/bin
       +        @mkdir -p ${DESTDIR}${PREFIX}/bin
       +        @cp -f sob ${DESTDIR}${PREFIX}/bin
       +        @chmod 755 ${DESTDIR}${PREFIX}/bin/sob
       +        @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
       +        @mkdir -p ${DESTDIR}${MANPREFIX}/man1
       +        @sed "s/VERSION/${VERSION}/g" < sob.1 > ${DESTDIR}${MANPREFIX}/man1/sob.1
       +
       +uninstall:
       +        @echo removing executable file from ${DESTDIR}${PREFIX}/bin
       +        @rm -f ${DESTDIR}${PREFIX}/bin/sob
       +        @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
       +        @rm -f ${DESTDIR}${MANPREFIX}/man1/sob.1
       +
       +.PHONY: all options clean dist install uninstall
 (DIR) diff --git a/README b/README
       @@ -0,0 +1,25 @@
       +Simple output bar
       +=================
       +
       +
       +Dependencies
       +------------
       +- Ncurses (at the moment, alternatives will be investigated).
       +- libc
       +
       +
       +Features
       +--------
       +- Custom prompt.
       +- Easy to write scripts to pipe input/output.
       +- Custom action on SIGWINCH (window resize).
       +- Familiar and customizable keybinds (in config.h).
       +- Example scripts for:
       +        - Word completion.
       +        - History
       +        - Yank line (xsel).
       +
       +
       +License
       +-------
       +See LICENSE file.
 (DIR) diff --git a/TODO b/TODO
       @@ -0,0 +1,20 @@
       +13:32 <@TLH> Evil_Bob: please support C-p for up in history C-n for down in history C-f for forward cursor C-b for backwards, C-a and 
       +             C-e, C-u, C-k (kill to rest of line), C-y, C-w
       +                         13:32 <@TLH> what else
       +                         13:32 <@TLH> C-j 
       +                         13:32 <@TLH> :P
       +                         13:33 <@TLH> C-m as an alias to C-j
       +
       +- test draw on wrapped lines.
       +
       +- line_yank doesn't work with xclip, but works with xsel...
       +
       +- optimize:
       +        reduce redraws (line_redraw and line_cursor_update).
       +
       +- selections / marks? (delete/pipe etc on selection)?
       +- cycle completions? (with tab for example).
       +- keybind to go to word next and previous (ctrl+arrow left/right).
       +- prompt callback? allow to update prompt?
       +
       +- try libedit, else just use ncurses.
 (DIR) diff --git a/arg.h b/arg.h
       @@ -0,0 +1,63 @@
       +/*
       + * Copy me if you can.
       + * by 20h
       + */
       +
       +#ifndef ARG_H__
       +#define ARG_H__
       +
       +extern char *argv0;
       +
       +/* use main(int argc, char *argv[]) */
       +#define ARGBEGIN        for (argv0 = *argv, argv++, argc--;\
       +                                        argv[0] && argv[0][1]\
       +                                        && argv[0][0] == '-';\
       +                                        argc--, argv++) {\
       +                                char argc_;\
       +                                char **argv_;\
       +                                int brk_;\
       +                                if (argv[0][1] == '-' && argv[0][2] == '\0') {\
       +                                        argv++;\
       +                                        argc--;\
       +                                        break;\
       +                                }\
       +                                for (brk_ = 0, argv[0]++, argv_ = argv;\
       +                                                argv[0][0] && !brk_;\
       +                                                argv[0]++) {\
       +                                        if (argv_ != argv)\
       +                                                break;\
       +                                        argc_ = argv[0][0];\
       +                                        switch (argc_)
       +
       +/* Handles obsolete -NUM syntax */
       +#define ARGNUM                                case '0':\
       +                                        case '1':\
       +                                        case '2':\
       +                                        case '3':\
       +                                        case '4':\
       +                                        case '5':\
       +                                        case '6':\
       +                                        case '7':\
       +                                        case '8':\
       +                                        case '9'
       +
       +#define ARGEND                        }\
       +                        }
       +
       +#define ARGC()                argc_
       +
       +#define ARGNUMF(base)        (brk_ = 1, estrtol(argv[0], (base)))
       +
       +#define EARGF(x)        ((argv[0][1] == '\0' && argv[1] == NULL)?\
       +                                ((x), abort(), (char *)0) :\
       +                                (brk_ = 1, (argv[0][1] != '\0')?\
       +                                        (&argv[0][1]) :\
       +                                        (argc--, argv++, argv[0])))
       +
       +#define ARGF()                ((argv[0][1] == '\0' && argv[1] == NULL)?\
       +                                (char *)0 :\
       +                                (brk_ = 1, (argv[0][1] != '\0')?\
       +                                        (&argv[0][1]) :\
       +                                        (argc--, argv++, argv[0])))
       +
       +#endif
 (DIR) diff --git a/config.def.h b/config.def.h
       @@ -0,0 +1,43 @@
       +static const char *prompt            = "> ";
       +static char id[256]                  = "";
       +static const char *completewordcmd[] = { "./scripts/complete_word", id, NULL };
       +static const char *historycmd[]      = { "./scripts/history", id, NULL };
       +static const char *yankcmd[]         = { "/bin/sh", "-c", "/bin/xsel -i -p", NULL };
       +static const char *resizecmd         = "tmux resize-pane -y 4 2> /dev/null";
       +
       +static struct keybind {
       +        int key;
       +        void (*func)(void);
       +} keybinds[] = {
       +        { CONTROL('T'),  line_delwordcursor },
       +        { CONTROL('A'),  line_cursor_begin },
       +        { CONTROL('E'),  line_cursor_end },
       +        { KEY_HOME,      line_cursor_begin },
       +        { KEY_END,       line_cursor_end },
       +        { CONTROL('B'),  line_cursor_prev },
       +        { KEY_LEFT,      line_cursor_prev },
       +        { CONTROL('F'),  line_cursor_next },
       +        { KEY_RIGHT,     line_cursor_next },
       +        { CONTROL('W'),  line_delwordback },
       +        { CONTROL('H'),  line_delcharback },
       +        { CONTROL('U'),  line_clear },
       +        { KEY_DL,        line_clear },
       +        { CONTROL('K'),  line_deltoend },
       +        { KEY_SDC,       line_delcharnext },
       +        { KEY_DC,        line_delcharnext },
       +        { KEY_BACKSPACE, line_delcharback },
       +        { CONTROL('M'),  line_newline },
       +        { CONTROL('J'),  line_newline },
       +        { '\r',          line_newline },
       +        { '\n',          line_newline },
       +        { KEY_ENTER,     line_newline },
       +        { CONTROL('Y'),  line_yank },
       +        { KEY_EXIT,      line_exit },
       +        { 0x04,          line_exit }, /* EOT */
       +        { KEY_EOL,       line_deltoend },
       +        { KEY_UP,        history_menu },
       +        { KEY_DOWN,      history_menu },
       +        { CONTROL('P'),  history_menu },
       +        { CONTROL('N'),  history_menu },
       +        { '\t',          complete_word },
       +};
 (DIR) diff --git a/config.mk b/config.mk
       @@ -0,0 +1,25 @@
       +VERSION = 0.1
       +
       +# Customize below to fit your system
       +
       +# paths
       +PREFIX = /usr/local
       +MANPREFIX = ${PREFIX}/share/man
       +
       +# includes and libs
       +INCS = -I. -I/usr/include
       +LIBS = -L/usr/lib -lc -lncurses
       +
       +# flags
       +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_POSIX_C_SOURCE=200809 -D_BSD_SOURCE
       +
       +# debug
       +#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
       +#LDFLAGS = ${LIBS}
       +
       +# release
       +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
       +LDFLAGS = -s ${LIBS}
       +
       +# compiler and linker
       +CC = cc
 (DIR) diff --git a/scripts/complete_word b/scripts/complete_word
       @@ -0,0 +1,21 @@
       +#!/bin/sh
       +
       +grepword() {
       +        grep -E "^$1" < out.log
       +}
       +
       +id=""
       +test x"$1" != x"" && id="$1"
       +read -r word
       +
       +#if test x"$DISPLAY" = x""; then
       +#        line=$(grepword "$word" | sort | uniq | slmenu -l 20)
       +#else
       +#        line=$(grepword "$word" | sort | uniq | dmenu -l 20)
       +#fi
       +
       +line=$(grepword "$word" | sort | uniq | dmenu -l 20)
       +
       +if test x"$line" != x""; then
       +        printf '%s\n' "$line"
       +fi
 (DIR) diff --git a/scripts/history b/scripts/history
       @@ -0,0 +1,18 @@
       +#!/bin/sh
       +id=""
       +test x"$1" != x"" && id="$1"
       +file="out.log"
       +
       +test -f "$file" || exit 1
       +
       +#if test x"$DISPLAY" = x""; then
       +#        line=$(tail -n 100 "$file" | slmenu -l 20)
       +#else
       +#        line=$(tail -n 100 "$file" | dmenu -l 20)
       +#fi
       +
       +line=$(tail -n 100 "$file" | dmenu -l 20)
       +
       +if test x"$line" != x""; then
       +        printf '%s\n' "$line"
       +fi
 (DIR) diff --git a/sob.1 b/sob.1
       @@ -0,0 +1,33 @@
       +.TH SOB 1 sob\-VERSION
       +.SH NAME
       +sob \- simple output bar
       +.SH SYNOPSIS
       +.B sob
       +.RB < \-f
       +.IR outfile >
       +.RB [ \-l
       +.IR line ]
       +.RB [ \-p
       +.IR prompt ]
       +.RB [ \-r
       +.IR resizecmd ]
       +.SH DESCRIPTION
       +sob is a simple line editor.
       +.SH OPTIONS
       +.TP
       +.B \-f " outfile"
       +output file.
       +.TP
       +.B \-i " id"
       +extra parameter passed to program.
       +.TP
       +.B \-l " line"
       +initial input on line.
       +.TP
       +.B \-p " prompt"
       +prompt string.
       +.TP
       +.B \-r " resizecmd"
       +command to run on SIGWINCH (ncurses KEY_RESIZE).
       +.SH BUGS
       +Please report them!
 (DIR) diff --git a/sob.c b/sob.c
       @@ -0,0 +1,593 @@
       +#include <ctype.h>
       +#include <errno.h>
       +#include <fcntl.h>
       +#include <limits.h>
       +#include <locale.h>
       +#include <signal.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/select.h>
       +#include <unistd.h>
       +
       +#include <ncurses.h>
       +
       +#include "arg.h"
       +char *argv0;
       +
       +#include "util.h"
       +
       +#define CONTROL(ch) ((ch)^0x40)
       +#define LEN(x)      (sizeof (x) / sizeof *(x))
       +#define MAX(A, B)   ((A) > (B) ? (A) : (B))
       +
       +struct line {
       +        char line[BUFSIZ];
       +        size_t len;
       +        size_t pos;
       +};
       +
       +static struct line line;
       +static int isrunning = 1;
       +static char * outname = NULL;
       +static WINDOW * win = NULL;
       +
       +static void line_clear(void);
       +static void line_copywordcursor(char *buf, size_t bufsiz);
       +static void line_cursor_begin(void);
       +static void line_cursor_end(void);
       +static void line_cursor_next(void);
       +static void line_cursor_prev(void);
       +static void line_cursor_update(void);
       +static void line_delcharback(void);
       +static void line_delcharnext(void);
       +static void line_deltoend(void);
       +static void line_delwordback(void);
       +static void line_delwordcursor(void);
       +static void line_exit(void);
       +static void line_getwordpos(unsigned int *start, unsigned int *end);
       +static void line_insertchar(int c);
       +static void line_inserttext(const char *s);
       +static void line_newline(void);
       +static int  line_out(void);
       +static void line_prompt(void);
       +static int  line_pipeto(char **cmd);
       +static void line_redraw(size_t max);
       +static void line_set(const char *s);
       +static void line_yank(void);
       +static void history_menu(void);
       +static void complete_word(void);
       +static int  pipereadline(int fd_in, int fd_out, char *writestr, char *outbuf,
       +                         size_t outbufsiz);
       +static int  pipecmd(char *cmd[], char *writestr, char *outbuf,
       +                    size_t outbufsiz);
       +static void sighandler(int signum);
       +static void setup(void);
       +static void cleanup(void);
       +static void run(void);
       +static void usage(void);
       +
       +#include "config.h"
       +
       +static void
       +line_insertchar(int c)
       +{
       +        char s[2];
       +
       +        s[0] = c;
       +        s[1] = '\0';
       +        line_inserttext(s);
       +}
       +
       +static void
       +line_inserttext(const char *s)
       +{
       +        size_t len;
       +
       +        len = strlen(s);
       +        if(line.pos + len + 1 > sizeof(line.line))
       +                return;
       +        /* append */
       +        if(line.pos == line.len) {
       +                memmove(&line.line[line.pos], s, len);
       +        } else {
       +                /* insert */
       +                memmove(&line.line[line.pos + len], &line.line[line.pos], line.len - line.pos);
       +                memcpy(&line.line[line.pos], s, len);
       +        }
       +        line.pos += len;
       +        line.len += len;
       +        line.line[line.len + 1] = '\0';
       +}
       +
       +static void
       +line_set(const char *s) {
       +        strlcpy(line.line, s, sizeof(line.line));
       +        line.len = strlen(line.line);
       +        line.pos = line.len;
       +}
       +
       +static void
       +line_prompt(void)
       +{
       +        size_t i;
       +
       +        wmove(win, 0, 0);
       +        for(i = 0; prompt[i]; i++)
       +                waddch(win, prompt[i]);
       +}
       +
       +static void
       +line_redraw(size_t max)
       +{
       +        size_t n;
       +
       +        line_prompt();
       +
       +        for(n = 0; line.line[n] && n < line.len && n < max; n++)
       +                waddch(win, line.line[n]);
       +        for(; n < max; n++)
       +                waddch(win, ' ');
       +        wrefresh(win);
       +}
       +
       +static int
       +line_out(void)
       +{
       +        FILE *fp;
       +        if(!(fp = fopen(outname, "a"))) {
       +                fprintf(stderr, "fopen: '%s': %s\n", outname, strerror(errno));
       +                return -1;
       +        }
       +        fprintf(fp, "%s\n", line.line);
       +        fflush(fp);
       +        fclose(fp);
       +        return 0;
       +}
       +
       +static void
       +line_cursor_update(void)
       +{
       +        wmove(win, 0, line.pos + strlen(prompt));
       +}
       +
       +static void
       +line_cursor_begin(void)
       +{
       +        line.pos = 0;
       +        line_cursor_update();
       +}
       +
       +static void
       +line_cursor_prev(void)
       +{
       +        if(line.pos > 0)
       +                line.pos--;
       +        line_cursor_update();
       +}
       +
       +static void
       +line_cursor_next(void)
       +{
       +        if(line.pos < line.len)
       +                line.pos++;
       +        line_cursor_update();
       +}
       +
       +static void
       +line_cursor_end(void)
       +{
       +        line.pos = line.len;
       +        line_cursor_update();
       +}
       +
       +static void
       +line_clear(void)
       +{
       +        line.line[0] = '\0';
       +        line_redraw(line.len);
       +        line.len = 0;
       +        line_cursor_begin();
       +}
       +
       +static void
       +line_delcharnext(void)
       +{
       +        size_t oldlen = line.len;
       +
       +        if(line.pos == line.len || line.len <= 0)
       +                return;
       +
       +        memmove(&line.line[line.pos], &line.line[line.pos + 1],
       +                line.line[line.len - line.pos - 1]);
       +        line.len--;
       +        line.line[line.len] = '\0';
       +        line_redraw(oldlen);
       +        line_cursor_update();
       +}
       +
       +static void
       +line_delcharback(void)
       +{
       +        size_t oldlen = line.len;
       +
       +        if(line.pos <= 0 || line.len <= 0)
       +                return;
       +        memmove(&line.line[line.pos - 1], &line.line[line.pos],
       +                line.line[line.len - line.pos]);
       +        line.len--;
       +        line.line[line.len] = '\0';
       +        line_redraw(oldlen);
       +        line_cursor_prev();
       +}
       +
       +static void
       +line_deltoend(void)
       +{
       +        size_t oldlen = line.len;
       +
       +        line.line[line.pos] = '\0';
       +        line.len = line.pos;
       +        line_redraw(oldlen);
       +        line_cursor_end();
       +}
       +
       +static void
       +line_delwordcursor(void)
       +{
       +        unsigned int s, e;
       +        size_t len, oldlen = line.len;
       +
       +        line_getwordpos(&s, &e);
       +
       +        memmove(&line.line[s], &line.line[e], line.len - e);
       +        len = e - s;
       +        line.len -= len;
       +        line.pos = s;
       +        line.line[line.len] = '\0';
       +        line_redraw(MAX(line.len, oldlen));
       +        line_cursor_update();
       +}
       +
       +static void
       +line_delwordback(void)
       +{
       +        size_t i, len, oldlen = line.len;
       +
       +        if(line.pos <= 0 || line.len <= 0)
       +                return;
       +
       +        i = line.pos;
       +        while(i > 0 && isspace(line.line[i - 1]))
       +                i--;
       +        while(i > 0 && !isspace(line.line[i - 1]))
       +                i--;
       +
       +        len = line.len - line.pos;
       +        if(len > 0)
       +                memmove(&line.line[i], &line.line[line.pos],
       +                        line.len - line.pos);
       +        len = line.pos - i;
       +        line.pos = i;
       +        line.len -= len;
       +        line.line[line.len] = '\0';
       +        line_redraw(oldlen);
       +        line_cursor_update();
       +}
       +
       +static void
       +line_newline(void)
       +{
       +        line_out();
       +        line_clear();
       +        line_prompt();
       +        wrefresh(win);
       +}
       +
       +static void
       +line_exit(void)
       +{
       +        line_newline();
       +        isrunning = 0;
       +}
       +
       +static void
       +line_getwordpos(unsigned int *start, unsigned int *end)
       +{
       +        size_t i;
       +
       +        i = line.pos;
       +        while(i > 0 && !isspace(line.line[i - 1]))
       +                i--;
       +        if(start)
       +                *start = i;
       +        i = line.pos;
       +        while(line.line[i] && i < line.len && !isspace(line.line[i]))
       +                i++;
       +        if(end)
       +                *end = i;
       +}
       +
       +static void
       +line_copywordcursor(char *buf, size_t bufsiz)
       +{
       +        unsigned int s, e;
       +        size_t len;
       +
       +        line_getwordpos(&s, &e);
       +        len = e - s;
       +        /* truncate */
       +        if(len + 1 > bufsiz)
       +                len = bufsiz - 1;
       +        memcpy(buf, &line.line[s], len);
       +        buf[len + 1] = '\0';
       +}
       +
       +static void
       +complete_word(void)
       +{
       +        char wordbuf[BUFSIZ], outbuf[BUFSIZ];
       +        size_t oldlen = line.len;
       +
       +        outbuf[0] = '\0';
       +        line_copywordcursor(wordbuf, sizeof(wordbuf));
       +
       +        if(pipecmd((char**)completewordcmd, wordbuf, outbuf,
       +                   sizeof(outbuf)) == -1)
       +                return;
       +        if(outbuf[0] == '\0')
       +                return;
       +
       +        line_delwordcursor();
       +        line_inserttext(outbuf);
       +        line_redraw(MAX(line.len, oldlen));
       +        line_cursor_update();
       +}
       +
       +static void
       +line_yank(void)
       +{
       +        line_pipeto((char**)yankcmd);
       +}
       +
       +static void
       +history_menu(void)
       +{
       +        line_pipeto((char**)historycmd);
       +}
       +
       +static int
       +pipereadline(int fd_in, int fd_out, char *writestr, char *outbuf,
       +        size_t outbufsiz)
       +{
       +        char buf[PIPE_BUF], *p;
       +        struct timeval tv;
       +        fd_set fdr, fdw;
       +        int r, w, maxfd, status = -1, haswritten = 0;
       +
       +        for(;;) {
       +                FD_ZERO(&fdr);
       +                FD_ZERO(&fdw);
       +                if(haswritten) {
       +                        FD_SET(fd_in, &fdr);
       +                        maxfd = fd_in;
       +                } else {
       +                        FD_SET(fd_out, &fdw);
       +                        maxfd = fd_out;
       +                }
       +                memset(&tv, 0, sizeof(tv));
       +                tv.tv_sec = 0;
       +                tv.tv_usec = 200;
       +
       +                if((r = select(maxfd + 1, haswritten ? &fdr : NULL,
       +                                   haswritten ? NULL : &fdw, NULL, &tv)) == -1)
       +                        goto fini;
       +                else if(!r) /* timeout */
       +                        continue;
       +
       +                if(haswritten) {
       +                        if(FD_ISSET(fd_in, &fdr)) {
       +                                /* read until newline */
       +                                if((r = read(fd_in, buf, sizeof(buf))) == -1)
       +                                        goto fini;
       +                                buf[r] = '\0';
       +                                if((p = strpbrk(buf, "\r\n")))
       +                                        *p = '\0';
       +                                strlcpy(outbuf, buf, sizeof(outbuf));
       +                                status = 0;
       +                                goto fini;
       +                        }
       +                } else {
       +                        if(FD_ISSET(fd_out, &fdw)) {
       +                                /* write error */
       +                                if((w = write(fd_out, writestr, strlen(writestr))) == -1)
       +                                        goto fini;
       +                                close(fd_out); /* sends EOF */
       +                                haswritten = 1;
       +                        }
       +                }
       +        }
       +fini:
       +        close(fd_in);
       +        close(fd_out);
       +        return status;
       +}
       +
       +static int
       +pipecmd(char *cmd[], char *writestr, char *outbuf, size_t outbufsiz)
       +{
       +        struct sigaction sa;
       +        pid_t pid;
       +        int pc[2], cp[2];
       +
       +        if ((pipe(pc) == -1) || (pipe(cp) == -1)) {
       +                perror("pipe");
       +                return -1;
       +        }
       +        pid = fork();
       +        if (pid == -1) {
       +                perror("fork");
       +                return -1;
       +        } else if (pid == 0) {
       +                /* child */
       +                close(cp[0]);
       +                close(pc[1]);
       +                if (dup2(pc[0], STDIN_FILENO) == -1 ||
       +                   dup2(cp[1], STDOUT_FILENO) == -1) {
       +                        perror("dup2");
       +                        return -1;
       +                }
       +                if(execv(cmd[0], (char**)cmd) == -1) {
       +                        perror("execv");
       +                        _exit(EXIT_FAILURE); /* NOTE: must be _exit */
       +                }
       +                _exit(EXIT_SUCCESS);
       +        } else {
       +                /* parent */
       +                close(pc[0]);
       +                close(cp[1]);
       +
       +                /* ignore SIGPIPE, we handle this for write(). */
       +                memset(&sa, 0, sizeof(sa));
       +                sa.sa_flags = SA_RESTART;
       +                sa.sa_handler = SIG_IGN;
       +                sigaction(SIGPIPE, &sa, NULL);
       +
       +                if(pipereadline(cp[0], pc[1], writestr, outbuf, outbufsiz) == -1)
       +                        return -1;
       +        }
       +        return 0;
       +}
       +
       +static int
       +line_pipeto(char **cmd)
       +{
       +        size_t oldlen = line.len;
       +
       +        if(pipecmd(cmd, line.line, line.line, sizeof(line.line)) == -1)
       +                return -1;
       +        line.len = strlen(line.line);
       +        line_redraw(MAX(line.len, oldlen));
       +        line_cursor_end();
       +        return 0;
       +}
       +
       +static void
       +sighandler(int signum)
       +{
       +        if(signum == SIGTERM) {
       +                isrunning = 0;
       +                cleanup();
       +        }
       +}
       +
       +static void
       +setup(void)
       +{
       +        struct sigaction sa;
       +
       +        initscr();
       +        win = stdscr;
       +        cbreak();
       +        noecho();
       +        nonl();
       +        nodelay(win, FALSE);
       +        keypad(win, TRUE);
       +        curs_set(1);
       +        ESCDELAY = 20;
       +
       +        /* signal handling */
       +        memset(&sa, 0, sizeof(sa));
       +        sa.sa_flags = SA_RESTART;
       +        sa.sa_handler = sighandler;
       +        sigaction(SIGTERM, &sa, NULL);
       +}
       +
       +static void
       +cleanup(void)
       +{
       +        endwin();
       +}
       +
       +static void
       +run(void)
       +{
       +        size_t i;
       +        int c, ismatch = 0;
       +
       +        line_redraw(line.len);
       +        while(isrunning) {
       +                c = wgetch(win);
       +                switch(c) {
       +                case ERR:
       +                        isrunning = 0;
       +                        break;
       +                case 0x1b: /* ignore unbinded escape sequences */
       +                        nodelay(win, TRUE);
       +                        while((c = wgetch(win)) != ERR);
       +                        nodelay(win, FALSE);
       +                        break;
       +                case KEY_RESIZE:
       +                        if(resizecmd && resizecmd[0])
       +                                system(resizecmd);
       +                        break;
       +                default:
       +                        ismatch = 0;
       +                        for(i = 0; i < LEN(keybinds); i++) {
       +                                if(keybinds[i].key == c) {
       +                                        ismatch = 1;
       +                                        keybinds[i].func();
       +                                        break;
       +                                }
       +                        }
       +                        if(!ismatch) {
       +                                line_insertchar(c);
       +                                line_redraw(line.len);
       +                                line_cursor_update();
       +                                wrefresh(win);
       +                        }
       +                }
       +        }
       +}
       +
       +static void
       +usage(void)
       +{
       +        fprintf(stderr, "usage: %s <-f outfile> [-i id] [-l line] [-p prompt] "
       +                        "[-r resizecmd]\n", argv0);
       +        exit(EXIT_FAILURE);
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        ARGBEGIN {
       +        case 'f':
       +                outname = EARGF(usage());
       +                break;
       +        case 'i':
       +                strlcpy(id, EARGF(usage()), sizeof(id));
       +                break;
       +        case 'l':
       +                line_set(EARGF(usage()));
       +                break;
       +        case 'p':
       +                prompt = EARGF(usage());
       +                break;
       +        case 'r':
       +                resizecmd = EARGF(usage());
       +                break;
       +        default:
       +                usage();
       +        } ARGEND;
       +
       +        if(!outname)
       +                usage();
       +
       +        setlocale(LC_ALL, "");
       +        setup();
       +        run();
       +        cleanup();
       +
       +        return EXIT_SUCCESS;
       +}
 (DIR) diff --git a/strlcpy.c b/strlcpy.c
       @@ -0,0 +1,47 @@
       +/*
       + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
       + *
       + * 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.
       + */
       +
       +#include <sys/types.h>
       +#include <string.h>
       +#include "util.h"
       +
       +/*
       + * Copy src to string dst of size siz. At most siz-1 characters
       + * will be copied. Always NUL terminates (unless siz == 0).
       + * Returns strlen(src); if retval >= siz, truncation occurred.
       + */
       +size_t
       +strlcpy(char *dst, const char *src, size_t siz)
       +{
       +        char *d = dst;
       +        const char *s = src;
       +        size_t n = siz;
       +        /* Copy as many bytes as will fit */
       +        if (n != 0) {
       +                while (--n != 0) {
       +                        if ((*d++ = *s++) == '\0')
       +                                break;
       +                }
       +        }
       +        /* Not enough room in dst, add NUL and traverse rest of src */
       +        if (n == 0) {
       +                if (siz != 0)
       +                        *d = '\0'; /* NUL-terminate dst */
       +                while (*s++)
       +                        ;
       +        }
       +        return(s - src - 1); /* count does not include NUL */
       +}
 (DIR) diff --git a/util.h b/util.h
       @@ -0,0 +1,5 @@
       +#include <stdio.h>
       +#include <unistd.h>
       +
       +#undef strlcpy
       +size_t strlcpy(char *dst, const char *src, size_t siz);