sob

Simple output bar
git clone https://git.sinitax.com/codemadness/sob
Log | Files | Refs | README | LICENSE | Upstream | sfeed.txt

sob.c (18973B)


      1#include <ctype.h>
      2#include <errno.h>
      3#include <fcntl.h>
      4#include <limits.h>
      5#include <locale.h>
      6#include <signal.h>
      7#include <stdio.h>
      8#include <stdlib.h>
      9#include <string.h>
     10#include <sys/ioctl.h>
     11#include <sys/select.h>
     12#include <unistd.h>
     13#include <termios.h>
     14#include <time.h>
     15#include <wchar.h>
     16
     17#include "arg.h"
     18char *argv0;
     19
     20#define LEN(x)    (sizeof (x) / sizeof *(x))
     21#define ISUTF8(c) (((c) & 0xc0) != 0x80)
     22
     23struct line {
     24	char line[16384]; /* static line buffer */
     25	size_t bytesiz;   /* length in bytes */
     26	size_t utflen;    /* length in characters */
     27	size_t bytepos;   /* index position (in bytes) */
     28	size_t utfpos;    /* pos in characters */
     29	size_t colpos;    /* cursor position (in columns) */
     30	size_t collen;    /* total length (in columns) */
     31};
     32
     33static void    cb_handleinput(const char *, size_t, size_t);
     34static void    cb_pipe_insert(const char *, size_t, size_t);
     35static void    cb_pipe_replaceword(const char *, size_t, size_t);
     36
     37static void    line_clear(void);
     38static void    line_copywordcursor(char *, size_t);
     39static void    line_cursor_begin(void);
     40static void    line_cursor_end(void);
     41static void    line_cursor_move(size_t);
     42static void    line_cursor_next(void);
     43static void    line_cursor_prev(void);
     44static void    line_cursor_wordprev(void);
     45static void    line_cursor_wordnext(void);
     46static void    line_delcharprev(void);
     47static void    line_delcharnext(void);
     48static void    line_deltoend(void);
     49static void    line_delwordprev(void);
     50static void    line_delwordcursor(void);
     51static void    line_draw(void);
     52static void    line_exit(void);
     53static void    line_getwordpos(size_t, size_t, size_t *, size_t *, size_t *,
     54                               size_t *);
     55static void    line_getwordposprev(size_t, size_t, size_t *, size_t *);
     56static void    line_getwordposnext(size_t, size_t, size_t *, size_t *);
     57static void    line_inserttext(const char *);
     58static void    line_newline(void);
     59static void    line_out(void);
     60static void    line_prompt(void);
     61static int     line_promptlen(void);
     62static int     line_pipeto(char **, void (*)(const char *, size_t, size_t));
     63static int     line_wordpipeto(char **,
     64                               void (*)(const char *, size_t, size_t));
     65
     66static int     pipe_rw(int, int, char *,
     67                         void (*)(const char *, size_t, size_t));
     68static int     pipe_cmd(char *[], char *,
     69                        void (*)(const char *, size_t, size_t));
     70
     71static void    clear(void);
     72static void    gettermsize(void);
     73static void    handleinput(const unsigned char *, size_t);
     74static void    initialinput(void);
     75static void    resize(void);
     76static void    run(void);
     77static void    setup(void);
     78static void    sighandler(int);
     79static void    usage(void);
     80
     81static size_t  colw(const char *, size_t);
     82static int     nonspace(int c);
     83static size_t  utf8len(const char *);
     84static size_t  utfprevn(const char *, size_t, size_t);
     85static size_t  utfnextn(const char *, size_t, size_t);
     86static void    utfuntilchar(size_t *, size_t *, int (*)(int), int);
     87
     88static struct  termios ttystate, ttysave;
     89
     90static struct  line line;
     91static size_t  dirtylen; /* dirty length (in columns) */
     92static int     cols = 79, rows = 24;
     93static int     termattrset = 0;
     94static int     isrunning = 1;
     95static FILE *  outfp = NULL;
     96static FILE *  lineoutfp = NULL;
     97
     98#include "config.h"
     99
    100static int
    101nonspace(int c)
    102{
    103	return !isspace(c);
    104}
    105
    106static size_t
    107colw(const char *s, size_t max)
    108{
    109	size_t len = 0, i;
    110	wchar_t w;
    111	int r;
    112
    113	for (i = 0; *s && i < max; s++, i++) {
    114		if (ISUTF8(*s)) {
    115			if ((r = mbtowc(&w, s, i + 4 > max ? max - i : 4)) == -1)
    116				break;
    117			if ((r = wcwidth(w)) == -1)
    118				r = 1;
    119			len += r;
    120		}
    121	}
    122	return len;
    123}
    124
    125static size_t
    126utf8len(const char *s)
    127{
    128	size_t i;
    129
    130	for (i = 0; *s; s++) {
    131		if (ISUTF8(*s))
    132			i++;
    133	}
    134	return i;
    135}
    136
    137/* returns amount of bytes needed to go to previous utf char
    138 * p is index in bytes. */
    139static size_t
    140utfprevn(const char *s, size_t p, size_t n)
    141{
    142	size_t i;
    143
    144	for (i = 1; p > 0; p--, i++) {
    145		if (ISUTF8(s[p - 1]) && !--n)
    146			return i;
    147	}
    148	return 0;
    149}
    150
    151/* returns amount of bytes needed to go to next utf char
    152 * p is index in bytes. */
    153static size_t
    154utfnextn(const char *s, size_t p, size_t n)
    155{
    156	size_t i;
    157
    158	for (i = 1; s[p]; p++, i++) {
    159		if (ISUTF8(s[p + 1]) && !--n)
    160			return i;
    161	}
    162	return 0;
    163}
    164
    165/* b is byte start pos, u is utf pos, f is filter function,
    166 * dir is -1 or +1 for prev or next */
    167static void
    168utfuntilchar(size_t *b, size_t *u, int (*f)(int), int dir)
    169{
    170	size_t n;
    171
    172	if (dir > 0) {
    173		while (*u < line.utflen && *b < line.bytesiz) {
    174			if (f(line.line[*b]))
    175				break;
    176			if ((n = utfnextn(line.line, *b, 1)) == 0)
    177				break;
    178			*b += n;
    179			(*u)++;
    180		}
    181
    182	} else {
    183		while (*u > 0 && *b > 0) {
    184			if (f(line.line[*b - 1]))
    185				break;
    186			if ((n = utfprevn(line.line, *b, 1)) == 0)
    187				break;
    188			*b -= n;
    189			(*u)--;
    190		}
    191	}
    192}
    193
    194static void
    195line_inserttext(const char *s)
    196{
    197	size_t siz, ulen, clen;
    198
    199	siz = strlen(s);
    200	if (line.bytepos + siz + 1 > sizeof(line.line))
    201		return;
    202	clen = colw(s, siz);
    203	ulen = utf8len(s);
    204	/* append */
    205	if (line.bytepos == line.bytesiz) {
    206		memmove(&line.line[line.bytepos], s, siz);
    207	} else {
    208		/* insert */
    209		memmove(&line.line[line.bytepos + siz], &line.line[line.bytepos],
    210		        line.bytesiz - line.bytepos);
    211		memcpy(&line.line[line.bytepos], s, siz);
    212	}
    213	line.bytepos += siz;
    214	line.bytesiz += siz;
    215	line.line[line.bytesiz + 1] = '\0';
    216	line.utflen = utf8len(line.line);
    217	line.utfpos += ulen;
    218	line.colpos += clen;
    219	line.collen = colw(line.line, line.bytesiz);
    220}
    221
    222/* like mksh, toggle counting of escape codes in prompt with "\x01" */
    223static int
    224line_promptlen(void)
    225{
    226	size_t i;
    227	int t = 0, n = 0;
    228
    229	for (i = 0; prompt[i]; i++) {
    230		if (prompt[i] == 1)
    231			t = !t;
    232		else if (!t && ISUTF8(prompt[i]))
    233			n++;
    234	}
    235	return n;
    236}
    237
    238static void
    239line_prompt(void)
    240{
    241	size_t i;
    242
    243	for (i = 0; prompt[i]; i++) {
    244		if (prompt[i] != 1)
    245			fputc(prompt[i], outfp);
    246	}
    247}
    248
    249static void
    250clear(void)
    251{
    252	/* clear screen, move cursor to (0, 0) */
    253	fprintf(outfp, "\x1b[2J\x1b[H");
    254	fflush(outfp);
    255}
    256
    257static void
    258line_draw(void)
    259{
    260	size_t i;
    261
    262	fprintf(outfp, "\x1b[?25l"); /* hide cursor */
    263	fprintf(outfp, "\x1b[H"); /* move cursor to (0, 0) */
    264
    265	line_prompt();
    266	fwrite(line.line, 1, line.bytesiz, outfp);
    267
    268	/* replace dirty chars with ' ' */
    269	if (dirtylen > line.collen) {
    270		for (i = line.collen; i < dirtylen; i++)
    271			fputc(' ', outfp);
    272	}
    273	line_cursor_move(line.colpos);
    274	fprintf(outfp, "\x1b[?25h"); /* show cursor */
    275	fflush(outfp);
    276}
    277
    278static void
    279line_out(void)
    280{
    281	fprintf(lineoutfp, "%s\n", line.line);
    282	fflush(lineoutfp);
    283}
    284
    285static void
    286line_cursor_move(size_t newpos)
    287{
    288	size_t x, y = 0;
    289
    290	x = newpos + line_promptlen();
    291
    292	/* linewrap */
    293	if (cols > 0 && x > (size_t)cols - 1) {
    294		y = (x - (x % cols)) / cols;
    295		x %= cols;
    296	}
    297	fprintf(outfp, "\x1b[%lu;%luH", (unsigned long)y + 1, (unsigned long)x + 1);
    298	fflush(outfp);
    299}
    300
    301static void
    302line_cursor_wordprev(void)
    303{
    304	line_getwordposprev(line.bytepos, line.utfpos, &line.bytepos,
    305	                    &line.utfpos);
    306	line.colpos = colw(line.line, line.bytepos);
    307	line_cursor_move(line.colpos);
    308}
    309
    310static void
    311line_cursor_wordnext(void)
    312{
    313	line_getwordposnext(line.bytepos, line.utfpos, &line.bytepos,
    314	                    &line.utfpos);
    315	line.colpos = colw(line.line, line.bytepos);
    316	line_cursor_move(line.colpos);
    317}
    318
    319static void
    320line_cursor_begin(void)
    321{
    322	line.bytepos = 0;
    323	line.utfpos = 0;
    324	line.colpos = 0;
    325	line_cursor_move(line.colpos);
    326}
    327
    328static void
    329line_cursor_prev(void)
    330{
    331	size_t n;
    332
    333	if (line.utfpos <= 0)
    334		return;
    335	if ((n = utfprevn(line.line, line.bytepos, 1)) == 0)
    336		return;
    337
    338	line.bytepos -= n;
    339	line.utfpos--;
    340	line.colpos -= colw(&line.line[line.bytepos], n);
    341	line_cursor_move(line.colpos);
    342}
    343
    344static void
    345line_cursor_next(void)
    346{
    347	size_t n;
    348
    349	if (line.utfpos >= line.utflen)
    350		return;
    351
    352	if ((n = utfnextn(line.line, line.bytepos, 1)) == 0)
    353		return;
    354	line.colpos += colw(&line.line[line.bytepos], n);
    355	line.bytepos += n;
    356	line.utfpos++;
    357	line_cursor_move(line.colpos);
    358}
    359
    360static void
    361line_cursor_end(void)
    362{
    363	line.bytepos = line.bytesiz;
    364	line.utfpos = line.utflen;
    365	line.colpos = line.collen;
    366	line_cursor_move(line.colpos);
    367}
    368
    369static void
    370line_clear(void)
    371{
    372	memset(&line, 0, sizeof(line));
    373	line_draw();
    374}
    375
    376static void
    377line_delcharnext(void)
    378{
    379	size_t siz;
    380
    381	if (line.utfpos == line.utflen || line.utflen <= 0)
    382		return;
    383
    384	if ((siz = utfnextn(line.line, line.bytepos, 1)) == 0)
    385		return;
    386
    387	line.collen -= colw(&line.line[line.bytepos], siz);
    388
    389	memmove(&line.line[line.bytepos], &line.line[line.bytepos + siz],
    390	        line.bytesiz - line.bytepos - siz);
    391
    392	line.bytesiz -= siz;
    393	line.line[line.bytesiz] = '\0';
    394	line.utflen--;
    395	line_draw();
    396}
    397
    398static void
    399line_delcharprev(void)
    400{
    401	size_t siz, col;
    402
    403	if (line.utfpos <= 0 || line.utflen <= 0)
    404		return;
    405	if ((siz = utfprevn(line.line, line.bytepos, 1)) == 0)
    406		return;
    407
    408	col = colw(&line.line[line.bytepos - siz], siz);
    409
    410	memmove(&line.line[line.bytepos - siz], &line.line[line.bytepos],
    411	        line.bytesiz - line.bytepos);
    412
    413	line.bytepos -= siz;
    414	line.bytesiz -= siz;
    415	line.line[line.bytesiz] = '\0';
    416	line.utflen--;
    417	line.utfpos--;
    418	line.colpos -= col;
    419	line.collen -= col;
    420	line_draw();
    421}
    422
    423static void
    424line_deltoend(void)
    425{
    426	line.line[line.bytepos] = '\0';
    427	line.bytesiz = line.bytepos;
    428	line.utflen = utf8len(line.line);
    429	line.utfpos = line.utflen;
    430	line.collen = colw(line.line, line.bytesiz);
    431	line.colpos = line.collen;
    432	line_draw();
    433}
    434
    435static void
    436line_delwordcursor(void)
    437{
    438	size_t bs, be, us, ue, siz, len;
    439
    440	line_getwordpos(line.bytepos, line.utfpos, &bs, &be, &us, &ue);
    441
    442	siz = be - bs;
    443	len = ue - us;
    444
    445	memmove(&line.line[bs], &line.line[be], line.bytesiz - be);
    446
    447	line.bytesiz -= siz;
    448	line.bytepos = bs;
    449	line.line[line.bytesiz] = '\0';
    450	line.utfpos = us;
    451	line.utflen -= len;
    452	line.collen = colw(line.line, line.bytesiz);
    453	line.colpos = colw(line.line, bs);
    454	line_draw();
    455}
    456
    457static void
    458line_delwordprev(void)
    459{
    460	size_t bs, us, siz, len;
    461
    462	if (line.utfpos <= 0 || line.utflen <= 0)
    463		return;
    464
    465	line_getwordposprev(line.bytepos, line.utfpos, &bs, &us);
    466
    467	siz = line.bytepos - bs;
    468	len = line.utfpos - us;
    469	line.colpos -= colw(&line.line[bs], siz);
    470
    471	memmove(&line.line[bs], &line.line[line.bytepos],
    472	        line.bytesiz - line.bytepos);
    473
    474	line.bytesiz -= siz;
    475	line.bytepos = bs;
    476	line.line[line.bytesiz] = '\0';
    477	line.utfpos = us;
    478	line.utflen -= len;
    479	line.collen = colw(line.line, line.bytesiz);
    480	line_draw();
    481}
    482
    483static void
    484line_newline(void)
    485{
    486	line_out();
    487	memset(&line, 0, sizeof(line));
    488	line_draw();
    489}
    490
    491static void
    492line_exit(void)
    493{
    494	fprintf(outfp, "\n");
    495	fflush(outfp);
    496	isrunning = 0;
    497}
    498
    499static void
    500line_getwordpos(size_t b, size_t u, size_t *bs, size_t *be,
    501	size_t *us, size_t *ue)
    502{
    503	size_t tb = b, tu = u;
    504
    505	utfuntilchar(&b, &u, isspace, -1);
    506	if (bs)
    507		*bs = b;
    508	if (us)
    509		*us = u;
    510
    511	/* seek from original specified position */
    512	utfuntilchar(&tb, &tu, isspace, +1);
    513	if (be)
    514		*be = tb;
    515	if (ue)
    516		*ue = tu;
    517}
    518
    519static void
    520line_getwordposprev(size_t sb, size_t su, size_t *b, size_t *u)
    521{
    522	utfuntilchar(&sb, &su, nonspace, -1);
    523	utfuntilchar(&sb, &su, isspace, -1);
    524	if (b)
    525		*b = sb;
    526	if (u)
    527		*u = su;
    528}
    529
    530static void
    531line_getwordposnext(size_t sb, size_t su, size_t *b, size_t *u)
    532{
    533	utfuntilchar(&sb, &su, nonspace, +1);
    534	utfuntilchar(&sb, &su, isspace, +1);
    535	if (b)
    536		*b = sb;
    537	if (u)
    538		*u = su;
    539}
    540
    541static void
    542line_copywordcursor(char *buf, size_t bufsiz)
    543{
    544	size_t bs, be, len;
    545
    546	line_getwordpos(line.bytepos, line.utfpos, &bs, &be, NULL, NULL);
    547	len = be - bs;
    548
    549	/* truncate */
    550	if (len + 1 > bufsiz)
    551		len = bufsiz - 1;
    552	memcpy(buf, &line.line[bs], len);
    553	buf[len] = '\0';
    554}
    555
    556/* It will be tried first to write to the pipe and then read.
    557 * if fd_in == -1 don't read, if fd_out == -1 or writestr == NULL don't write.
    558 * f is the read callback() on the data (buf, read_size, total_read).
    559 * if f is NULL no data is read from the pipe. */
    560static int
    561pipe_rw(int fd_in, int fd_out, char *writestr,
    562	void (*f)(const char *, size_t, size_t))
    563{
    564	char buf[PIPE_BUF];
    565	struct timeval tv;
    566	fd_set fdr, fdw;
    567	size_t total = 0;
    568	ssize_t r;
    569	int maxfd, status = -1, haswritten = 0;
    570
    571	if (fd_out == -1 || writestr == NULL)
    572		haswritten = 1;
    573
    574	while (isrunning) {
    575		FD_ZERO(&fdr);
    576		FD_ZERO(&fdw);
    577		if (haswritten) {
    578			if (!f || fd_in == -1)
    579				break;
    580			FD_SET(fd_in, &fdr);
    581			maxfd = fd_in;
    582		} else {
    583			FD_SET(fd_out, &fdw);
    584			maxfd = fd_out;
    585		}
    586		memset(&tv, 0, sizeof(tv));
    587		tv.tv_usec = 50000; /* 50 ms */
    588
    589		if ((r = select(maxfd + 1, haswritten ? &fdr : NULL,
    590				   haswritten ? NULL : &fdw, NULL, &tv)) == -1) {
    591			if (errno != EINTR)
    592				goto fini;
    593		} else if (!r) { /* timeout */
    594			continue;
    595		}
    596		if (fd_out != -1 && FD_ISSET(fd_out, &fdw)) {
    597			if (write(fd_out, writestr, strlen(writestr)) == -1) {
    598				if (errno == EWOULDBLOCK || errno == EAGAIN ||
    599				   errno == EINTR)
    600					continue;
    601				else if (errno == EPIPE)
    602					goto fini;
    603				goto fini;
    604			}
    605			if (fd_out > 2)
    606				close(fd_out); /* sends EOF */
    607			fd_out = -1;
    608			haswritten = 1;
    609		}
    610		if (haswritten && fd_in != -1 && FD_ISSET(fd_in, &fdr)) {
    611			r = read(fd_in, buf, sizeof(buf));
    612			if (r == -1) {
    613				if (errno == EWOULDBLOCK || errno == EAGAIN)
    614					continue;
    615				goto fini;
    616			}
    617			if (r > 0) {
    618				buf[r] = '\0';
    619				total += (size_t)r;
    620				if (f)
    621					f(buf, r, total);
    622			} else if (!r) {
    623				status = 0;
    624				goto fini;
    625			}
    626		}
    627	}
    628fini:
    629	if (fd_in != -1 && fd_in > 2)
    630		close(fd_in);
    631	if (fd_out != -1 && fd_out > 2)
    632		close(fd_out);
    633	return status;
    634}
    635
    636static int
    637pipe_cmd(char *cmd[], char *writestr, void (*f)(const char *, size_t, size_t))
    638{
    639	struct sigaction sa;
    640	pid_t pid;
    641	int pc[2], cp[2];
    642
    643	if (pipe(pc) == -1 || pipe(cp) == -1) {
    644		perror("pipe");
    645		return -1;
    646	}
    647	pid = fork();
    648	if (pid == -1) {
    649		perror("fork");
    650		return -1;
    651	} else if (pid == 0) {
    652		/* child */
    653		setenv("SOBLINE", line.line, 1);
    654		setenv("SOBWRITE", writestr, 1);
    655		close(cp[0]);
    656		close(pc[1]);
    657
    658		if (dup2(pc[0], 0) == -1 || dup2(cp[1], 1) == -1) {
    659			perror("dup2");
    660			return -1;
    661		}
    662
    663		if (execv(cmd[0], (char**)cmd) == -1) {
    664			perror("execv");
    665			_exit(1); /* NOTE: must be _exit */
    666		}
    667		_exit(0);
    668	} else {
    669		/* parent */
    670		close(pc[0]);
    671		close(cp[1]);
    672
    673		/* ignore SIGPIPE, we handle this for write(). */
    674		memset(&sa, 0, sizeof(sa));
    675		sa.sa_flags = SA_RESTART;
    676		sa.sa_handler = SIG_IGN;
    677		sigaction(SIGPIPE, &sa, NULL);
    678
    679		if (pipe_rw(cp[0], pc[1], writestr, f) == -1)
    680			return -1;
    681	}
    682	return 0;
    683}
    684
    685static void
    686cb_handleinput(const char *buf, size_t len, size_t total)
    687{
    688	if (!len || !total)
    689		return;
    690	handleinput((unsigned char *)buf, len);
    691}
    692
    693static void
    694cb_pipe_insert(const char *buf, size_t len, size_t total)
    695{
    696	if (!len || !total)
    697		return;
    698	memset(&line, 0, sizeof(line));
    699	handleinput((unsigned char *)buf, len);
    700}
    701
    702static void
    703cb_pipe_replaceword(const char *buf, size_t len, size_t total)
    704{
    705	if (!len)
    706		return;
    707	/* first read: delete word under cursor. */
    708	if (len == total)
    709		line_delwordcursor();
    710	handleinput((unsigned char *)buf, len);
    711}
    712
    713static int
    714line_pipeto(char **cmd, void (*f)(const char *, size_t, size_t))
    715{
    716	return pipe_cmd(cmd, line.line, f);
    717}
    718
    719/* pipe word under cursor and replace it */
    720static int
    721line_wordpipeto(char **cmd, void (*f)(const char *, size_t, size_t))
    722{
    723	char wordbuf[BUFSIZ];
    724
    725	wordbuf[0] = '\0';
    726	line_copywordcursor(wordbuf, sizeof(wordbuf));
    727
    728	return pipe_cmd((char**)cmd, wordbuf, f);
    729}
    730
    731static void
    732sighandler(int signum)
    733{
    734	switch(signum) {
    735	case SIGINT: /* fallthrough */
    736	case SIGTERM:
    737		line_exit();
    738		break;
    739	case SIGWINCH:
    740		clear();
    741		gettermsize();
    742		resize();
    743		line_draw();
    744		break;
    745	}
    746}
    747
    748static void
    749gettermsize(void)
    750{
    751	struct winsize w;
    752
    753	if (ioctl(0, TIOCGWINSZ, &w) == -1)
    754		return;
    755	cols = w.ws_col;
    756	rows = w.ws_row;
    757}
    758
    759static void
    760resize(void)
    761{
    762	pipe_cmd((char **)resizecmd, line.line, NULL);
    763}
    764
    765static void
    766handleinput(const unsigned char *input, size_t len)
    767{
    768	size_t p = 0, keylen, i;
    769	int ismatch = 0;
    770	char buf[BUFSIZ];
    771
    772	dirtylen = line.collen;
    773	while (p < len && input[p] != '\0') {
    774		if (input[p] == 0x1b || iscntrl(input[p])) {
    775			ismatch = 0;
    776			for (i = 0; i < LEN(keybinds); i++) {
    777				keylen = strlen((char*)keybinds[i].key);
    778				if (len - p >= keylen &&
    779				   memcmp(&input[p], keybinds[i].key, keylen) == 0) {
    780					keybinds[i].func();
    781					p += keylen;
    782					ismatch = 1;
    783					break;
    784				}
    785			}
    786			if (!ismatch) {
    787				if (input[p] == 0x1b)
    788					return;
    789				p++;
    790			}
    791		} else {
    792			for (i = p; input[i] && input[i] > 0x1b; i++)
    793				;
    794			if (i - p < sizeof(buf)) {
    795				memcpy(buf, &input[p], i - p);
    796				buf[i - p] = '\0';
    797				p = i;
    798				line_inserttext((char*)buf);
    799				line_draw();
    800			} else {
    801				p++;
    802			}
    803		}
    804	}
    805}
    806
    807static void
    808setup(void)
    809{
    810	struct sigaction sa;
    811
    812	lineoutfp = stdout;
    813	outfp = stderr;
    814	setlocale(LC_ALL, "");
    815
    816	/* signal handling */
    817	memset(&sa, 0, sizeof(sa));
    818	sa.sa_flags = SA_RESTART;
    819	sa.sa_handler = sighandler;
    820	sigaction(SIGTERM, &sa, NULL);
    821	sigaction(SIGINT, &sa, NULL);
    822	sigaction(SIGWINCH, &sa, NULL);
    823	/* reap zombie childs >=) */
    824	sa.sa_handler = SIG_IGN;
    825	sigaction(SIGCHLD, &sa, NULL);
    826	/* ignore SIGPIPE, we handle this for write(). */
    827	sa.sa_handler = SIG_IGN;
    828	sigaction(SIGPIPE, &sa, NULL);
    829
    830	if (tcgetattr(0, &ttystate) == 0) {
    831		termattrset = 1;
    832		ttysave = ttystate;
    833		/* turn off canonical mode and echo */
    834		ttystate.c_lflag &= ~(ICANON | ECHO);
    835		ttystate.c_cc[VMIN] = 1;
    836		/* set the terminal attributes */
    837		tcsetattr(0, TCSANOW, &ttystate);
    838	} else {
    839		/* not a tty */
    840		initialinput();
    841		/* setup tty again because we (re)open "/dev/tty" */
    842		termattrset = 0;
    843		if (tcgetattr(0, &ttystate) == 0) {
    844			termattrset = 1;
    845			ttysave = ttystate;
    846			/* turn off canonical mode and echo */
    847			ttystate.c_lflag &= ~(ICANON | ECHO);
    848			ttystate.c_cc[VMIN] = 1;
    849			/* set the terminal attributes */
    850			tcsetattr(0, TCSANOW, &ttystate);
    851		}
    852	}
    853	/* get terminal window size */
    854	gettermsize();
    855}
    856
    857static void
    858run(void)
    859{
    860	if (!isrunning)
    861		return;
    862
    863	/* clear screen */
    864	clear();
    865
    866	fcntl(0, F_SETFL, O_NONBLOCK);
    867	line_draw();
    868
    869	pipe_rw(0, -1, NULL, cb_handleinput);
    870
    871	/* cleanup: restore terminal attributes */
    872	if (termattrset) {
    873		ttystate.c_lflag = ttysave.c_lflag;
    874		tcsetattr(0, TCSANOW, &ttystate);
    875	}
    876}
    877
    878static void
    879initialinput(void)
    880{
    881	int fd;
    882
    883	/* read initial input from stdin */
    884	fcntl(0, F_SETFL, O_NONBLOCK);
    885	pipe_rw(0, -1, NULL, cb_handleinput);
    886	/* restore terminal attributes */
    887	if (termattrset) {
    888		ttystate.c_lflag = ttysave.c_lflag;
    889		tcsetattr(0, TCSANOW, &ttystate);
    890	}
    891	if (!isrunning)
    892		return;
    893	/* close and re-attach to stdin */
    894	close(0);
    895	if ((fd = open("/dev/tty", O_RDONLY | O_NONBLOCK)) == -1) {
    896		fprintf(stderr, "open: /dev/tty: %s\n", strerror(errno));
    897		exit(1);
    898	}
    899	if (dup2(fd, 0) == -1) {
    900		fprintf(stderr, "dup2: /dev/tty: %s\n", strerror(errno));
    901		exit(1);
    902	}
    903}
    904
    905static void
    906usage(void)
    907{
    908	fprintf(stderr, "usage: %s [-p prompt]\n", argv0);
    909	exit(1);
    910}
    911
    912int
    913main(int argc, char **argv)
    914{
    915	ARGBEGIN {
    916	case 'p':
    917		prompt = EARGF(usage());
    918		break;
    919	default:
    920		usage();
    921	} ARGEND;
    922
    923	setup();
    924	run();
    925
    926	return 0;
    927}