}
}
-void chat_notice(channel_t *channel, const char *from, const char *fmt, ...)
-{
- static char buf[1024];
-
- va_list ap;
- va_start(ap, fmt);
- vsnprintf(buf, sizeof(buf), fmt, ap);
- va_end(ap);
-
- chat_recv(channel, from, buf);
-}
-
void chat_recv(channel_t *channel, const char *from, const char *text)
{
append(&msg_buf, NULL, sizeof(message_t));
message_t *msg = &messages[history];
msg->channel = channel;
msg->when = time(NULL);
- msg->from = strcopy("andy");
msg->text = strcopy(text);
history++;
/* Message Types */
typedef unsigned long long stamp_t;
-typedef struct server_t server_t;
+typedef struct server_t server_t;
typedef struct channel_t channel_t;
typedef enum {
void chat_init(void);
void chat_config(const char *group, const char *name,
const char *key, const char *value);
-void chat_notice(channel_t *channel, const char *from, const char *fmt, ...);
void chat_recv(channel_t *channel, const char *from, const char *msg);
void chat_send(channel_t *channel, const char *msg);
void chat_exit(void);
} irc_state_t;
typedef struct {
- server_t server;
-
- int connect;
- const char *host;
- int port;
- int tls;
- const char *nick;
- const char *auth;
- const char *pass;
-
- net_t net;
- irc_state_t state;
- char line[IRC_LINE];
- int pos;
-} irc_server_t;
-
-typedef struct {
- channel_t channel;
+ channel_t channel;
- const char *dest;
- int join;
+ const char *dest;
+ int join;
} irc_channel_t;
typedef struct {
- char src[IRC_LINE]; // (:([^ ]+) +)?
- char cmd[IRC_LINE]; // (([A-Z0-9]+) +)
- char dst[IRC_LINE]; // (([^ ]+)[= ]+)?
- char arg[IRC_LINE]; // (([^: ]+) *)?
- char msg[IRC_LINE]; // (:(.*))?
+ server_t server;
+
+ int connect;
+ const char *host;
+ int port;
+ int tls;
+ const char *nick;
+ const char *auth;
+ const char *pass;
+
+ irc_channel_t system;
+ net_t net;
+ irc_state_t state;
+ char line[IRC_LINE];
+ int pos;
+} irc_server_t;
+
+typedef struct {
+ char src[IRC_LINE]; // (:([^ ]+) +)?
+ char cmd[IRC_LINE]; // (([A-Z0-9]+) +)
+ char dst[IRC_LINE]; // (([^ ]+)[= ]+)?
+ char arg[IRC_LINE]; // (([^: ]+) *)?
+ char msg[IRC_LINE]; // (:(.*))?
char from[IRC_LINE];
} irc_command_t;
/* Local functions */
-static irc_channel_t *find_dest(irc_server_t *srv, const char *dest)
+static void srv_notice(irc_server_t *srv, const char *from, const char *fmt, ...)
+{
+ static char buf[1024];
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ chat_recv(&srv->system.channel, from, buf);
+}
+
+static void chan_notice(irc_channel_t *chan, const char *from, const char *fmt, ...)
+{
+ static char buf[1024];
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ chat_recv(&chan->channel, from, buf);
+}
+
+static irc_channel_t *find_dest(irc_server_t *srv, const char *dest, int create)
{
irc_channel_t *chan;
return chan;
}
+static void join_channel(irc_server_t *srv, irc_channel_t *chan)
+{
+ chan_notice(chan, NULL, "Joining Channel: %s", chan->channel.name);
+ net_print(&srv->net, "JOIN %s\n", chan->dest);
+ net_print(&srv->net, "TOPIC %s\n", chan->dest);
+ net_print(&srv->net, "WHO %s\n", chan->dest);
+}
+
+static void part_channel(irc_server_t *srv, irc_channel_t *chan)
+{
+ chan_notice(chan, NULL, "Leaving Channel: %s", chan->channel.name);
+ net_print(&srv->net, "PART %s\n", chan->dest);
+}
+
static void parse_line(irc_server_t *srv, const char *line,
irc_command_t *_cmd)
{
/* Read arg */
if (*c && *c != ':' && *c != ' ') {
- for (i = 0; *c && *c != ' '; i++)
+ for (i = 0; *c && *c != ':'; i++)
arg[i] = *c++;
while (*c == ' ')
c++;
- arg[i] = '\0';
+ do
+ arg[i--] = '\0';
+ while (i >= 0 && arg[i] == ' ');
}
/* Read msg */
srv->net.recv = on_recv;
srv->net.err = on_err;
srv->net.data = srv;
+
+ srv_notice(srv, NULL, "Joining Server: %s", srv->server.name);
net_open(&srv->net, srv->host, srv->port);
if (srv->tls)
}
if (srv->state == IRC_RECV_TLS) {
if (match(cmd, "CAP") && match(arg, "ACK")) {
- chat_notice(NULL, from, "Start TLS proceeding");
+ srv_notice(srv, from, "Start TLS proceeding");
srv->state = IRC_SEND_STARTTLS;
}
if (match(cmd, "CAP") && match(arg, "NAK")) {
- chat_notice(NULL, from, "Start TLS unsupported");
+ srv_notice(srv, from, "Start TLS unsupported");
srv->state = IRC_SEND_END;
}
}
}
if (srv->state == IRC_RECV_SASL) {
if (match(cmd, "CAP") && match(arg, "ACK")) {
- chat_notice(NULL, from, "SASL auth proceeding");
+ srv_notice(srv, from, "SASL auth proceeding");
srv->state = IRC_SEND_PLAIN;
}
if (match(cmd, "CAP") && match(arg, "NAK")) {
- chat_notice(NULL, from, "SASL auth unsupported");
+ srv_notice(srv, from, "SASL auth unsupported");
srv->state = IRC_SEND_END;
}
}
}
if (srv->state == IRC_RECV_STATUS) {
if (match(cmd, "903")) {
- chat_notice(NULL, from, "SASL auth succeeded");
+ srv_notice(srv, from, "SASL auth succeeded");
srv->state = IRC_SEND_END;
}
if (match(cmd, "904") ||
match(cmd, "905") ||
match(cmd, "906") ||
match(cmd, "907")) {
- chat_notice(NULL, from, "SASL auth failed");
+ srv_notice(srv, from, "SASL auth failed");
srv->state = IRC_DEAD;
}
}
irc_channel_t *chan = (irc_channel_t*)cur;
if (cur->server != &srv->server || !chan->join)
continue;
- net_print(&srv->net, "JOIN %s\n", chan->dest);
+ join_channel(srv, chan);
}
srv->state = IRC_READY;
}
if (match(cmd, "PING")) {
net_print(&srv->net, "PING %s\n", msg);
}
+ if (match(cmd, "TOPIC")) {
+ chan = find_dest(srv, arg, 1);
+ chan_notice(chan, NULL, "Topic changed to %s", msg);
+ }
+ if (match(cmd, "331") || match(cmd, "322")) {
+ chan = find_dest(srv, arg, 1);
+ chan_notice(chan, NULL, "Topic: %s", msg);
+ }
+ if (match(cmd, "353") && prefix(arg, "@", &arg)) {
+ chan = find_dest(srv, arg, 1);
+ chan_notice(chan, NULL, "Members: %s", msg);
+ }
if (match(cmd, "PRIVMSG") && dst[0] == '#') {
- chan = find_dest(srv, dst);
+ chan = find_dest(srv, dst, 1);
chat_recv(&chan->channel, from, msg);
}
if (match(cmd, "PRIVMSG") && dst[0] != '#') {
- chan = find_dest(srv, from);
+ chan = find_dest(srv, from, 1);
chat_recv(&chan->channel, from, msg);
}
}
match(cmd, "372") ||
match(cmd, "375") ||
match(cmd, "376"))
- chat_notice(NULL, from, "%s", msg);
+ srv_notice(srv, from, "%s", msg);
}
/* IRC functions */
continue;
irc_server_t *srv = (irc_server_t*)cur;
+ srv->system.channel.server = &srv->server;
+ srv->system.channel.name = srv->server.name;
+
if (!srv->port)
srv->port = srv->tls ? 6697 : 6667;
if (!srv->nick)
{
irc_channel_t *chan = (irc_channel_t*)msg->channel;
irc_server_t *srv = (irc_server_t*)msg->channel->server;
-
- net_print(&srv->net, "PRIVMSG %s :%s\n", chan->dest, msg->text);
-
- msg->from = strcopy(srv->nick);
+ const char *txt = msg->text;
+ const char *arg;
+
+ if (txt[0] == '/') {
+ if (prefix(txt, "/join", &arg)) {
+ if (arg)
+ chan = find_dest(srv, arg, 1);
+ if (chan)
+ join_channel(srv, chan);
+ }
+ else if (prefix(txt, "/part", &arg)) {
+ if (arg)
+ chan = find_dest(srv, arg, 0);
+ if (chan)
+ part_channel(srv, chan);
+ }
+ else {
+ debug("unknown: [%s]", txt);
+ chan_notice(chan, "unknown command %s", txt);
+ }
+ } else {
+ if (chan == &srv->system) {
+ strset(&msg->text, "Cannot send to server");
+ }
+ else if (!chan->dest) {
+ strset(&msg->text, "No destination for message");
+ }
+ else {
+ net_print(&srv->net, "PRIVMSG %s :%s\n", chan->dest, msg->text);
+ msg->from = strcopy(srv->nick);
+ }
+ }
}
void irc_exit(void)
return strdup(str);
}
+void strset(char **old, const char *str)
+{
+ if (*old != NULL)
+ free(old);
+ if (str)
+ *old = strdup(str);
+ else
+ *old = NULL;
+}
+
int match(const char *a, const char *b)
{
if (a == b)
/* Stirng functions */
void strsub(char *str, char find, char repl);
char *strcopy(const char *str);
+void strset(char **old, const char *str);
int match(const char *a, const char *b);
int match(const char *a, const char *b);
int prefix(const char *str, const char *prefix, const char **suffix);
#include "chat.h"
#include "view.h"
+/* View constants */
+#define RE_FLAGS (REG_EXTENDED|REG_NOSUB)
+
/* Extra keys */
#define KEY_CTRL_G '\7'
#define KEY_CTRL_L '\14'
typedef struct window_t {
char *name;
+ int hide;
int flag;
char *filter;
regex_t regex;
+ server_t *server;
channel_t *channel;
window_t *next;
} window_t;
static int in_window(message_t *msg, window_t *win)
{
channel_t *chan = msg->channel;
- const char *name = chan ? chan->name : "";
+ server_t *srv = chan->server;
+ const char *name = chan->name;
+ if (!win)
+ return 0;
+ if (win->server) {
+ if (srv != win->server) {
+ debug("srv: %p:%s != %p:%s",
+ srv, srv->name,
+ win->server, win->server->name);
+ return 0;
+ }
+ }
if (win->filter)
return !regexec(&win->regex, name, 0, 0, 0);
else
*last = win;
}
-static window_t *find_window(const char *name)
+static window_t *find_window(const char *name, int create)
{
window_t *win;
for (win = windows; win; win = win->next)
if (match(win->name, name))
break;
- if (!win) {
+ if (!win && create) {
win = new0(window_t);
win->name = strcopy(name);
add_window(win);
return win;
}
+static window_t *next_window(window_t *win)
+{
+ if (!win)
+ return NULL;
+ for (window_t *cur = win->next; cur; cur = cur->next)
+ if (!cur->hide)
+ return cur;
+ return win && !win->hide ? win : NULL;
+}
+
+static window_t *prev_window(window_t *win)
+{
+ if (!win)
+ return NULL;
+ window_t *prev = windows;
+ for (window_t *cur = windows; cur != win; cur = cur->next)
+ if (!cur->hide)
+ prev = cur;
+ return prev && !prev->hide ? prev : NULL;
+}
+
static void update_windows(void)
{
static int seen = 0;
for (; seen < history; seen++) {
- window_t *win = NULL;
- message_t *msg = &messages[seen];
- channel_t *chan = msg->channel;
+ window_t *win = NULL;
+ message_t *msg = &messages[seen];
+ channel_t *chan = msg->channel;
/* Flag existing windows */
for (window_t *cur = windows; cur; cur = cur->next) {
if (in_window(msg, cur)) {
win = cur;
+ win->hide = 0;
win->flag = 1;
}
}
/* Update focus */
if (!focus)
focus = windows;
+ if (focus && focus->hide)
+ focus = next_window(focus) ?:
+ prev_window(focus);
}
/* Print functions */
(*row)++;
}
+/* Commands */
+static int send_command(const char *text)
+{
+ const char *arg;
+ window_t *win = focus;
+
+ if (match(text, "/quit")) {
+ poll_quit();
+ }
+ else if (prefix(text, "/open", &arg)) {
+ if (!arg && focus)
+ arg = focus->channel->name;
+ if (!arg)
+ arg = "(new)";
+ focus = find_window(arg, 1);
+ focus->hide = 0;
+ }
+ else if (prefix(text, "/close", &arg)) {
+ if (arg)
+ win = find_window(arg, 0);
+ if (win)
+ win->hide = 1;
+ }
+ else if (prefix(text, "/name", &arg)) {
+ strset(&focus->name, arg);
+ }
+ else if (prefix(text, "/filter", &arg)) {
+ strset(&focus->filter, arg);
+ regcomp(&focus->regex, focus->filter, RE_FLAGS);
+ }
+ else if (focus) {
+ chat_send(focus->channel, text);
+ }
+ else {
+ return 0;
+ }
+ return 1;
+}
+
/* Drawing functions */
void draw_header(void)
{
if (!windows)
printw(" none");
for (window_t *cur = windows; cur; cur = cur->next) {
+ if (cur->hide)
+ continue;
printw(" ");
if (cur == focus)
attron(A_BOLD);
/* View init */
void view_init(void)
{
- const int flags = REG_EXTENDED|REG_NOSUB;
-
/* Setup windows */
for (window_t *win = windows; win; win = win->next)
if (win->filter)
- regcomp(&win->regex, win->filter, flags);
+ regcomp(&win->regex, win->filter, RE_FLAGS);
/* Set default escape timeout */
if (!getenv("ESCDELAY"))
if (match(group, "window")) {
if (match(key, ""))
get_name(name);
- win = find_window(name);
+ win = find_window(name, 1);
+ if (match(key, "server"))
+ win->server = find_server(get_string(value));
if (match(key, "channel"))
win->channel = find_channel(get_string(value));
if (match(key, "filter"))
/* View management */
else if (chr == KEY_CTRL_N) {
- if (focus && focus->next)
- focus = focus->next;
- else if (!focus)
- focus = windows;
+ focus = next_window(focus) ?: focus;
view_draw();
}
else if (chr == KEY_CTRL_P) {
- window_t *cur = windows;
- for (; cur; cur = cur->next)
- if (cur->next == focus)
- focus = cur;
+ focus = prev_window(focus) ?: focus;
view_draw();
}
else if (chr == KEY_CTRL_X) {
/* Cmdline Input */
else if (chr == KEY_RETURN) {
- if (focus) {
- cmd_buf[cmd_len] = '\0';
+ cmd_buf[cmd_len] = '\0';
+ if (send_command(cmd_buf)) {
cmd_pos = 0;
cmd_len = 0;
- chat_send(focus->channel, cmd_buf);
}
- draw_chat();
- draw_cmdline();
+ view_draw();
}
else if (chr == KEY_ESCAPE) {
cmd_pos = 0;
} xmpp_state_t;
typedef struct {
- server_t server;
-
- int connect;
- const char *host;
- int port;
- const char *muc;
- const char *nick;
- const char *jid;
- const char *user;
- const char *pass;
-
- net_t net;
- XML_Parser expat;
- xmpp_state_t state;
- buf_t buf;
- int indent;
-
- int in_error;
-
- char msg_from[JID_LEN];
- char msg_usr[JID_LEN];
- char msg_srv[JID_LEN];
- char msg_res[JID_LEN];
-} xmpp_server_t;
-
-typedef struct {
- channel_t channel;
- char dest[JID_LEN];
+ channel_t channel;
+ char dest[JID_LEN];
- const char *room;
- int join;
+ const char *room;
+ int join;
} xmpp_channel_t;
+typedef struct {
+ server_t server;
+ xmpp_channel_t system;
+
+ int connect;
+ const char *host;
+ int port;
+ const char *muc;
+ const char *nick;
+ const char *jid;
+ const char *user;
+ const char *pass;
+
+ net_t net;
+ XML_Parser expat;
+ xmpp_state_t state;
+ buf_t buf;
+ int indent;
+
+ int in_error;
+
+ char msg_from[JID_LEN];
+ char msg_usr[JID_LEN];
+ char msg_srv[JID_LEN];
+ char msg_res[JID_LEN];
+} xmpp_server_t;
+
/* Helper functions */
+static void srv_notice(xmpp_server_t *srv, const char *fmt, ...)
+{
+ static char buf[1024];
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ chat_recv(&srv->system.channel, NULL, buf);
+}
+
+static void chan_notice(xmpp_channel_t *chan, const char *fmt, ...)
+{
+ static char buf[1024];
+
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ chat_recv(&chan->channel, NULL, buf);
+}
+
static void split_jid(const char *jid, char *usr, char *srv, char *res)
{
char *ptr = usr;
/* Info queries */
if (srv->state == XMPP_READY) {
if (match(start, "item"))
- chat_notice(NULL, NULL, "item: [%s] %s",
+ srv_notice(srv, "item: [%s] %s",
find_attr(attrs, "jid"),
find_attr(attrs, "name"));
if (match(start, "identity"))
- chat_notice(NULL, NULL, "identity: %s",
+ srv_notice(srv, "identity: %s",
find_attr(attrs, "name"));
if (match(start, "feature"))
- chat_notice(NULL, NULL, "feature: %s",
+ srv_notice(srv, "feature: %s",
find_attr(attrs, "var"));
}
if (srv->in_error) {
if (match(end, "text")) {
debug("xmpp: error: %s", data);
- chat_notice(NULL, NULL, "error: %s", data);
+ srv_notice(srv, "error: %s", data);
}
}
}
continue;
xmpp_server_t *srv = (xmpp_server_t*)cur;
+ srv->system.channel.server = &srv->server;
+ srv->system.channel.name = srv->server.name;
+ srv_notice(srv, "XMPP Server: %s", srv->server.name);
+
if (!srv->port)
srv->port = 5222;
if (!srv->jid)
}
else if (prefix(txt, "/join", &arg)) {
if (!arg) {
- chat_notice(NULL, NULL, "usage: /join <channel>");
+ chan_notice(chan, "usage: /join <channel>");
return;
}
chan = find_dest(srv, arg, 1);
}
else if (prefix(txt, "/query", &arg)) {
if (!arg) {
- chat_notice(NULL, NULL, "usage: /query <user>");
+ chan_notice(chan, "usage: /query <user>");
return;
}
chan = find_dest(srv, arg, 0);
- (void)chan;
+ chan_notice(chan, "query <user>");
}
else {
debug("unknown: [%s]", txt);
- chat_notice(NULL, NULL,
- "unknown command %s", txt);
+ chan_notice(chan, "unknown command %s", txt);
}
} else {
debug("message: [%s]", txt);