wip - lchat - A line oriented chat front end for ii. | |
git clone git://git.suckless.org/lchat | |
Log | |
Files | |
Refs | |
README | |
--- | |
commit 98fa5e3861a515b0b9f7b212e7eb5abf550e1f33 | |
parent b1db0ac6e9850a858120ac43596405208a3fc581 | |
Author: Jan Klemkow <[email protected]> | |
Date: Mon, 26 Oct 2015 22:17:57 +0100 | |
wip | |
Diffstat: | |
M lchat.c | 174 ++++++++++++++++++++++++++++-… | |
M slackline.c | 58 +++++++++++++++++++++++++++--… | |
M 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]\… | |
+ 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 inp… | |
+ } | |
+ | |
+ /* 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 |