aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/example.pngbin672695 -> 0 bytes
-rw-r--r--.github/example2.pngbin964934 -> 0 bytes
-rw-r--r--README.md35
-rw-r--r--kirc.c444
4 files changed, 389 insertions, 90 deletions
diff --git a/.github/example.png b/.github/example.png
deleted file mode 100644
index 93ac0f4..0000000
--- a/.github/example.png
+++ /dev/null
Binary files differ
diff --git a/.github/example2.png b/.github/example2.png
deleted file mode 100644
index fe654fa..0000000
--- a/.github/example2.png
+++ /dev/null
Binary files differ
diff --git a/README.md b/README.md
index 96ef0ba..851d55b 100644
--- a/README.md
+++ b/README.md
@@ -29,16 +29,7 @@
* [TLS/SSL](https://en.m.wikipedia.org/wiki/Transport_Layer_Security) protocol capable (via external TLS utilities).
* Full chat history logging.
* Multi-channel joining at server connection.
-* Simple shortcut commands and full support for all IRC commands in the [RFC 2812](https://tools.ietf.org/html/rfc2812) standard:
-
-```shell
-<message> Send a PRIVMSG to the current channel.
-@<channel|nick> <message> Send a message to a specified channel or nick
-/<command> Send command to IRC server (see RFC 2812 for full list).
-/#<channel> Assign new default message channel.
-/? Print current message channel.
-```
-
+* Simple command aliases and full support for all [RFC 2812](https://tools.ietf.org/html/rfc2812) commands.
* Color scheme definition via [ANSI 8-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code), allowing for uniform color definition across all shell applications.
## Installation & Usage
@@ -75,6 +66,26 @@ Consult `man kirc` for a full list and explanation of available `kirc` arguments
kirc [-s hostname] [-p port] [-c channels] [-n nickname] [-r realname] [-u username] [-k password] [-a token] [-x command] [-w nick_width] [-o logfile] [-e|v|V]
```
+### Command Aliases
+
+```shell
+<message> Send a PRIVMSG to the current channel.
+@<channel|nick> <message> Send a message to a specified channel or nick
+/<command> Send command to IRC server (see RFC 2812 for full list).
+/#<channel> Assign new default message channel.
+/? Print current message channel.
+```
+
+### User Input Key Bindings
+
+* **CTRL+B** or **LEFT ARROW** moves the cursor one character to the left.
+* **CTRL+F** or **RIGHT ARROW** moves the cursor one character to the right.
+* **CTRL+A** moves the cursor to the end of the line.
+* **CTRL+E** or **HOME** moves the cursor to the start of the line.
+* **CTRL+W** deletes the previous word.
+* **CTRL+U** deletes the entire line.
+* **CTRL+K** deletes the from current character to end of line.
+
## Transport Layer Security (TLS) Support
There is no native TLS/SSL support. Instead, users can achieve this functionality by using third-party utilities (e.g. stunnel, [socat](https://linux.die.net/man/1/socat), ghosttunnel, etc).
@@ -154,10 +165,6 @@ Applying a new color scheme is easy! One of the quickest ways is to use an appli
* **KISS** is an acronym for [Keep It Simple Stupid](https://en.wikipedia.org/wiki/KISS_principle), which is a design principle noted by the U.S. Navy in 1960s. The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided.
* **POSIX** is an acronym for [Portable Operating System Interface](https://opensource.com/article/19/7/what-posix-richard-stallman-explains), which is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems. The *C99* Standard is preferred over other versions (e.g. *C89* or *C11*) since this currently the only one specified by [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html).
-## Known Bugs
-
-Some users may experience abnormal *BACKSPACE* key press behavior, particularly when trying to return to a previous line being editted. This has been confirmed to be an [upstream issue](https://github.com/mcpcpc/kirc/issues/39) and has been reported accordingly to the known impacted terminal emulators. While we wait for the upstream fixes, I would recommend using [urxvt](https://wiki.archlinux.org/index.php/Rxvt-unicode) or a terminal multiplexer (such as [screen](https://www.gnu.org/software/screen/) or [tmux](https://github.com/tmux/tmux/wiki)), which seemed to have resolved this by setting the [terminfo](https://osr507doc.sco.com/en/man/html.M/terminfo.M.html) `<bw>` flag as default. Alternatively, you can try to set terminfo `<bw>` flag manually by passing the command escape sequence `echo -e "\x1b[?45h"` or using the *tputs* function before starting *kirc*.
-
## Contact
For any further questions or concerns, feel free to reach out to me, [mcpcpc](https://github.com/mcpcpc), on `#kirc`
diff --git a/kirc.c b/kirc.c
index 2e0593b..deaf4de 100644
--- a/kirc.c
+++ b/kirc.c
@@ -13,7 +13,7 @@
#include <sys/socket.h>
#include <sys/ioctl.h>
-#define VERSION "0.1.3"
+#define VERSION "0.1.4"
#define MSG_MAX 512 /* max message length */
#define CHA_MAX 200 /* max channel length */
@@ -34,9 +34,326 @@ static char * real = NULL; /* real name */
static char * olog = NULL; /* chat log path*/
static char * inic = NULL; /* additional server command */
-static void
-log_append(char *str, char *path) {
+static struct termios orig;
+static int rawmode = 0;
+static int atexit_registered = 0;
+
+struct State {
+ int ifd; /* Terminal stdin file descriptor. */
+ int ofd; /* Terminal stdout file descriptor. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ size_t pos; /* Current cursor position. */
+ size_t oldpos; /* Previous refresh cursor position. */
+ size_t len; /* Current edited line length. */
+ size_t cols; /* Number of columns in terminal. */
+};
+
+static void disableRawMode() {
+ if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig) != -1)
+ rawmode = 0;
+}
+
+static int enableRawMode(int fd) {
+ struct termios raw;
+
+ if (!isatty(STDIN_FILENO)) {
+ errno = ENOTTY;
+ return -1;
+ }
+ if (!atexit_registered) {
+ atexit(disableRawMode);
+ atexit_registered = 1;
+ }
+ if (tcgetattr(fd,&orig) == -1) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ raw = orig;
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ raw.c_oflag &= ~(OPOST);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ raw.c_cc[VMIN] = 1;
+ raw.c_cc[VTIME] = 0;
+
+ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ rawmode = 1;
+ return 0;
+}
+
+static int getCursorPosition(int ifd, int ofd) {
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
+
+ while (i < sizeof(buf)-1) {
+ if (read(ifd,buf+i,1) != 1) break;
+ if (buf[i] == 'R') break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ if (buf[0] != 27 || buf[1] != '[') return -1;
+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
+ return cols;
+}
+
+static int getColumns(int ifd, int ofd) {
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ int start, cols;
+
+ start = getCursorPosition(ifd,ofd);
+ if (start == -1) return 80;
+
+ if (write(ofd,"\x1b[999C",6) != 6) return 80;
+ cols = getCursorPosition(ifd,ofd);
+ if (cols == -1) return 80;
+ if (cols > start) {
+ char seq[32];
+ snprintf(seq,32,"\x1b[%dD",cols-start);
+ if (write(ofd,seq,strlen(seq)) == -1) {}
+ }
+ return cols;
+ } else {
+ return ws.ws_col;
+ }
+}
+
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+static void refreshLine(struct State *l) {
+ char seq[64];
+ int fd = l->ofd;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+
+ while(pos >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while (len > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ abAppend(&ab,buf,len);
+ snprintf(seq,64,"\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+ snprintf(seq,64,"\r\x1b[%dC", (int)(pos));
+ abAppend(&ab,seq,strlen(seq));
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+static int editInsert(struct State *l, char c) {
+ if (l->len < l->buflen) {
+ if (l->len == l->pos) {
+ l->buf[l->pos] = c;
+ l->pos++;
+ l->len++;
+ l->buf[l->len] = '\0';
+ if ((l->len < l->cols)) {
+ char d = c;
+ if (write(l->ofd,&d,1) == -1) return -1;
+ } else {
+ refreshLine(l);
+ }
+ } else {
+ memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
+ l->buf[l->pos] = c;
+ l->len++;
+ l->pos++;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+ }
+ return 0;
+}
+
+static void editMoveLeft(struct State *l) {
+ if (l->pos > 0) {
+ l->pos--;
+ refreshLine(l);
+ }
+}
+
+static void editMoveRight(struct State *l) {
+ if (l->pos != l->len) {
+ l->pos++;
+ refreshLine(l);
+ }
+}
+
+static void editMoveHome(struct State *l) {
+ if (l->pos != 0) {
+ l->pos = 0;
+ refreshLine(l);
+ }
+}
+
+static void editMoveEnd(struct State *l) {
+ if (l->pos != l->len) {
+ l->pos = l->len;
+ refreshLine(l);
+ }
+}
+
+static void editDelete(struct State *l) {
+ if (l->len > 0 && l->pos < l->len) {
+ memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+static void editBackspace(struct State *l) {
+ if (l->pos > 0 && l->len > 0) {
+ memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
+ l->pos--;
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+static void editDeletePrevWord(struct State *l) {
+ size_t old_pos = l->pos;
+ size_t diff;
+
+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
+ l->pos--;
+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
+ l->pos--;
+ diff = old_pos - l->pos;
+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+ l->len -= diff;
+ refreshLine(l);
+}
+
+static int edit(char *buf, size_t buflen)
+{
+ struct State l;
+
+ l.ifd = STDIN_FILENO;
+ l.ofd = STDOUT_FILENO;
+ l.buf = buf;
+ l.buflen = buflen;
+ l.oldpos = l.pos = 0;
+ l.len = 0;
+ l.cols = getColumns(STDIN_FILENO, STDOUT_FILENO);
+
+ l.buf[0] = '\0';
+ l.buflen--; /* Make sure there is always space for the nulterm */
+
+ while(1) {
+ int nread;
+ char c, seq[3];
+
+ nread = read(l.ifd,&c,1);
+ if (nread <= 0) return l.len;
+
+ switch(c) {
+ case 3: /* ctrl-c */
+ errno = EAGAIN;
+ return -1;
+ case 4:
+ if (l.len > 0) {
+ editDelete(&l);
+ } else {
+ return -1;
+ }
+ break;
+ case 13: return (int)l.len; /* enter */
+ case 127: /* backspace */
+ case 8: editBackspace(&l); break; /* backspace */
+ case 2: editMoveLeft(&l); break; /* ctrl+b */
+ case 6: editMoveRight(&l); break; /* ctrl+f */
+ case 1: editMoveHome(&l); break; /* ctrl+a */
+ case 5: editMoveEnd(&l); break; /* ctrl+e */
+ case 23: editDeletePrevWord(&l); break; /* ctrl+w */
+ case 27: /* esc sequence */
+ if (read(l.ifd,seq,1) == -1) break;
+ if (read(l.ifd,seq+1,1) == -1) break;
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ if (read(l.ifd,seq+2,1) == -1) break;
+ if (seq[2] == '~') {
+ switch(seq[1]) {
+ case '3': editDelete(&l); break;
+ }
+ }
+ } else {
+ switch(seq[1]) {
+ case 'C': editMoveRight(&l); break;
+ case 'D': editMoveLeft(&l); break;
+ case 'H': editMoveHome(&l); break;
+ case 'F': editMoveEnd(&l); break;
+ }
+ }
+ } else if (seq[0] == 'O') {
+ switch(seq[1]) {
+ case 'H': editMoveHome(&l); break;
+ case 'F': editMoveEnd(&l); break;
+ }
+ }
+ break;
+ case 21: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ l.pos = l.len = 0;
+ refreshLine(&l);
+ break;
+ case 11: /* Ctrl+k, delete from current to end of line. */
+ buf[l.pos] = '\0';
+ l.len = l.pos;
+ refreshLine(&l);
+ break;
+ default:
+ if (editInsert(&l,c)) return -1;
+ break;
+ }
+ }
+ return l.len;
+}
+
+static void logAppend(char *str, char *path) {
FILE *out;
if ((out = fopen(path, "a")) == NULL) {
@@ -48,9 +365,7 @@ log_append(char *str, char *path) {
fclose(out);
}
-static void
-raw(char *fmt, ...) {
-
+static void raw(char *fmt, ...) {
va_list ap;
char *cmd_str = malloc(MSG_MAX);
@@ -64,7 +379,7 @@ raw(char *fmt, ...) {
va_end(ap);
if (verb) printf("<< %s", cmd_str);
- if (olog) log_append(cmd_str, olog);
+ if (olog) logAppend(cmd_str, olog);
if (write(conn, cmd_str, strlen(cmd_str)) < 0) {
perror("write");
exit(EXIT_FAILURE);
@@ -73,9 +388,7 @@ raw(char *fmt, ...) {
free(cmd_str);
}
-static int
-connection_initialize(void) {
-
+static int connectionInit(void) {
int gai_status;
struct addrinfo *res, hints = {
.ai_family = AF_UNSPEC,
@@ -115,18 +428,10 @@ connection_initialize(void) {
return 0;
}
-static void
-message_wrap(char *line, size_t offset) {
-
- struct winsize window_dims;
-
- if (ioctl(0, TIOCGWINSZ, &window_dims) < 0) {
- perror("ioctrl");
- exit(EXIT_FAILURE);
- }
+static void messageWrap(char *line, size_t offset) {
- unsigned short cmax = window_dims.ws_col;
char *tok;
+ size_t cmax = getColumns(STDIN_FILENO, STDOUT_FILENO);
size_t wordwidth, spaceleft = cmax - gutl - offset, spacewidth = 1;
for (tok = strtok(line, " "); tok != NULL; tok = strtok(NULL, " ")) {
@@ -140,12 +445,10 @@ message_wrap(char *line, size_t offset) {
spaceleft -= (wordwidth + spacewidth);
}
- putchar('\n');
+ puts("\x1b[0m");
}
-static void
-raw_parser(char *string) {
-
+static void rawParser(char *string) {
if (verb) printf(">> %s", string);
if (!strncmp(string, "PING", 4)) {
@@ -156,7 +459,7 @@ raw_parser(char *string) {
if (string[0] != ':') return;
- if (olog) log_append(string, olog);
+ if (olog) logAppend(string, olog);
char *tok, *prefix = strtok(string, " ") + 1, *suffix = strtok(NULL, ":"),
*message = strtok(NULL, "\r"), *nickname = strtok(prefix, "!"),
@@ -176,23 +479,27 @@ raw_parser(char *string) {
printf("%*s--> \x1b[32;1m%s\x1b[0m\n", g - 3, "", nickname);
return;
} else if (!strncmp(command, "NICK", 4)) {
- printf("\x1b[35;1m%*s\x1b[0m --> \x1b[35;1m%s\x1b[0m\n", g - 4, nickname, message);
+ printf("\x1b[35;1m%*s\x1b[0m ", g - 4, nickname);
+ printf("--> \x1b[35;1m%s\x1b[0m\n", message);
return;
- } else if (!strncmp(command, "PRIVMSG", 7) && strcmp(channel, nick) == 0) {
- printf("%*s\x1b[43;1m%-.*s\x1b[0m ", s, "", g, nickname);
- } else if (!strncmp(command, "PRIVMSG", 7) && strstr(channel, chan_default) == NULL) {
- printf("%*s\x1b[33;1m%-.*s\x1b[0m [\x1b[33m%s\x1b[0m] ", s, "", \
- g, nickname, channel);
- offset += 12 + strlen(channel);
- } else printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", g, nickname);
- message_wrap((message ? message : " "), offset);
+ } else if (!strncmp(command, "PRIVMSG", 7)) {
+ if (strcmp(channel, nick) == 0) {
+ printf("%*s\x1b[33;1m%-.*s\x1b[36m ", s, "", g, nickname);
+ } else if (strstr(channel, chan_default) == NULL) {
+ printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", g, nickname);
+ printf("[\x1b[33m%s\x1b[0m] ", channel);
+ offset += 12 + strlen(channel);
+ } else printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", g, nickname);
+ } else {
+ printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", g, nickname);
+ }
+ messageWrap((message ? message : " "), offset);
}
static char message_buffer[MSG_MAX + 1];
static size_t message_end = 0;
-static int
-handle_server_message(void) {
+static int handleServerMessage(void) {
for (;;) {
ssize_t sl = read(conn, &message_buffer[message_end], MSG_MAX - message_end);
if (sl == -1) {
@@ -215,7 +522,7 @@ handle_server_message(void) {
if (i != 0 && message_buffer[i - 1] == '\r' && message_buffer[i] == '\n') {
char saved_char = message_buffer[i + 1];
message_buffer[i + 1] = '\0';
- raw_parser(message_buffer);
+ rawParser(message_buffer);
message_buffer[i + 1] = saved_char;
memmove(&message_buffer, &message_buffer[i + 1], message_end - i - 1);
message_end = message_end - i - 1;
@@ -228,63 +535,39 @@ handle_server_message(void) {
}
}
-static void
-handle_user_input(void) {
-
- char usrin[MSG_MAX], *tok;
-
- if (fgets(usrin, MSG_MAX, stdin) == NULL) {
- perror("fgets");
- exit(EXIT_FAILURE);
- }
-
+static void handleUserInput(char *usrin) {
+ char *tok;
size_t msg_len = strlen(usrin);
+
if (usrin[msg_len - 1] == '\n') {
usrin[msg_len - 1] = '\0';
}
if (usrin[0] == '/' && usrin[1] == '#') {
strcpy(chan_default, usrin + 2);
- printf("new channel: #%s\n", chan_default);
- } else if (usrin[0] == '/' && usrin[1] == '?' && msg_len == 3) {
- printf("current channel: #%s\n", chan_default);
+ printf("\x1b[35mnew channel: #%s\x1b[0m\x1b[0F\n\n", chan_default);
+ } else if (usrin[0] == '/' && usrin[1] == '?') {
+ printf("\x1b[35mcurrent channel: #%s\x1b[0m\x1b[0F\n\n", chan_default);
} else if (usrin[0] == '/') {
raw("%s\r\n", usrin + 1);
} else if (usrin[0] == '@') {
strtok_r(usrin, " ", &tok);
raw("privmsg %s :%s\r\n", usrin + 1, tok);
+ printf("\x1b[35mprivmsg %s :%s\x1b[0m\x1b[0F\n\n", usrin + 1, tok);
} else {
raw("privmsg #%s :%s\r\n", chan_default, usrin);
+ printf("\x1b[35mprivmsg #%s :%s\x1b[0m\x1b[0F\n\n", chan_default, usrin);
}
}
-static int
-keyboard_hit() {
-
- struct termios save, tp;
- int byteswaiting;
-
- tcgetattr(STDIN_FILENO, &tp);
- save = tp;
- tp.c_lflag &= ~ICANON;
- tcsetattr(STDIN_FILENO, TCSANOW, &tp);
- ioctl(STDIN_FILENO, FIONREAD, &byteswaiting);
- tcsetattr(STDIN_FILENO, TCSANOW, &save);
-
- return byteswaiting;
-}
-
-static void
-usage(void) {
+static void usage(void) {
fputs("kirc [-s host] [-p port] [-c channel] [-n nick] [-r realname] \
-[-u username] [-k password] [-a token] [-x command] [-w columns] [-o path] \
+[-u username] [-k password] [-a token] [-x command] [-w nickwidth] [-o path] \
[-e|v|V]\n", stderr);
exit(EXIT_FAILURE);
}
-int
-main(int argc, char **argv) {
-
+int main(int argc, char **argv) {
int cval;
while ((cval = getopt(argc, argv, "s:p:o:n:k:c:u:r:x:w:a:evV")) != -1) {
@@ -312,7 +595,7 @@ main(int argc, char **argv) {
usage();
}
- if (connection_initialize() != 0) {
+ if (connectionInit() != 0) {
return EXIT_FAILURE;
}
@@ -332,18 +615,27 @@ main(int argc, char **argv) {
fds[0].events = POLLIN;
fds[1].events = POLLIN;
+ char usrin[MSG_MAX];
+ int count, byteswaiting = 1;
+
for (;;) {
int poll_res = poll(fds, 2, -1);
if (poll_res != -1) {
if (fds[0].revents & POLLIN) {
- handle_user_input();
+ byteswaiting = 0;
+ edit(usrin, MSG_MAX);
+ printf("\n\x1b[0F\x1b[0K");
+ handleUserInput(usrin);
+ byteswaiting = 1;
}
- if (fds[1].revents & POLLIN && (keyboard_hit() < 1)) {
- int rc = handle_server_message();
+ if (fds[1].revents & POLLIN && byteswaiting) {
+ disableRawMode();
+ int rc = handleServerMessage();
if (rc != 0) {
if (rc == -2) return EXIT_FAILURE;
return EXIT_SUCCESS;
};
+ if (enableRawMode(STDIN_FILENO) == -1) return -1;
}
} else {
if (errno == EAGAIN) continue;