commit 99d263504572720db6a3a78f1d1b7e0b50060ca7
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(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+sob
+*.o
+core
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.
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
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.
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.
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
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 },
+};
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
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
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
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!
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;
+}
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 */
+}
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);