aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/CONTRIBUTING21
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md38
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md20
-rw-r--r--Makefile34
-rw-r--r--README162
-rw-r--r--kirc.c1499
6 files changed, 891 insertions, 883 deletions
diff --git a/.github/CONTRIBUTING b/.github/CONTRIBUTING
deleted file mode 100644
index e9d9f41..0000000
--- a/.github/CONTRIBUTING
+++ /dev/null
@@ -1,21 +0,0 @@
-CONTRIBUTING
-------------
-
-Want to contribute? Great! First, read this page.
-
-Code reviews:
-- All submissions, including submissions by project members, require review. We
- use Github pull requests for this purpose.
-
-Some tips for good pull requests:
-- Use our code
- When in doubt, try to stay true to the existing code of the project.
-- Write a descriptive commit message. What problem are you solving and what
- are the consequences? Where and what did you test? Some good tips:
- [here](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message)
- and [here](https://www.kernel.org/doc/Documentation/SubmittingPatches).
-- If your PR consists of multiple commits which are successive improvements /
- fixes to your first commit, consider squashing them into a single commit
- (`git rebase -i`) such that your PR is a single commit on top of the current
- HEAD. This make reviewing the code so much easier, and our history more
- readable.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index dd84ea7..0000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,38 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: ''
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain your problem.
-
-**Desktop (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
- - Version [e.g. 22]
-
-**Smartphone (please complete the following information):**
- - Device: [e.g. iPhone6]
- - OS: [e.g. iOS8.1]
- - Browser [e.g. stock browser, safari]
- - Version [e.g. 22]
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index bbcbbe7..0000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this project
-title: ''
-labels: ''
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/Makefile b/Makefile
index 88e0f8d..28a6b2a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,31 +1,27 @@
.POSIX:
-
+ALL_WARNING = -Wall -Wextra -pedantic
+ALL_LDFLAGS = $(LDFLAGS)
+ALL_CFLAGS = $(CPPFLAGS) $(CFLAGS) -std=c99 $(ALL_WARNING)
PREFIX = /usr/local
+LDLIBS = -lm
BINDIR = $(PREFIX)/bin
-MANPREFIX = $(PREFIX)/share/man
+MANDIR = $(PREFIX)/share/man
all: kirc
-
-kirc: kirc.o Makefile
- $(CC) -o kirc kirc.o $(LDFLAGS)
-
-.c.o:
- $(CC) $(CPPFLAGS) $(CFLAGS) -c $<
-
install: all
mkdir -p $(DESTDIR)$(BINDIR)
- mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ mkdir -p $(DESTDIR)$(MANDIR)/man1
cp -f kirc $(DESTDIR)$(BINDIR)
+ cp -f kirc.1 $(DESTDIR)$(MANDIR)/man1
chmod 755 $(DESTDIR)$(BINDIR)/kirc
- version=$$(sed -n '/#define VERSION/{s/^[^"]*"//;s/".*//;p;q}' kirc.c); \
- sed "s/VERSION/$$version/g" kirc.1 > $(DESTDIR)$(MANPREFIX)/man1/kirc.1
- chmod 644 $(DESTDIR)$(MANPREFIX)/man1/kirc.1
-
-uninstall:
- rm -f $(DESTDIR)$(BINDIR)/kirc
- rm -f $(DESTDIR)$(MANPREFIX)/man1/kirc.1
-
+ chmod 644 $(DESTDIR)$(MANDIR)/man1/kirc.1
+kirc: kirc.o
+ $(CC) $(ALL_LDFLAGS) -o kirc kirc.o $(LDLIBS)
+kirc.o: kirc.c
+ $(CC) $(ALL_CFLAGS) -c kirc.c
clean:
rm -f kirc *.o
-
+uninstall:
+ rm -f $(DESTDIR)$(BINDIR)/kirc
+ rm -f $(DESTDIR)$(MANDIR)/man1/kirc.1
.PHONY: all install uninstall clean
diff --git a/README b/README
index 1a451b5..be6a73f 100644
--- a/README
+++ b/README
@@ -1,99 +1,63 @@
-<!-- vim: syntax=Markdown -->
-<!--
- Title: KISS for IRC (kirc)
- Description: A tiny IRC client written in POSIX C99.
- Author: mcpcpc
--->
-
-<h3 align="center">
- <img src="https://raw.githubusercontent.com/mcpcpc/kirc/gh-pages/kirc.png" alt="kirc" height="170px">
-</h3>
-
-<p align="center">KISS for IRC, a tiny IRC client written in POSIX C99.</p>
-
-<p align="center">
- <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
- <a href="https://github.com/mcpcpc/kirc/releases"><img src="https://img.shields.io/github/v/release/mcpcpc/kirc.svg"></a>
- <a href="https://repology.org/metapackage/kirc"><img src="https://repology.org/badge/tiny-repos/kirc.svg" alt="Packaging status"></a>
- <a href="https://www.codacy.com/manual/mcpcpc/kirc/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=mcpcpc/kirc&amp;utm_campaign=Badge_Grade"><img src="https://app.codacy.com/project/badge/Grade/5616c0ed4b2f4209826038dbc270dbf5" alt="Codacy status"></a>
-</p>
-
-## Features
-
-* Excellent cross-platform compatibility.
-* Asynchronous user input and server messager handling.
-* No dependencies other than a C99 compiler.
-* Simple Authentication and Security Layer (SASL) procotol support.
-* Client-to-client protocol (CTCP) support.
-* Transport Layer Security (TLS) protocol support (via external utilities).
-* Full chat history logging.
-* Multi-channel joining at server connection.
-* Simple command aliases and full support for all RFC 2812 commands.
-* Easy color scheme definition via ANSI 8-bit colors.
-
-## Installation & Usage
-
-Building and installing on **KISS Linux** using the Community repository:
-
-```shell
-kiss b kirc
-kiss i kirc
-```
-
-Building and installing on **Arch** and **Arch-based** distros using the AUR:
-
-```shell
-git clone https://aur.archlinux.org/kirc-git.git
-cd kirc
-makepkg -si
-```
-
-Building and installing from source (works on **Raspbian**, **Debian**, **Ubuntu** and many other Unix distributions):
-
-```shell
-git clone https://github.com/mcpcpc/kirc.git
-cd kirc
-make
-make install
-```
-
-### Usage
-
-Consult `man kirc` for a full list and explanation of available `kirc` arguments.
-
-```shell
-kirc [-s hostname] [-p port] [-c channels] [-n nickname] [-r realname] [-u username] [-k password] [-a token] [-x command] [-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
-@@<channel|nick> <message> Send a CTCP ACTION 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.
-```
-
-### 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+E** moves the cursor to the end of the line.
-* **CTRL+A** 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.
-* **CTRL+C** Force quit kirc.
-* **CTRL+D** deletes the character to the right of cursor.
-* **CTRL+T** swap character at cursor with previous character.
-* **CTRL+H** equivalent to backspace.
-
-## Support Documentation
-
-Please refer to the official [Support Documention](https://mcpcpc.github.io/kirc/documentation.html) for examples, troubleshooting and use cases.
-
-## Contact
-
-For any further questions or concerns, feel free to reach out to me, [mcpcpc](https://github.com/mcpcpc), on `#kirc`
-or `#kisslinux` Freenode IRC channels.
+kirc
+====
+
+kirc (KISS for IRC) is a tiny IRC client written in POSIX C99.
+
+Installation
+------------
+
+Building and installing from source:
+
+ git clone https://github.com/mcpcpc/kirc
+ cd kirc
+ make
+ make install
+
+Usage
+-----
+
+Consult `man kirc` for a full list and explanation of available arguments.
+
+ kirc [-s hostname] [-p port] [-c channels] [-n nickname] [-r realname]
+ [-u username] [-k password] [-a token] [-x command] [-o logfile]
+ [-e|v|V]
+
+Command Aliases
+---------------
+
+ <message> send PRIVMSG to the current channel.
+ @<channel|nick> <message> send message to a specified channel or nick.
+ @@<channel|nick> <message> send CTCP ACTION message to a specified channel or nick.
+ /<command> send command to the IRC server (see RFC 2812).
+ /#<channel> assign new default message channel.
+
+Key Bindings
+------------
+
+ CTRL+B or LEFT ARROW move the cursor one character to the left.
+ CTRL+F or RIGHT ARROW move the cursor one character to the right.
+ CTRL+P or UP ARROW move to previous record in the input history buffer.
+ CTRL+N or DOWN ARROW move to next record in the input history buffer.
+ CTRL+E move the cursor to the end of the line.
+ CTRL+A or HOME move the cursor to the start of the line.
+ CTRL+W delete the previous word.
+ CTRL+U delete the entire line.
+ CTRL+K delete the from current character to end of line.
+ CTRL+D delete the character to the right of cursor.
+ CTRL+C force quit kirc.
+ CTRL+T swap character at cursor with previous character.
+ CTRL+H equivalent to backspace.
+
+Support Documentation
+---------------------
+
+Please refer to the official for examples, troubleshooting and use cases.
+
+ https://mcpcpc.github.io/kirc/documentation.html
+
+Contact
+-------
+
+For any further questions or concerns, feel free to email me at:
+
+ michaelczigler [at] mcpcpc [dot] com
diff --git a/kirc.c b/kirc.c
index 4665128..ffc514d 100644
--- a/kirc.c
+++ b/kirc.c
@@ -13,728 +13,855 @@
#include <termios.h>
#include <sys/ioctl.h>
-#define VERSION "0.2.4" /* version */
-#define MSG_MAX 512 /* max message length */
-#define CHA_MAX 200 /* max channel length */
-#define NIC_MAX 26 /* max nickname length */
-#define CTCP_CMDS "ACTION VERSION TIME CLIENTINFO PING"
-
-static char cdef[MSG_MAX] = "?"; /* default PRIVMSG channel */
-static int conn; /* connection socket */
-static int verb = 0; /* verbose output */
-static int sasl = 0; /* SASL method */
-static char * host = "irc.freenode.net"; /* host address */
-static char * port = "6667"; /* port */
-static char * chan = NULL; /* channel(s) */
-static char * nick = NULL; /* nickname */
-static char * pass = NULL; /* password */
-static char * user = NULL; /* user name */
-static char * auth = NULL; /* PLAIN SASL token */
-static char * real = NULL; /* real name */
-static char * olog = NULL; /* chat log path*/
-static char * inic = NULL; /* additional server command */
+#define CTCP_CMDS "ACTION VERSION TIME CLIENTINFO PING"
+#define VERSION "0.2.5"
+#define MSG_MAX 512
+#define CHA_MAX 200
+#define NIC_MAX 26
+#define HIS_MAX 100
+
+static char cdef[MSG_MAX] = "?"; /* default PRIVMSG channel */
+static int conn; /* connection socket */
+static int verb = 0; /* verbose output */
+static int sasl = 0; /* SASL method */
+static char *host = "irc.freenode.net"; /* host address */
+static char *port = "6667"; /* port */
+static char *chan = NULL; /* channel(s) */
+static char *nick = NULL; /* nickname */
+static char *pass = NULL; /* password */
+static char *user = NULL; /* user name */
+static char *auth = NULL; /* PLAIN SASL token */
+static char *real = NULL; /* real name */
+static char *olog = NULL; /* chat log path*/
+static char *inic = NULL; /* additional server command */
struct Param {
- char * prefix;
- char * suffix;
- char * message;
- char * nickname;
- char * command;
- char * channel;
- char * params;
- size_t offset;
- size_t maxcols;
- int nicklen;
+ char *prefix;
+ char *suffix;
+ char *message;
+ char *nickname;
+ char *command;
+ char *channel;
+ char *params;
+ size_t offset;
+ size_t maxcols;
+ int nicklen;
};
-static struct termios orig; /* restore at exit. */
-static int rawmode = 0; /* check if restore is needed */
-static int atexit_registered = 0; /* register atexit() */
+static struct termios orig;
+static int rawmode = 0;
+static int atexit_registered = 0;
+static int history_max_len = HIS_MAX;
+static int history_len = 0;
+static char **history = NULL;
struct State {
- char * prompt; /* Prompt to display. */
- char * buf; /* Edited line buffer. */
- size_t buflen; /* Edited line buffer size. */
- size_t plen; /* Prompt length. */
- 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. */
+ char *prompt; /* Prompt to display. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ size_t plen; /* Prompt length. */
+ 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. */
+ int history_index; /* Current line in the edit history */
};
struct abuf {
- char * b;
- int len;
+ char *b;
+ int len;
};
+static void freeHistory(void) {
+ if (history) {
+ int j;
+ for (j = 0; j < history_len; j++) {
+ free(history[j]);
+ }
+ free(history);
+ }
+}
+
static void disableRawMode(void) {
- if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig) != -1)
- rawmode = 0;
+ if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig) != -1) {
+ rawmode = 0;
+ }
+ freeHistory();
}
static int enableRawMode(int fd) {
- if (!isatty(STDIN_FILENO))
- goto fatal;
- if (!atexit_registered) {
- atexit(disableRawMode);
- atexit_registered = 1;
- }
- if (tcgetattr(fd,&orig) == -1)
- goto fatal;
- struct termios 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)
- goto fatal;
- rawmode = 1;
- return 0;
+ if (!isatty(STDIN_FILENO)) {
+ goto fatal;
+ }
+ if (!atexit_registered) {
+ atexit(disableRawMode);
+ atexit_registered = 1;
+ }
+ if (tcgetattr(fd,&orig) == -1) {
+ goto fatal;
+ }
+ struct termios 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) {
+ goto fatal;
+ }
+ rawmode = 1;
+ return 0;
fatal:
- errno = ENOTTY;
- return -1;
+ errno = ENOTTY;
+ return -1;
}
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;
+ 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 = getCursorPosition(ifd, ofd);
- if (start == -1)
- return 80;
- if (write(ofd,"\x1b[999C",6) != 6)
- return 80;
- int cols = getCursorPosition(ifd, ofd);
- if (cols == -1)
- return 80;
- if (cols > start) {
- char seq[32];
- snprintf(seq, sizeof(seq), "\x1b[%dD", cols - start);
- if (write(ofd, seq, strnlen(seq, MSG_MAX)) == -1) {}
- }
- return cols;
- } else {
- return ws.ws_col;
- }
-}
-
-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];
- size_t plen = strlen(l->prompt) + 2;
- int fd = STDOUT_FILENO;
- char * buf = l->buf;
- size_t len = l->len;
- size_t pos = l->pos;
- struct abuf ab;
- l->cols = getColumns(STDIN_FILENO, STDOUT_FILENO);
- while (plen + pos >= l->cols) {
- buf++;
- len--;
- pos--;
- }
- while (plen + len > l->cols) {
- len--;
- }
-
- abInit(&ab);
- snprintf(seq, sizeof(seq), "\r");
- abAppend(&ab, seq, strnlen(seq, MSG_MAX));
- abAppend(&ab,l->prompt, strnlen(l->prompt, MSG_MAX));
- abAppend(&ab, "> ", 2);
- abAppend(&ab, buf, len);
- snprintf(seq, sizeof(seq), "\x1b[0K");
- abAppend(&ab, seq, strnlen(seq, MSG_MAX));
- snprintf(seq, sizeof(seq), "\r\x1b[%dC", (int)(pos + plen));
- abAppend(&ab, seq, strlen(seq));
- if (write(fd, ab.b, ab.len) == -1) {}
- 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->plen + l->len < l->cols) {
- char d = c;
- if (write(STDOUT_FILENO, &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;
- while (l->pos > 0 && l->buf[l->pos - 1] == ' ')
- l->pos--;
- while (l->pos > 0 && l->buf[l->pos - 1] != ' ')
- l->pos--;
- size_t 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 void editDeleteWholeLine(struct State * l) {
- l->buf[0] = '\0';
- l->pos = l->len = 0;
- refreshLine(l);
-}
-
-static void editDeleteLineToEnd(struct State * l) {
- l->buf[l->pos] = '\0';
- l->len = l->pos;
- refreshLine(l);
-}
-
-static void editSwapCharWithPrev(struct State * l) {
- if (l->pos > 0 && l->pos < l->len) {
- int aux = l->buf[l->pos - 1];
- l->buf[l->pos - 1] = l->buf[l->pos];
- l->buf[l->pos] = aux;
- if (l->pos != l->len - 1)
- l->pos++;
- refreshLine(l);
- }
-}
-
-static int edit(struct State * l) {
- char c, seq[3];
- ssize_t nread = read(STDIN_FILENO, &c, 1);
- if (nread <= 0)
- return 1;
- switch(c) {
- case 13: return 1; /* enter */
- case 3: errno = EAGAIN; return -1; /* ctrl-c */
- case 127: /* backspace */
- case 8: editBackspace(l); break; /* ctrl-h */
- 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 21: editDeleteWholeLine(l); break; /* Ctrl+u */
- case 11: editDeleteLineToEnd(l); break; /* Ctrl+k */
- case 20: editSwapCharWithPrev(l); break; /* ctrl-t */
- case 4: /* ctrl-d */
- if (l->len > 0) {
- editDelete(l);
- } else {
- return -1;
- }
- break;
- case 27: /* escape sequence */
- if (read(STDIN_FILENO, seq, 1) == -1) break;
- if (read(STDIN_FILENO, seq + 1, 1) == -1) break;
- if (seq[0] == '[') { /* ESC [ sequences. */
- if (seq[1] >= '0' && seq[1] <= '9') {
- /* Extended escape, read additional byte. */
- if (read(STDIN_FILENO, seq + 2, 1) == -1) break;
- if (seq[2] == '~') {
- if (seq[1] == 3) editDelete(l); /* Delete key. */
- }
- } else {
- switch(seq[1]) {
- case 'C': editMoveRight(l); break; /* Right */
- case 'D': editMoveLeft(l); break; /* Left */
- case 'H': editMoveHome(l); break; /* Home */
- case 'F': editMoveEnd(l); break; /* End*/
- }
- }
- }
- else if (seq[0] == 'O') { /* ESC O sequences. */
- switch(seq[1]) {
- case 'H': editMoveHome(l); break; /* Home */
- case 'F': editMoveEnd(l); break; /* End*/
- }
- }
- break;
- default: if (editInsert(l, c)) return -1; break;
- }
- return 0;
-}
-
-static void stateReset(struct State * l) {
- l->plen = strlen(l->prompt);
- l->oldpos = 0;
- l->pos = 0;
- l->len = 0;
- l->buf[0] = '\0';
- l->buflen--;
-}
-
-static char * ctime_now(char buf[26]) {
- struct tm tm_;
- time_t now = time(NULL);
- if (!asctime_r(localtime_r (&now, &tm_), buf))
- return NULL;
- *strchr(buf, '\n') = '\0';
- return buf;
-}
-
-static void logAppend(char * str, char * path) {
- FILE * out;
- char buf[26];
- if ((out = fopen(path, "a")) == NULL) {
- perror("fopen");
- exit(1);
- }
- ctime_now(buf);
- fprintf(out, "%s:%s", buf, str);
- fclose(out);
-}
-
-static void raw(char * fmt, ...) {
- va_list ap;
- char *cmd_str = malloc(MSG_MAX);
- if (!cmd_str) {
- perror("malloc");
- exit(1);
- }
- va_start(ap, fmt);
- vsnprintf(cmd_str, MSG_MAX, fmt, ap);
- va_end(ap);
- if (verb)
- printf("<< %s", cmd_str);
- if (olog)
- logAppend(cmd_str, olog);
- if (write(conn, cmd_str, strnlen(cmd_str, MSG_MAX)) < 0) {
- perror("write");
- exit(1);
- }
- free(cmd_str);
+ struct winsize ws;
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ int start = getCursorPosition(ifd, ofd);
+ if (start == -1) {
+ return 80;
+ }
+ if (write(ofd,"\x1b[999C",6) != 6) {
+ return 80;
+ }
+ int cols = getCursorPosition(ifd, ofd);
+ if (cols == -1) {
+ return 80;
+ }
+ if (cols > start) {
+ char seq[32];
+ snprintf(seq, sizeof(seq), "\x1b[%dD", cols - start);
+ if (write(ofd, seq, strnlen(seq, MSG_MAX)) == -1) {}
+ }
+ return cols;
+ } else {
+ return ws.ws_col;
+ }
+}
+
+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];
+ size_t plen = strlen(l->prompt) + 2;
+ int fd = STDOUT_FILENO;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+ l->cols = getColumns(STDIN_FILENO, STDOUT_FILENO);
+ while ((plen + pos) >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while ((plen + len) > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ snprintf(seq, sizeof(seq), "\r");
+ abAppend(&ab, seq, strnlen(seq, MSG_MAX));
+ abAppend(&ab,l->prompt, strnlen(l->prompt, MSG_MAX));
+ abAppend(&ab, "> ", 2);
+ abAppend(&ab, buf, len);
+ snprintf(seq, sizeof(seq), "\x1b[0K");
+ abAppend(&ab, seq, strnlen(seq, MSG_MAX));
+ snprintf(seq, sizeof(seq), "\r\x1b[%dC", (int)(pos + plen));
+ abAppend(&ab, seq, strlen(seq));
+ if (write(fd, ab.b, ab.len) == -1) {}
+ 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->plen + l->len) < l->cols) {
+ char d = c;
+ if (write(STDOUT_FILENO, &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;
+ while ((l->pos > 0) && (l->buf[l->pos - 1] == ' ')) {
+ l->pos--;
+ }
+ while ((l->pos > 0) && (l->buf[l->pos - 1] != ' ')) {
+ l->pos--;
+ }
+ size_t 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 void editDeleteWholeLine(struct State *l) {
+ l->buf[0] = '\0';
+ l->pos = l->len = 0;
+ refreshLine(l);
+}
+
+static void editDeleteLineToEnd(struct State *l) {
+ l->buf[l->pos] = '\0';
+ l->len = l->pos;
+ refreshLine(l);
+}
+
+static void editSwapCharWithPrev(struct State *l) {
+ if (l->pos > 0 && l->pos < l->len) {
+ int aux = l->buf[l->pos - 1];
+ l->buf[l->pos - 1] = l->buf[l->pos];
+ l->buf[l->pos] = aux;
+ if (l->pos != (l->len - 1)) {
+ l->pos++;
+ }
+ refreshLine(l);
+ }
+}
+
+static void editHistory(struct State *l, int dir) {
+ if (history_len > 1) {
+ free(history[history_len - (1 + l->history_index)]);
+ history[history_len - (1 + l->history_index)] = strdup(l->buf);
+ l->history_index += (dir == 1) ? 1 : -1;
+ if (l->history_index < 0) {
+ l->history_index = 0;
+ return;
+ } else if (l->history_index >= history_len) {
+ l->history_index = history_len - 1;
+ return;
+ }
+ strncpy(l->buf, history[history_len - (1 + l->history_index)], l->buflen);
+ l->buf[l->buflen - 1] = '\0';
+ l->len = l->pos = strlen(l->buf);
+ refreshLine(l);
+ }
+}
+
+static int historyAdd(const char *line) {
+ char *linecopy;
+ if (history_max_len == 0) {
+ return 0;
+ }
+ if (history == NULL) {
+ history = malloc(sizeof(char*)*history_max_len);
+ if (history == NULL) {
+ return 0;
+ }
+ memset(history, 0, (sizeof(char*)*history_max_len));
+ }
+ if (history_len && !strcmp(history[history_len-1], line)) {
+ return 0;
+ }
+ linecopy = strdup(line);
+ if (!linecopy) {
+ return 0;
+ }
+ if (history_len == history_max_len) {
+ free(history[0]);
+ memmove(history, history + 1, sizeof(char*)*(history_max_len - 1));
+ history_len--;
+ }
+ history[history_len] = linecopy;
+ history_len++;
+ return 1;
+}
+
+static void editEnter(void) {
+ history_len--;
+ free(history[history_len]);
+}
+
+static void editEscSequence(struct State *l, char seq[3]) {
+ if (read(STDIN_FILENO, seq, 1) == -1) return;
+ if (read(STDIN_FILENO, seq + 1, 1) == -1) return;
+ if (seq[0] == '[') { /* ESC [ sequences. */
+ if ((seq[1] >= '0') && (seq[1] <= '9')) {
+ /* Extended escape, read additional byte. */
+ if (read(STDIN_FILENO, seq + 2, 1) == -1) {
+ return;
+ }
+ if ((seq[2] == '~') && (seq[1] == 3)) {
+ editDelete(l); /* Delete key. */
+ }
+ } else {
+ switch(seq[1]) {
+ case 'A': editHistory(l, 1); break; /* Up */
+ case 'B': editHistory(l, 0); break; /* Down */
+ case 'C': editMoveRight(l); break; /* Right */
+ case 'D': editMoveLeft(l); break; /* Left */
+ case 'H': editMoveHome(l); break; /* Home */
+ case 'F': editMoveEnd(l); break; /* End*/
+ }
+ }
+ } else if (seq[0] == 'O') { /* ESC O sequences. */
+ switch(seq[1]) {
+ case 'H': editMoveHome(l); break; /* Home */
+ case 'F': editMoveEnd(l); break; /* End*/
+ }
+ }
+}
+
+static int edit(struct State *l) {
+ char c, seq[3];
+ ssize_t nread = read(STDIN_FILENO, &c, 1);
+ if (nread <= 0) {
+ return 1;
+ }
+ switch(c) {
+ case 13: editEnter(); return 1; /* enter */
+ case 3: errno = EAGAIN; return -1; /* ctrl-c */
+ case 127: /* backspace */
+ case 8: editBackspace(l); break; /* ctrl-h */
+ 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 21: editDeleteWholeLine(l); break; /* Ctrl+u */
+ case 11: editDeleteLineToEnd(l); break; /* Ctrl+k */
+ case 14: editHistory(l, 0); break; /* Ctrl+n */
+ case 16: editHistory(l, 1); break; /* Ctrl+p */
+ case 20: editSwapCharWithPrev(l); break; /* ctrl-t */
+ case 4: /* ctrl-d */
+ if (l->len > 0) {
+ editDelete(l);
+ } else {
+ history_len--;
+ free(history[history_len]);
+ return -1;
+ }
+ break;
+ case 27: editEscSequence(l, seq); break; /* escape sequence */
+ default: if (editInsert(l, c)) return -1; break;
+ }
+ return 0;
+}
+
+static void stateReset(struct State *l) {
+ l->plen = strlen(l->prompt);
+ l->oldpos = 0;
+ l->pos = 0;
+ l->len = 0;
+ l->history_index = 0;
+ l->buf[0] = '\0';
+ l->buflen--;
+ historyAdd("");
+}
+
+static char *ctime_now(char buf[26]) {
+ struct tm tm_;
+ time_t now = time(NULL);
+ if (!asctime_r(localtime_r (&now, &tm_), buf)) {
+ return NULL;
+ }
+ *strchr(buf, '\n') = '\0';
+ return buf;
+}
+
+static void logAppend(char *str, char *path) {
+ FILE *out;
+ char buf[26];
+ if ((out = fopen(path, "a")) == NULL) {
+ perror("fopen");
+ exit(1);
+ }
+ ctime_now(buf);
+ fprintf(out, "%s:%s", buf, str);
+ fclose(out);
+}
+
+static void raw(char *fmt, ...) {
+ va_list ap;
+ char *cmd_str = malloc(MSG_MAX);
+ if (!cmd_str) {
+ perror("malloc");
+ exit(1);
+ }
+ va_start(ap, fmt);
+ vsnprintf(cmd_str, MSG_MAX, fmt, ap);
+ va_end(ap);
+ if (verb) {
+ printf("<< %s", cmd_str);
+ }
+ if (olog) {
+ logAppend(cmd_str, olog);
+ }
+ if (write(conn, cmd_str, strnlen(cmd_str, MSG_MAX)) < 0) {
+ perror("write");
+ exit(1);
+ }
+ free(cmd_str);
}
static int initConnection(void) {
- int gai_status;
- struct addrinfo *res, hints = {
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_STREAM
- };
- if ((gai_status = getaddrinfo(host, port, &hints, &res)) != 0) {
- fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_status));
- return -1;
- }
- struct addrinfo *p;
- for (p = res; p != NULL; p = p->ai_next) {
- if ((conn = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
- perror("socket");
- continue;
- }
- if (connect(conn, p->ai_addr, p->ai_addrlen) == -1) {
- close(conn);
- perror("connect");
- continue;
- }
- break;
- }
- freeaddrinfo(res);
- if (p == NULL) {
- fputs("Failed to connect\n", stderr);
- return -1;
- }
- int flags = fcntl(conn, F_GETFL, 0);
- flags |= O_NONBLOCK;
- fcntl(conn, F_SETFL, flags);
-
- return 0;
-}
-
-static void messageWrap(struct Param * p) {
- if (!p->message)
- return;
- char * tok;
- size_t wordwidth, spacewidth = 1;
- size_t spaceleft = p->maxcols - p->nicklen - p->offset;
- for (tok = strtok(p->message, " "); tok != NULL; tok = strtok(NULL, " ")) {
- wordwidth = strnlen(tok, MSG_MAX);
- if ((wordwidth + spacewidth) > spaceleft) {
- printf("\r\n%*.s%s ", (int) p->nicklen + 1, " ", tok);
- spaceleft = p->maxcols - (p->nicklen + 1);
- } else {
- printf("%s ", tok);
- }
- spaceleft -= (wordwidth + spacewidth);
- }
-}
-
-static void paramPrintNick(struct Param * p) {
- printf("\x1b[35;1m%*s\x1b[0m ", p->nicklen - 4, p->nickname);
- printf("--> \x1b[35;1m%s\x1b[0m", p->message);
-}
-
-static void paramPrintPart(struct Param * p) {
- printf("%*s<-- \x1b[34;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
- if (p->channel != NULL && strcmp(p->channel+1, cdef))
- printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
-}
-
-static void paramPrintQuit(struct Param * p) {
- printf("%*s<<< \x1b[34;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
-}
-
-static void paramPrintJoin(struct Param * p) {
- printf("%*s--> \x1b[32;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
- if (p->channel != NULL && strcmp(p->channel+1, cdef))
- printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
-}
-
-static void handleCTCP(const char * nickname, char * message) {
- if (message[0] != '\001' && strncmp(message, "ACTION", 6))
- return;
- message++;
- if (!strncmp(message, "VERSION", 7)) {
- raw("NOTICE %s :\001VERSION kirc " VERSION "\001\r\n", nickname);
- } else if (!strncmp(message, "TIME", 7)) {
- char buf[26];
- if (!ctime_now(buf))
- raw("NOTICE %s :\001TIME %s\001\r\n", nickname, buf);
- } else if (!strncmp(message, "CLIENTINFO", 10)) {
- raw("NOTICE %s :\001CLIENTINFO " CTCP_CMDS "\001\r\n", nickname);
- } else if (!strncmp(message, "PING", 4)) {
- raw("NOTICE %s :\001%s\r\n", nickname, message);
- }
-}
-
-static void paramPrintPriv(struct Param * p) {
- int s = 0;
- if (strnlen(p->nickname, MSG_MAX) <= p->nicklen)
- s = p->nicklen - strnlen(p->nickname, MSG_MAX);
- if (p->channel != NULL && strcmp(p->channel, nick) == 0) {
- handleCTCP(p->nickname, p->message);
- printf("%*s\x1b[33;1m%-.*s\x1b[36m ", s, "", p->nicklen, p->nickname);
- } else if (p->channel != NULL && strcmp(p->channel + 1, cdef)) {
- printf("%*s\x1b[33;1m%-.*s\x1b[0m", s, "", p->nicklen, p->nickname);
- printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
- p->offset += 12 + strnlen(p->channel, CHA_MAX);
- } else {
- printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", p->nicklen, p->nickname);
- }
- if (!strncmp(p->message, "\x01""ACTION", 7)) {
- p->message += 7;
- p->offset += 10;
- printf("[ACTION] ");
- }
-}
-
-static void paramPrintChan(struct Param * p) {
- int s = 0;
- if (strnlen(p->nickname, MSG_MAX) <= p->nicklen)
- s = p->nicklen - strnlen(p->nickname, MSG_MAX);
- printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", p->nicklen, p->nickname);
- if (p->params) {
- printf("%s", p->params);
- p->offset += strnlen(p->params, CHA_MAX);
- }
-}
-
-static void rawParser(char * string) {
- if (!strncmp(string, "PING", 4)) {
- string[1] = 'O';
- raw("%s\r\n", string);
- return;
- }
- if (string[0] != ':' || (strnlen(string, MSG_MAX) < 4))
- return;
- printf("\r\x1b[0K");
- if (verb)
- printf(">> %s", string);
- if (olog)
- logAppend(string, olog);
- char * tok;
- struct Param p;
- p.prefix = strtok(string, " ") + 1;
- p.suffix = strtok(NULL, ":");
- p.message = strtok(NULL, "\r");
- p.nickname = strtok(p.prefix, "!");
- p.command = strtok(p.suffix, "#& ");
- p.channel = strtok(NULL, " \r");
- p.params = strtok(NULL, ":\r");
- p.maxcols = getColumns(STDIN_FILENO, STDOUT_FILENO);
- p.nicklen = (p.maxcols / 3 > NIC_MAX ? NIC_MAX : p.maxcols / 3);
- p.offset = 0;
- if (!strncmp(p.command, "001", 3) && chan != NULL) {
- for (tok = strtok(chan, ",|"); tok != NULL; tok = strtok(NULL, ",|")) {
- strcpy(cdef, tok);
- raw("JOIN #%s\r\n", tok);
- } return;
- } else if (!strncmp(p.command, "QUIT", 4)) {
- paramPrintQuit(&p);
- } else if (!strncmp(p.command, "PART", 4)) {
- paramPrintPart(&p);
- } else if (!strncmp(p.command, "JOIN", 4)) {
- paramPrintJoin(&p);
- } else if (!strncmp(p.command, "NICK", 4)) {
- paramPrintNick(&p);
- } else if (!strncmp(p.command, "PRIVMSG", 7)) {
- paramPrintPriv(&p);
- messageWrap(&p);
- } else {
- paramPrintChan(&p);
- messageWrap(&p);
- }
- printf("\x1b[0m\r\n");
-}
-
-static char message_buffer[MSG_MAX + 1];
+ int gai_status;
+ struct addrinfo *res, hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM
+ };
+ if ((gai_status = getaddrinfo(host, port, &hints, &res)) != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_status));
+ return -1;
+ }
+ struct addrinfo *p;
+ for (p = res; p != NULL; p = p->ai_next) {
+ if ((conn = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+ perror("socket");
+ continue;
+ }
+ if (connect(conn, p->ai_addr, p->ai_addrlen) == -1) {
+ close(conn);
+ perror("connect");
+ continue;
+ }
+ break;
+ }
+ freeaddrinfo(res);
+ if (p == NULL) {
+ fputs("Failed to connect\n", stderr);
+ return -1;
+ }
+ int flags = fcntl(conn, F_GETFL, 0);
+ flags |= O_NONBLOCK;
+ fcntl(conn, F_SETFL, flags);
+
+ return 0;
+}
+
+static void messageWrap(struct Param *p) {
+ if (!p->message) {
+ return;
+ }
+ char *tok;
+ size_t wordwidth, spacewidth = 1;
+ size_t spaceleft = p->maxcols - (p->nicklen + p->offset);
+ for (tok = strtok(p->message, " "); tok != NULL; tok = strtok(NULL, " ")) {
+ wordwidth = strnlen(tok, MSG_MAX);
+ if ((wordwidth + spacewidth) > spaceleft) {
+ printf("\r\n%*.s%s ", (int) p->nicklen + 1, " ", tok);
+ spaceleft = p->maxcols - (p->nicklen + 1);
+ } else {
+ printf("%s ", tok);
+ }
+ spaceleft -= wordwidth + spacewidth;
+ }
+}
+
+static void paramPrintNick(struct Param *p) {
+ printf("\x1b[35;1m%*s\x1b[0m ", p->nicklen - 4, p->nickname);
+ printf("--> \x1b[35;1m%s\x1b[0m", p->message);
+}
+
+static void paramPrintPart(struct Param *p) {
+ printf("%*s<-- \x1b[34;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
+ if (p->channel != NULL && strcmp(p->channel + 1, cdef)) {
+ printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
+ }
+}
+
+static void paramPrintQuit(struct Param *p) {
+ printf("%*s<<< \x1b[34;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
+}
+
+static void paramPrintJoin(struct Param *p) {
+ printf("%*s--> \x1b[32;1m%s\x1b[0m", p->nicklen - 3, "", p->nickname);
+ if (p->channel != NULL && strcmp(p->channel + 1, cdef)) {
+ printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
+ }
+}
+
+static void handleCTCP(const char *nickname, char *message) {
+ if (message[0] != '\001' && strncmp(message, "ACTION", 6)) {
+ return;
+ }
+ message++;
+ if (!strncmp(message, "VERSION", 7)) {
+ raw("NOTICE %s :\001VERSION kirc " VERSION "\001\r\n", nickname);
+ } else if (!strncmp(message, "TIME", 7)) {
+ char buf[26];
+ if (!ctime_now(buf)) {
+ raw("NOTICE %s :\001TIME %s\001\r\n", nickname, buf);
+ }
+ } else if (!strncmp(message, "CLIENTINFO", 10)) {
+ raw("NOTICE %s :\001CLIENTINFO " CTCP_CMDS "\001\r\n", nickname);
+ } else if (!strncmp(message, "PING", 4)) {
+ raw("NOTICE %s :\001%s\r\n", nickname, message);
+ }
+}
+
+static void paramPrintPriv(struct Param *p) {
+ int s = 0;
+ if (strnlen(p->nickname, MSG_MAX) <= (size_t) p->nicklen) {
+ s = p->nicklen - strnlen(p->nickname, MSG_MAX);
+ }
+ if (p->channel != NULL && (strcmp(p->channel, nick) == 0)) {
+ handleCTCP(p->nickname, p->message);
+ printf("%*s\x1b[33;1m%-.*s\x1b[36m ", s, "", p->nicklen, p->nickname);
+ } else if (p->channel != NULL && strcmp(p->channel + 1, cdef)) {
+ printf("%*s\x1b[33;1m%-.*s\x1b[0m", s, "", p->nicklen, p->nickname);
+ printf(" [\x1b[33m%s\x1b[0m] ", p->channel);
+ p->offset += 12 + strnlen(p->channel, CHA_MAX);
+ } else {
+ printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", p->nicklen, p->nickname);
+ }
+ if (!strncmp(p->message, "\x01""ACTION", 7)) {
+ p->message += 7;
+ p->offset += 10;
+ printf("[ACTION] ");
+ }
+}
+
+static void paramPrintChan(struct Param *p) {
+ int s = 0;
+ if (strnlen(p->nickname, MSG_MAX) <= (size_t) p->nicklen) {
+ s = p->nicklen - strnlen(p->nickname, MSG_MAX);
+ }
+ printf("%*s\x1b[33;1m%-.*s\x1b[0m ", s, "", p->nicklen, p->nickname);
+ if (p->params) {
+ printf("%s", p->params);
+ p->offset += strnlen(p->params, CHA_MAX);
+ }
+}
+
+static void rawParser(char *string) {
+ if (!strncmp(string, "PING", 4)) {
+ string[1] = 'O';
+ raw("%s\r\n", string);
+ return;
+ }
+ if (string[0] != ':' || (strnlen(string, MSG_MAX) < 4)) {
+ return;
+ }
+ printf("\r\x1b[0K");
+ if (verb) {
+ printf(">> %s", string);
+ }
+ if (olog) {
+ logAppend(string, olog);
+ }
+ char *tok;
+ struct Param p;
+ p.prefix = strtok(string, " ") + 1;
+ p.suffix = strtok(NULL, ":");
+ p.message = strtok(NULL, "\r");
+ p.nickname = strtok(p.prefix, "!");
+ p.command = strtok(p.suffix, "#& ");
+ p.channel = strtok(NULL, " \r");
+ p.params = strtok(NULL, ":\r");
+ p.maxcols = getColumns(STDIN_FILENO, STDOUT_FILENO);
+ p.nicklen = (p.maxcols / 3 > NIC_MAX ? NIC_MAX : p.maxcols / 3);
+ p.offset = 0;
+ if (!strncmp(p.command, "001", 3) && chan != NULL) {
+ for (tok = strtok(chan, ",|"); tok != NULL; tok = strtok(NULL, ",|")) {
+ strcpy(cdef, tok);
+ raw("JOIN #%s\r\n", tok);
+ } return;
+ } else if (!strncmp(p.command, "QUIT", 4)) {
+ paramPrintQuit(&p);
+ } else if (!strncmp(p.command, "PART", 4)) {
+ paramPrintPart(&p);
+ } else if (!strncmp(p.command, "JOIN", 4)) {
+ paramPrintJoin(&p);
+ } else if (!strncmp(p.command, "NICK", 4)) {
+ paramPrintNick(&p);
+ } else if (!strncmp(p.command, "PRIVMSG", 7)) {
+ paramPrintPriv(&p);
+ messageWrap(&p);
+ } else {
+ paramPrintChan(&p);
+ messageWrap(&p);
+ }
+ printf("\x1b[0m\r\n");
+}
+
+static char message_buffer[MSG_MAX + 1];
static size_t message_end = 0;
static int handleServerMessage(void) {
- for (;;) {
- ssize_t nread = read(conn, &message_buffer[message_end], MSG_MAX - message_end);
- if (nread == -1) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) {
- return 0;
- } else {
- perror("read");
- return -2;
- }
- }
- if (nread == 0) {
- fputs("\rconnection closed", stderr);
- puts("\r\x1b[E");
- return -1;
- }
- size_t i, old_message_end = message_end;
- message_end += nread;
- for (i = old_message_end; i < message_end; ++i) {
- if (i != 0 && message_buffer[i - 1] == '\r' && message_buffer[i] == '\n') {
- char saved_char = message_buffer[i + 1];
- message_buffer[i + 1] = '\0';
- 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;
- i = 0;
- }
- }
- if (message_end == MSG_MAX)
- message_end = 0;
- }
-}
-
-static void handleUserInput(struct State * l) {
- if (l->buf == NULL)
- return;
- char * tok;
- size_t msg_len = strnlen(l->buf, MSG_MAX);
- if (msg_len > 0 && l->buf[msg_len - 1] == '\n')
- l->buf[msg_len - 1] = '\0';
- printf("\r\x1b[0K");
- switch (l->buf[0]) {
- case '/' : /* send system command */
- if (l->buf[1] == '#') {
- strcpy(cdef, l->buf + 2);
- printf("\x1b[35mnew channel: #%s\x1b[0m\r\n", cdef);
- } else {
- raw("%s\r\n", l->buf + 1);
- printf("\x1b[35m%s\x1b[0m\r\n", l->buf);
- }
- break;
- case '@' : /* send private message to target channel or user */
- strtok_r(l->buf, " ", &tok);
- if (l->buf[1] == '@') {
- raw("privmsg %s :\001ACTION %s\001\r\n", l->buf + 2, tok);
- printf("\x1b[35mprivmsg %s :ACTION %s\x1b[0m\r\n", l->buf + 2, tok);
- } else {
- raw("privmsg %s :%s\r\n", l->buf + 1, tok);
- printf("\x1b[35mprivmsg %s :%s\x1b[0m\r\n", l->buf + 1, tok);
- } break;
- default : /* send private message to default channel */
- raw("privmsg #%s :%s\r\n", cdef, l->buf);
- printf("\x1b[35mprivmsg #%s :%s\x1b[0m\r\n", cdef, l->buf);
- }
+ for (;;) {
+ ssize_t nread = read(conn, &message_buffer[message_end],
+ MSG_MAX - message_end);
+ if (nread == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return 0;
+ } else {
+ perror("read");
+ return -2;
+ }
+ }
+ if (nread == 0) {
+ fputs("\rconnection closed", stderr);
+ puts("\r\x1b[E");
+ return -1;
+ }
+ size_t i, old_message_end = message_end;
+ message_end += nread;
+ for (i = old_message_end; i < message_end; ++i) {
+ if (i != 0 && message_buffer[i - 1] == '\r'
+ && message_buffer[i] == '\n') {
+ char saved_char = message_buffer[i + 1];
+ message_buffer[i + 1] = '\0';
+ 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;
+ i = 0;
+ }
+ }
+ if (message_end == MSG_MAX) {
+ message_end = 0;
+ }
+ }
+}
+
+static void handleUserInput(struct State *l) {
+ if (l->buf == NULL) {
+ return;
+ }
+ char *tok;
+ size_t msg_len = strnlen(l->buf, MSG_MAX);
+ if (msg_len > 0 && l->buf[msg_len - 1] == '\n') {
+ l->buf[msg_len - 1] = '\0';
+ }
+ printf("\r\x1b[0K");
+ switch (l->buf[0]) {
+ case '/' : /* send system command */
+ if (l->buf[1] == '#') {
+ strcpy(cdef, l->buf + 2);
+ printf("\x1b[35mnew channel: #%s\x1b[0m\r\n", cdef);
+ } else {
+ raw("%s\r\n", l->buf + 1);
+ printf("\x1b[35m%s\x1b[0m\r\n", l->buf);
+ }
+ break;
+ case '@' : /* send private message to target channel or user */
+ strtok_r(l->buf, " ", &tok);
+ if (l->buf[1] == '@') {
+ raw("privmsg %s :\001ACTION %s\001\r\n", l->buf + 2, tok);
+ printf("\x1b[35mprivmsg %s :ACTION %s\x1b[0m\r\n", l->buf + 2, tok);
+ } else {
+ raw("privmsg %s :%s\r\n", l->buf + 1, tok);
+ printf("\x1b[35mprivmsg %s :%s\x1b[0m\r\n", l->buf + 1, tok);
+ } break;
+ default : /* send private message to default channel */
+ raw("privmsg #%s :%s\r\n", cdef, l->buf);
+ printf("\x1b[35mprivmsg #%s :%s\x1b[0m\r\n", cdef, l->buf);
+ }
}
static void usage(void) {
- fputs("kirc [-s host] [-p port] [-c channel] [-n nick] [-r realname] \
+ fputs("kirc [-s host] [-p port] [-c channel] [-n nick] [-r realname] \
[-u username] [-k password] [-a token] [-x command] [-o path] [-e] [-v] [-V]\n", stderr);
- exit(2);
+ exit(2);
}
static void version(void) {
- fputs("kirc-" VERSION "Copyright © 2021 Michael Czigler, MIT License\n", stdout);
- exit(0);
+ fputs("kirc-" VERSION "Copyright © 2021 Michael Czigler, MIT License\n",
+ stdout);
+ exit(0);
}
int main(int argc, char **argv) {
- int cval;
- while ((cval = getopt(argc, argv, "s:p:o:n:k:c:u:r:x:a:evV")) != -1) {
- switch (cval) {
- case 'v' : version(); break;
- case 'V' : ++verb; break;
- case 'e' : ++sasl; break;
- case 's' : host = optarg; break;
- case 'p' : port = optarg; break;
- case 'r' : real = optarg; break;
- case 'u' : user = optarg; break;
- case 'a' : auth = optarg; break;
- case 'o' : olog = optarg; break;
- case 'n' : nick = optarg; break;
- case 'k' : pass = optarg; break;
- case 'c' : chan = optarg; break;
- case 'x' : inic = optarg; break;
- case '?' : usage(); break;
- }
- }
- if (!nick) {
- fputs("Nick not specified\n", stderr);
- usage();
- }
- if (initConnection() != 0)
- return 1;
- if (auth || sasl)
- raw("CAP REQ :sasl\r\n");
- raw("NICK %s\r\n", nick);
- raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick));
- if (auth || sasl) {
- raw("AUTHENTICATE %s\r\n", (sasl ? "EXTERNAL" : "PLAIN"));
- raw("AUTHENTICATE %s\r\nCAP END\r\n", (sasl ? "+" : auth));
- }
- if (pass)
- raw("PASS %s\r\n", pass);
- if (inic)
- raw("%s\r\n", inic);
- struct pollfd fds[2];
- fds[0].fd = STDIN_FILENO;
- fds[1].fd = conn;
- fds[0].events = POLLIN;
- fds[1].events = POLLIN;
- char usrin[MSG_MAX];
- struct State l;
- l.buf = usrin;
- l.buflen = MSG_MAX;
- l.prompt = cdef;
- stateReset(&l);
- int rc, editReturnFlag = 0;
- if (enableRawMode(STDIN_FILENO) == -1)
- return 1;
- for (;;) {
- if (poll(fds, 2, -1) != -1) {
- if (fds[0].revents & POLLIN) {
- editReturnFlag = edit(&l);
- if (editReturnFlag > 0) {
- handleUserInput(&l);
- stateReset(&l);
- } else if (editReturnFlag < 0) {
- printf("\r\n");
- return 0;
- }
- refreshLine(&l);
- }
- if (fds[1].revents & POLLIN) {
- rc = handleServerMessage();
- if (rc != 0) {
- if (rc == -2)
- return 1;
- return 0;
- }
- refreshLine(&l);
- }
- } else {
- if (errno == EAGAIN)
- continue;
- perror("poll");
- return 1;
- }
- }
+ int cval;
+ while ((cval = getopt(argc, argv, "s:p:o:n:k:c:u:r:x:a:evV")) != -1) {
+ switch (cval) {
+ case 'v' : version(); break;
+ case 'V' : ++verb; break;
+ case 'e' : ++sasl; break;
+ case 's' : host = optarg; break;
+ case 'p' : port = optarg; break;
+ case 'r' : real = optarg; break;
+ case 'u' : user = optarg; break;
+ case 'a' : auth = optarg; break;
+ case 'o' : olog = optarg; break;
+ case 'n' : nick = optarg; break;
+ case 'k' : pass = optarg; break;
+ case 'c' : chan = optarg; break;
+ case 'x' : inic = optarg; break;
+ case '?' : usage(); break;
+ }
+ }
+ if (!nick) {
+ fputs("Nick not specified\n", stderr);
+ usage();
+ }
+ if (initConnection() != 0) {
+ return 1;
+ }
+ if (auth || sasl) {
+ raw("CAP REQ :sasl\r\n");
+ }
+ raw("NICK %s\r\n", nick);
+ raw("USER %s - - :%s\r\n", (user ? user : nick), (real ? real : nick));
+ if (auth || sasl) {
+ raw("AUTHENTICATE %s\r\n", (sasl ? "EXTERNAL" : "PLAIN"));
+ raw("AUTHENTICATE %s\r\nCAP END\r\n", (sasl ? "+" : auth));
+ }
+ if (pass) {
+ raw("PASS %s\r\n", pass);
+ }
+ if (inic) {
+ raw("%s\r\n", inic);
+ }
+ struct pollfd fds[2];
+ fds[0].fd = STDIN_FILENO;
+ fds[1].fd = conn;
+ fds[0].events = POLLIN;
+ fds[1].events = POLLIN;
+ char usrin[MSG_MAX];
+ struct State l;
+ l.buf = usrin;
+ l.buflen = MSG_MAX;
+ l.prompt = cdef;
+ stateReset(&l);
+ int rc, editReturnFlag = 0;
+ if (enableRawMode(STDIN_FILENO) == -1) {
+ return 1;
+ }
+ for (;;) {
+ if (poll(fds, 2, -1) != -1) {
+ if (fds[0].revents & POLLIN) {
+ editReturnFlag = edit(&l);
+ if (editReturnFlag > 0) {
+ historyAdd(l.buf);
+ handleUserInput(&l);
+ stateReset(&l);
+ } else if (editReturnFlag < 0) {
+ printf("\r\n");
+ return 0;
+ }
+ refreshLine(&l);
+ }
+ if (fds[1].revents & POLLIN) {
+ rc = handleServerMessage();
+ if (rc != 0) {
+ if (rc == -2) {
+ return 1;
+ }
+ return 0;
+ }
+ refreshLine(&l);
+ }
+ } else {
+ if (errno == EAGAIN) {
+ continue;
+ }
+ perror("poll");
+ return 1;
+ }
+ }
}