lchat

A line oriented chat front end for ii.
git clone git://git.suckless.org/lchat
Log | Files | Refs

commit 98fa5e3861a515b0b9f7b212e7eb5abf550e1f33
parent b1db0ac6e9850a858120ac43596405208a3fc581
Author: Jan Klemkow <j.klemkow@wemelug.de>
Date:   Mon, 26 Oct 2015 22:17:57 +0100

wip

Diffstat:
lchat.c | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
slackline.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++------
slackline.h | 16+++++++---------
3 files changed, 216 insertions(+), 32 deletions(-)

diff --git a/lchat.c b/lchat.c @@ -1,6 +1,10 @@ #include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <term.h> #include <termios.h> #include <unistd.h> @@ -9,29 +13,104 @@ struct termios origin_term; -void +static void exit_handler(void) { if (tcsetattr(STDIN_FILENO, TCSANOW, &origin_term) == -1) err(EXIT_FAILURE, "tcsetattr"); } +static void +line_output(struct slackline *sl, char *file) +{ + int fd; + + if ((fd = open(file, O_WRONLY|O_APPEND)) == -1) + err(EXIT_FAILURE, "open: %s", file); + + /* replace NUL-terminator with newline as line separator for file */ + sl->buf[sl->len] = '\n'; + + if (write(fd, sl->buf, sl->len + 1) == -1) + err(EXIT_FAILURE, "write"); + + if (close(fd) == -1) + err(EXIT_FAILURE, "close"); +} + +static void +usage(void) +{ + fprintf(stderr, "lchar [-nH] [-p prompt] [-i in] [-o out] [directory]\n"); + exit(EXIT_FAILURE); +} + int -main(void) +main(int argc, char *argv[]) { + char tail_cmd[BUFSIZ]; + struct pollfd pfd[2]; struct termios term; struct slackline *sl = sl_init(); - char *term_name = getenv("TERM"); int fd = STDIN_FILENO; int c; + int ch; + bool empty_line = true; + size_t history_len = 0; + char *prompt = ">"; + size_t prompt_len = strlen(prompt); + char *dir = "."; + char *in_file = NULL; + char *out_file = NULL; + FILE *tail_fh; + + while ((ch = getopt(argc, argv, "H:i:no:p:h")) != -1) { + switch (ch) { + case 'H': + errno = 0; + history_len = strtoull(optarg, NULL, 0); + if (errno != 0) + err(EXIT_FAILURE, "strtoull"); + break; + case 'i': + if ((in_file = strdup(optarg)) == NULL) + err(EXIT_FAILURE, "strdup"); + break; + case 'n': + empty_line = false; + break; + case 'o': + if ((out_file = strdup(optarg)) == NULL) + err(EXIT_FAILURE, "strdup"); + break; + case 'p': + if ((prompt = strdup(optarg)) == NULL) + err(EXIT_FAILURE, "strdup"); + prompt_len = strlen(prompt); + break; + case 'h': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; - if (term_name == NULL) - errx(EXIT_FAILURE, "environment TERM is not set"); + if (argc > 1) + usage(); - switch (tgetent(NULL, term_name)) { - case -1: err(EXIT_FAILURE, "tgetent"); - case 0: errx(EXIT_FAILURE, "no termcap entry found for %s", term_name); - } + if (argc == 1) + if ((dir = strdup(argv[0])) == NULL) + err(EXIT_FAILURE, "strdup"); + + if (in_file == NULL) + if (asprintf(&in_file, "%s/in", dir) == -1) + err(EXIT_FAILURE, "asprintf"); + + if (out_file == NULL) + if (asprintf(&out_file, "%s/out", dir) == -1) + err(EXIT_FAILURE, "asprintf"); if (isatty(fd) == 0) err(EXIT_FAILURE, "isatty"); @@ -47,7 +126,14 @@ main(void) if (tcgetattr(fd, &term) == -1) err(EXIT_FAILURE, "tcgetattr"); - cfmakeraw(&term); + /* TODO: clean up this block. copied from cfmakeraw(3) */ + term.c_iflag &= ~(IMAXBEL|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); +// term.c_oflag &= ~OPOST; + term.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + term.c_cflag &= ~(CSIZE|PARENB); + term.c_cflag |= CS8; + term.c_cc[VMIN] = 1; + term.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSANOW, &term) == -1) err(EXIT_FAILURE, "tcsetattr"); @@ -55,13 +141,67 @@ main(void) setbuf(stdin, NULL); setbuf(stdout, NULL); - while ((c = getchar()) != 13) { - if (sl_keystroke(sl, c) == -1) - errx(EXIT_FAILURE, "sl_keystroke"); - printf("\r\033[2K%s", sl->buf); + /* open external source */ + snprintf(tail_cmd, sizeof tail_cmd, "exec tail -n %zd -f %s", + history_len, out_file); + if ((tail_fh = popen(tail_cmd, "r")) == NULL) + err(EXIT_FAILURE, "unable to open pipe to tail command"); + + pfd[0].fd = fd; + pfd[0].events = POLLIN; + + pfd[1].fd = fileno(tail_fh); + pfd[1].events = POLLIN; + + /* print initial prompt */ + fputs(prompt, stdout); + + for (;;) { + poll(pfd, 2, INFTIM); + + /* carriage return and erase the whole line */ + fputs("\r\033[2K", stdout); + + /* handle keyboard intput */ + if (pfd[0].revents & POLLIN) { + c = getchar(); + if (c == 13) { /* return */ + if (sl->len == 0 && empty_line == false) + continue; + line_output(sl, in_file); + sl_reset(sl); + } + if (sl_keystroke(sl, c) == -1) + errx(EXIT_FAILURE, "sl_keystroke"); + } + + /* handle tail command error and its broken pipe */ + if (pfd[1].revents & POLLHUP) + break; + + /* handle file intput */ + if (pfd[1].revents & POLLIN) { + char buf[BUFSIZ]; + ssize_t n = read(pfd[1].fd, buf, sizeof buf); + if (n == 0) + errx(EXIT_FAILURE, "tail command exited"); + if (n == -1) + err(EXIT_FAILURE, "read"); + if (write(STDOUT_FILENO, buf, n) == -1) + err(EXIT_FAILURE, "write"); + putchar('\a'); /* ring the bell on external input */ + } + + /* show current input line */ + fputs(prompt, stdout); + fputs(sl->buf, stdout); + + if (sl->cur < sl->len) { /* move the cursor */ + putchar('\r'); + /* HACK: because \033[0C does the same as \033[1C */ + if (sl->cur + prompt_len > 0) + printf("\033[%zdC", sl->cur + prompt_len); + } } - - puts("\r"); - return EXIT_SUCCESS; } diff --git a/slackline.c b/slackline.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <stdbool.h> #include "slackline.h" @@ -18,9 +19,7 @@ sl_init(void) return NULL; } - sl->buf[0] = '\0'; - sl->len = 0; - sl->cur = 0; + sl_reset(sl); return sl; } @@ -32,14 +31,54 @@ sl_free(struct slackline *sl) free(sl); } +void +sl_reset(struct slackline *sl) +{ + sl->buf[0] = '\0'; + sl->len = 0; + sl->cur = 0; + sl->esc = ESC_NONE; +} + int sl_keystroke(struct slackline *sl, int key) { if (sl == NULL || sl->len < sl->cur) return -1; + /* handle escape sequences */ + switch (sl->esc) { + case ESC_NONE: + break; + case ESC: + sl->esc = key == '[' ? ESC_BRACKET : ESC_NONE; + return 0; + case ESC_BRACKET: + switch (key) { + case 'A': /* up */ + case 'B': /* down */ + break; + case 'C': /* right */ + if (sl->cur < sl->len) + sl->cur++; + break; + case 'D': /* left */ + if (sl->cur > 0) + sl->cur--; + break; + case 'H': /* Home */ + sl->cur = 0; + break; + case 'F': /* End */ + sl->cur = sl->len; + break; + } + sl->esc = ESC_NONE; + return 0; + } + /* add character to buffer */ - if (key >= 32 && key <= 127) { + if (key >= 32 && key < 127) { if (sl->cur < sl->len) { memmove(sl->buf + sl->cur + 1, sl->buf + sl->cur, sl->len - sl->cur); @@ -54,12 +93,19 @@ sl_keystroke(struct slackline *sl, int key) /* handle ctl keys */ switch (key) { - case 8: /* backspace */ + case 27: /* Escape */ + sl->esc = ESC; + break; + case 127: /* backspace */ + case 8: /* backspace */ if (sl->cur == 0) break; + if (sl->cur < sl->len) + memmove(sl->buf + sl->cur - 1, sl->buf + sl->cur, + sl->len - sl->cur); sl->cur--; sl->len--; - sl->buf[sl->cur] = '\0'; + sl->buf[sl->len] = '\0'; break; } diff --git a/slackline.h b/slackline.h @@ -1,23 +1,21 @@ #ifndef SLACKLIINE_H #define SLACKLIINE_H -/* - * +-+-+-+-+-+ - * |c|c|c|0|0| - * +-+-+-+-+-+ - * ^ ^ - * len bufsize - */ +#include <stdbool.h> + +enum esc_seq {ESC_NONE, ESC, ESC_BRACKET}; struct slackline { char *buf; size_t bufsize; size_t len; size_t cur; + enum esc_seq esc; }; -struct slackline * sl_init(void); -void sl_free(struct slackline *); +struct slackline *sl_init(void); +void sl_free(struct slackline *sl); +void sl_reset(struct slackline *sl); int sl_keystroke(struct slackline *sl, int key); #endif