From 6a27897a9b966c5c28f593cc4ad47a19e8f98752 Mon Sep 17 00:00:00 2001 From: Søren Sandmann Pedersen Date: Sun, 23 Jan 2011 14:10:23 -0500 Subject: initial checkin --- channel-buffer.c | 132 ++++ channel-buffer.h | 32 + chatterbox.glade | 2297 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ irc.c | 931 ++++++++++++++++++++++ irc.h | 70 ++ main.c | 41 + 6 files changed, 3503 insertions(+) create mode 100644 channel-buffer.c create mode 100644 channel-buffer.h create mode 100644 chatterbox.glade create mode 100644 irc.c create mode 100644 irc.h create mode 100644 main.c diff --git a/channel-buffer.c b/channel-buffer.c new file mode 100644 index 0000000..5390439 --- /dev/null +++ b/channel-buffer.c @@ -0,0 +1,132 @@ +/* + * This data structure purpose in life is to maintain a GtkTextBuffer + * with these properies: + * + * - The channel is not editable: + * + * - There is an area after channel area where the user can enter new + * messages + * + * - You can add messages, and they will be displayed in gray in the + * channel area. + * + * - You can /commit/ messages which means they will turn black + * + * - Messages will be added to the channel area in the order they + * are committed + * + * - The user displays as either + * + * - The users nickname, followed by a colon, followed by a space, + * followed by the text that the user has entered in a normal + * typeface + * + * or + * + * - The users nickname in italic, followed by a apsce, followed + * by the text the user has entered, in italics + * + * The first style is used for nomal messages, the second for + * 'third person messages', ie. 'ssp thinks blah blah .... ' + * as opposed to 'ssp: blaah blah blah' + * + * The channel buffer also maintains a topic + * + */ +#include "channel-buffer.h" + +struct ChannelBuffer +{ + const char *user_nick; + + GtkTextBuffer *text_buffer; + GtkTextTag *nick_tag; + + GtkTextMark *mid_point; + GtkTextMark *nick_end; +}; + +static void +set_mid_point_gravity (ChannelBuffer *buffer, + gboolean left_gravity) +{ + GtkTextIter iter; + + if (gtk_text_mark_get_left_gravity (buffer->mid_point) == left_gravity) + return; + + gtk_text_buffer_get_iter_at_mark (buffer->text_buffer, &iter, + buffer->mid_point); + gtk_text_buffer_delete_mark (buffer->text_buffer, buffer->midpoint); + buffer->mid_point = gtk_text_buffer_create_mark ( + buffer->text_buffer, "mid_point", &iter, lelft_gravity); +} + +static void +channel_buffer_change_nick_text (ChannelBuffer *buffer, + const char *text, + gboolean italic) +{ + + gtk_text_buffer_insert (buffer->text_buffer, &iter, + buffer->user_nick, -1); +} + +ChannelBuffer * +channel_buffer_new (const char *user_nick) +{ + ChannelBuffer *buffer = g_new (ChannelBuffer, 1); + GtkTextIter iter; + + g_return_val_if_fail (g_utf8_validate (user_nick, -1, NULL), NULL); + + buffer->user_nick = g_strdup (user_nick); + + /* TextBuffer stuff */ + buffer->text_buffer = gtk_text_buffer_new (NULL); + buffer->nick_tag = + gtk_text_buffer_create_tag (buffer->text_buffer, "nick", + "weight", PANGO_WEIGHT_BOLD, + NULL); + + gtk_text_buffer_get_start_iter (buffer->text_buffer, &iter); + + buffer->mid_point = gtk_text_buffer_create_mark ( + buffer->text_buffer, "nick_begin", &iter, FALSE); + + buffer->nick_end = gtk_text_buffer_create_mark ( + buffer->text_buffer, "nick_end", &iter, TRUE); + + gtk_text_buffer_get_start_iter (buffer->text_buffer, &iter); + + buffer->channel_area_begin = gtk_text_buffer_create_mark ( + buffer->text_buffer, "channel_area_begin", &iter, TRUE); + + buffer->channel_area_end = gtk_text_buffer_create_mark ( + buffer->text_buffer, "channel_area_end", &iter, FALSE); + + return buffer; +} + +void channel_buffer_free (ChannelBuffer *buffer); +ChannelMessage *channel_buffer_add_message (ChannelBuffer *buffer, + const char *nick, + const char *message); +void channel_buffer_commit_message (ChannelMessage *message); +void channel_buffer_set_user_nick (ChannelBuffer *buffer, + const char *nick); +const char * channel_buffer_get_user_text (ChannelBuffer *buffer); +void channel_buffer_set_action_mode (ChannelBuffer *buffer, + gboolean action_mode); +gboolean channel_buffer_get_action_mode (ChannelBuffer *buffer); +#if 0 +void channel_buffer_set_topic (ChannelBuffer *buffer, + const char *topic); +const char * channel_buffer_get_topic (ChannelBuffer *buffer); +#endif + +GtkTextBuffer * +channel_buffer_get_text_buffer (ChannelBuffer *buffer) +{ + return buffer->text_buffer; +} diff --git a/channel-buffer.h b/channel-buffer.h new file mode 100644 index 0000000..416c761 --- /dev/null +++ b/channel-buffer.h @@ -0,0 +1,32 @@ +#include + +typedef struct ChannelBuffer ChannelBuffer; +typedef struct ChannelMessage ChannelMessage; + +typedef union +{ + +} ChannelBufferEvent; + +typedef void (* ChannelBufferEventFunc) (ChannelBuffer *buffer, + const ChannelBufferEvent *event); + +ChannelBuffer *channel_buffer_new (); +void channel_buffer_free (ChannelBuffer *buffer); +ChannelMessage *channel_buffer_add_message (ChannelBuffer *buffer, + const char *nick, + const char *message); +void channel_buffer_commit_message (ChannelMessage *message); +void channel_buffer_set_user_nick (ChannelBuffer *buffer, + const char *nick); +const char * channel_buffer_get_user_text (ChannelBuffer *buffer); +void channel_buffer_set_action_mode (ChannelBuffer *buffer, + gboolean action_mode); +gboolean channel_buffer_get_action_mode (ChannelBuffer *buffer); +#if 0 +void channel_buffer_set_topic (ChannelBuffer *buffer, + const char *topic); +const char * channel_buffer_get_topic (ChannelBuffer *buffer); +#endif + +GtkTextBuffer *channel_buffer_get_text_buffer (ChannelBuffer *buffer); diff --git a/chatterbox.glade b/chatterbox.glade new file mode 100644 index 0000000..d487cf8 --- /dev/null +++ b/chatterbox.glade @@ -0,0 +1,2297 @@ + + + + + + + + 12 + True + Connecting + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + True + False + 18 + + + + True + False + 12 + + + + True + False + 12 + + + + True + False + 6 + + + + True + <b>_Server:</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + combo-entry1 + + + 0 + False + False + + + + + + True + False + True + False + True + False + + + + True + True + True + True + 0 + irc.gimp.org + True + * + False + + + + + + True + GTK_SELECTION_BROWSE + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + False + 6 + + + + True + <b>_Port:</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + entry2 + + + 0 + False + False + + + + + + 91 + True + True + True + True + 0 + 6667 + True + * + False + + + 0 + True + True + + + + + 0 + False + True + + + + + 0 + False + False + + + + + + True + False + 6 + + + + True + <b>_Channels</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + True + GTK_POLICY_ALWAYS + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_END + 0 + + + + True + True + True + True + _Connect + True + GTK_RELIEF_NORMAL + True + + + + + 0 + False + False + + + + + + + + True + irc.gimp.org + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + True + False + 0 + + + + True + + + + True + _Chat + True + + + + + + + True + _Join Channel... + True + + + + + + True + gtk-new + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _Leave Channel... + True + + + + + + True + gtk-close + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + _Servers... + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _Edit + True + + + + + + + True + gtk-cut + True + + + + + + + True + gtk-copy + True + + + + + + + True + gtk-paste + True + + + + + + + True + gtk-clear + True + + + + + + + True + + + + + + True + Find ... + True + + + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Find Ne_xt + True + + + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + gtk-preferences + True + + + + + + + + + + + True + _Action + True + + + + + + + True + Talk in the Third Person... + True + + + + + + + + True + Change _Nickname... + True + + + + + + + + True + + + + + + True + Marked A_way + True + False + + + + + + + + + + + True + _Help + True + + + + + + + True + gnome-stock-about + True + + + + + + + + + + 0 + False + False + + + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + False + 0 + + + + 6 + True + True + 0 + + + + True + False + 6 + + + + True + False + 6 + + + + True + Topic: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + + True + False + True + _Change Topic... + True + GTK_RELIEF_NORMAL + True + + + 0 + False + False + + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + True + False + + + + + + True + False + 0 + + + + True + False + 4 + + + + 0 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + 6 + True + True + True + False + False + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + True + True + + + + + 0 + True + True + + + + + False + True + + + + + + True + False + 0 + + + + True + gnome-stock-about + 1 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + <b><span color="#004784">#gtk</span></b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + True + True + 0 + + + + 6 + True + False + 6 + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + + True + False + 6 + + + + True + <b>sandmann:</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + 30 + True + True + True + True + 0 + + True + * + False + + + 0 + True + True + + + + + + True + True + gtk-ok + True + GTK_RELIEF_NORMAL + True + + + 0 + False + False + + + + + 0 + False + False + + + + + True + False + + + + + + True + False + 12 + + + + 6 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + 6 + True + True + True + False + False + True + + + + + 0 + True + True + + + + + True + True + + + + + False + True + + + + + + True + False + 0 + + + + 16 + 16 + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + #gnome + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + + + + + True + False + 0 + + + + 16 + 16 + True + <b>|</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + <b>#usability</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + + + + + True + False + 0 + + + + 16 + 16 + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + #danmark + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + 0 + True + True + + + + + + + + True + Connecting + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + False + + + + True + False + 0 + + + + True + GTK_BUTTONBOX_END + + + + True + True + True + True + _Connect + True + GTK_RELIEF_NORMAL + True + 0 + + + + + 0 + False + True + GTK_PACK_END + + + + + + 6 + True + False + 12 + + + + True + False + 3 + + + + True + <b>_Servers</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + False + 12 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + False + True + + + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_START + 0 + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-new + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + _New Server... + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + True + 0.5 + 0.5 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 3 + + + + True + gtk-properties + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + _Properties... + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + gtk-delete + True + GTK_RELIEF_NORMAL + True + + + + + 0 + False + True + + + + + 0 + True + True + + + + + 0 + False + True + + + + + + True + False + 3 + + + + True + <b>_Channels</b> + True + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + False + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + + + True + irc.gimp.org + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + True + False + 0 + + + + True + + + + True + _Chat + True + + + + + + + True + _Join Channel... + True + + + + + + True + gtk-new + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _Leave Channel... + True + + + + + + True + gtk-close + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + _Servers... + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _Edit + True + + + + + + + True + gtk-cut + True + + + + + + + True + gtk-copy + True + + + + + + + True + gtk-paste + True + + + + + + + True + gtk-clear + True + + + + + + + True + + + + + + True + Find ... + True + + + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Find Ne_xt + True + + + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + gtk-preferences + True + + + + + + + + + + + True + _Action + True + + + + + + + True + Talk in the Third Person... + True + + + + + + + + True + Change _Nickname... + True + + + + + + + + True + + + + + + True + Marked A_way + True + False + + + + + + + + + + + True + _Help + True + + + + + + + True + gnome-stock-about + True + + + + + + + + + + 0 + False + False + + + + + + 0 + 0 + True + True + True + True + GTK_POS_LEFT + False + False + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 6 + 6 + + + + True + False + 0 + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 6 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + + + False + True + + + + + + True + False + 0 + + + + True + gnome-stock-about + 1 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + <b><span color="#004784">#gtk</span></b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + True + False + 6 + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + False + True + + + + + + True + False + 0 + + + + 16 + 16 + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + #gnome + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + + + + + True + False + 0 + + + + 16 + 16 + True + <b>|</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + <b>#usability</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + + + + + + True + False + 0 + + + + 16 + 16 + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + #danmark + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 6 + 0 + + + 0 + True + True + + + + + tab + + + + + 0 + True + True + + + + + + + diff --git a/irc.c b/irc.c new file mode 100644 index 0000000..565a00d --- /dev/null +++ b/irc.c @@ -0,0 +1,931 @@ +#include "irc.h" +#include +#include + +typedef struct IrcMessage IrcMessage; +typedef struct CommandInfo CommandInfo; + +typedef enum +{ + /* Errors */ + ERR_NOSUCHNICK = 401, + ERR_NOSUCHSERVER = 402, + ERR_NOSUCHCHANNEL = 403, + ERR_CANNOTSENDTOCHAN = 404, + ERR_TOOMANYCHANNELS = 405, + ERR_WASNOSUCHNICK = 406, + ERR_TOOMANYTARGETS = 407, + ERR_NOORIGIN = 409, + ERR_NORECIPIENT = 411, + ERR_NOTEXTTOSEND = 412, + ERR_NOTOPLEVEL = 413, + ERR_WILDTOPLEVEL = 414, + ERR_UNKNOWNCOMMAND = 421, + ERR_NOMOTD = 422, + ERR_NOADMININFO = 423, + ERR_FILEERROR = 424, + ERR_NONICKNAMEGIVEN = 431, + ERR_ERRONEUSNICKNAME = 432, + ERR_NICKNAMEINUSE = 433, + ERR_NICKCOLLISION = 436, + ERR_USERNOTINCHANNEL = 441, + ERR_NOTONCHANNEL = 442, + ERR_USERONCHANNEL = 443, + ERR_NOLOGIN = 444, + ERR_SUMMONDISABLED = 445, + ERR_USERSDISABLED = 446, + ERR_NOTREGISTERED = 451, + ERR_NEEDMOREPARAMS = 461, + ERR_ALREADYREGISTRED = 462, + ERR_NOPERMFORHOST = 463, + ERR_PASSWDMISMATCH = 464, + ERR_YOUREBANNEDCREEP = 465, + ERR_KEYSET = 467, + ERR_CHANNELISFULL = 471, + ERR_UNKNOWNMODE = 472, + ERR_INVITEONLYCHAN = 473, + ERR_BANNEDFROMCHAN = 474, + ERR_BADCHANNELKEY = 475, + ERR_NOPRIVILEGES = 481, + ERR_CHANOPRIVSNEEDED = 482, + ERR_CANTKILLSERVER = 483, + ERR_NOOPERHOST = 491, + ERR_UMODEUNKNOWNFLAG = 501, + ERR_USERSDONTMATCH = 502, + + /* Replies */ + RPL_NONE = 300, + RPL_USERHOST = 302, + RPL_ISON = 303, + RPL_AWAY = 301, + RPL_UNAWAY = 305, + RPL_NOWAWAY = 306, + RPL_WHOISUSER = 311, + RPL_WHOISSERVER = 312, + RPL_WHOISOPERATOR = 313, + RPL_WHOISIDLE = 317, + RPL_ENDOFWHOIS = 318, + RPL_WHOWASUSER = 314, + RPL_ENDOFWHOWAS = 369, + RPL_LISTSTART = 321, + RPL_LIST = 322, + RPL_LISTEND = 323, + RPL_CHANNELMODEIS = 324, + RPL_NOTOPIC = 331, + RPL_TOPIC = 332, + RPL_INVITING = 341, + RPL_SUMMONING = 342, + RPL_VERSION = 351, + RPL_WHOREPLY = 352, + RPL_ENDOFWHO = 315, + RPL_NAMREPLY = 353, + RPL_ENDOFNAMES = 366, + RPL_LINKS = 364, + RPL_ENDOFLINKS = 365, + RPL_BANLIST = 367, + RPL_ENDOFBANLIST = 368, + RPL_INFO = 371, + RPL_ENDOFINFO = 374, + RPL_MOTDSTART = 375, + RPL_MOTD = 372, + RPL_ENDOFMOTD = 376, + RPL_YOUREOPER = 381, + RPL_REHASHING = 382, + RPL_TIME = 391, + RPL_USERSSTART = 392, + RPL_USERS = 393, + RPL_ENDOFUSERS = 394, + RPL_NOUSERS = 395, + RPL_TRACELINK = 200, + RPL_TRACECONNECTING = 201, + RPL_TRACEHANDSHAKE = 202, + RPL_TRACEUNKNOWN = 203, + RPL_TRACEOPERATOR = 204, + RPL_TRACEUSER = 205, + RPL_TRACESERVER = 206, + RPL_TRACENEWTYPE = 208, + RPL_TRACELOG = 261, + RPL_STATSLINKINFO = 211, + RPL_STATSCOMMANDS = 212, + RPL_STATSCLINE = 213, + RPL_STATSNLINE = 214, + RPL_STATSILINE = 215, + RPL_STATSKLINE = 216, + RPL_STATSYLINE = 218, + RPL_ENDOFSTATS = 219, + RPL_STATSLLINE = 241, + RPL_STATSUPTIME = 242, + RPL_STATSOLINE = 243, + RPL_STATSHLINE = 244, + RPL_UMODEIS = 221, + RPL_LUSERCLIENT = 251, + RPL_LUSEROP = 252, + RPL_LUSERUNKNOWN = 253, + RPL_LUSERCHANNELS = 254, + RPL_LUSERME = 255, + RPL_ADMINME = 256, + RPL_ADMINLOC1 = 257, + RPL_ADMINLOC2 = 258, + RPL_ADMINEMAIL = 259, + + /* These codes are not specified by the RFC spec, just + * used internally. (IRC can't use them, because they + * have more than three digits) + */ + CMD_NOTICE = 9001, + CMD_PRIVMSG = 9002, + CMD_PASS = 9003, + CMD_NICK = 9004, + CMD_USER = 9005, + + CMD_UNKNOWN = 10000, /* message not understood */ +} ReplyCode; + +#define CR 0x0d +#define LF 0x0a +#define NUL 0x00 +#define BELL 0x07 +#define SPACE 0x20 +#define COMMA 0x2c + +static const gchar CRLF[] = { CR, LF, '\0' }; + +struct CommandInfo +{ + const ReplyCode code; + const char *text; +}; + +static const CommandInfo commands[] = +{ + { CMD_NOTICE, "NOTICE" }, + { CMD_PRIVMSG, "PRIVMSG" }, + { CMD_USER, "USER" }, + { CMD_PASS, "PASS" }, + { CMD_NICK, "NICK" }, + { CMD_UNKNOWN, "Unknown command" } +}; + +static ReplyCode +command_to_code (const char *start, const char *end) +{ + char *command = g_strndup (start, end - start); + ReplyCode code = CMD_UNKNOWN; + int i; + + for (i = 0; i < G_N_ELEMENTS (commands); ++i) + { + if (strcmp (command, commands[i].text) == 0) + { + code = commands[i].code; + break; + } + } + + return code; +} + +static const char * +code_to_command (ReplyCode code) +{ + const char *cmd = NULL; + int i; + + for (i = 0; i < G_N_ELEMENTS (commands); ++i) + { + if (commands[i].code == code) + { + cmd = commands[i].text; + break; + } + } + + return cmd; +} + +struct IrcMessage +{ + char * prefix; + ReplyCode code; + GQueue * parameters; + + GList * free_list; +}; + +static IrcMessage *message_new_parsed (const char **input, + int *length); +static void message_free (IrcMessage *msg); + +static gpointer trace (gpointer data, + gpointer free_func, + GList **freelist); + +/* + * Message parser + */ +static int +eat_spaces (const char **input, int *length) +{ + int n_spaces; + + n_spaces = 0; + while (**input == SPACE) + { + *input += 1; + *length -= 1; + + n_spaces++; + } + + return n_spaces; +} + +static const char * +find_char (const char *input, int length, char c) +{ + int i; + for (i = 0; i < length; ++i) + { + if (input[i] == c) + return input + i; + } + return NULL; +} + +static char * +parse_prefix (const char **input, + int *length, + GList **free_list, + gboolean *error) +{ + const char *space; + + space = find_char (*input, *length, SPACE); + if (!space || space == *input) + { + *error = TRUE; + return NULL; + } + else + { + int prefix_len = space - *input; + char *result = g_strndup (*input, prefix_len); + + *input += prefix_len; + *length -= prefix_len; + + return trace (result, g_free, free_list); + } +} + +static ReplyCode +parse_command (const char **input, + int *length, + GList **free_list, + gboolean *err) +{ + ReplyCode result; + + if (*length < 1) + goto error; + + if (g_ascii_isalpha (**input)) + { + const char *start = *input; + + while (*length > 0 && g_ascii_isalpha (**input)) + { + *input += 1; + *length -= 1; + } + + return command_to_code (start, *input); + } + else if (g_ascii_isdigit (**input)); + { + int d1, d2, d3; + + if (*length < 3) + goto error; + + d1 = (*input)[0] - '0'; + d2 = (*input)[1] - '0'; + d3 = (*input)[2] - '0'; + + if (d1 < 0 || d1 > 9 || + d2 < 0 || d2 > 9 || + d3 < 0 || d3 > 9) + { + goto error; + } + + *input += 3; + *length -= 3; + + return (ReplyCode) d1 * 100 + d2 * 10 + d3; + } + + return result; + +error: + *err = TRUE; + return -1; +} + +static gboolean +is_trailing_char (char c) +{ + return c != NUL && c != CR && c != LF; +} + +static gboolean +is_middle_char (char c) +{ + return c != SPACE && c != NUL && c != CR && c != LF; +} + +static char * +parse_trailing (const char **input, + int *length, + GList **free_list, + gboolean *err) +{ + const char *start = *input; + + while (*length > 0 && is_trailing_char (**input)) + { + *input += 1; + *length -= 1; + } + + if (*input != start) + return trace (g_strndup (start, *input - start), g_free, free_list); + else + return NULL; +} + +static char * +parse_middle (const char **input, + int *length, + GList **free_list, + gboolean *err) +{ + const char *start = *input; + + while (*length > 0 && is_middle_char (**input)) + { + *input += 1; + *length -= 1; + } + + if (*input == start) + goto error; + + return trace (g_strndup (start, *input - start), g_free, free_list); + +error: + *err = TRUE; + return NUL; +} + +static GQueue * +parse_params (const char **input, + int *length, + GList **free_list, + gboolean *err) +{ + GQueue *params = NULL; + + if (*length < 1) + goto error; + + if (**input == SPACE) + eat_spaces (input, length); + + if (*length > 0) + { + char *trailing; + + if (**input == ':') + { + *input += 1; + *length -= 1; + + /* */ + trailing = parse_trailing (input, length, free_list, err); + if (*err) + goto error; + + if (trailing) + { + params = trace (g_queue_new(), g_queue_free, free_list); + + g_queue_push_tail (params, trailing); + } + } + else if (is_middle_char (**input)) + { + char *middle; + + /* */ + middle = parse_middle (input, length, free_list, err); + if (*err) + goto error; + + /* */ + + /* The RFC states that parameters always start with SPACE, + * but that is a lie: The final params following a + * can be the empty string + */ + if (**input == SPACE) + { + params = parse_params (input, length, free_list, err); + if (*err) + goto error; + } + else + { + params = trace (g_queue_new(), g_queue_free, free_list); + + g_queue_push_head (params, middle); + } + } + } + + if (!params) + params = trace (g_queue_new(), g_queue_free, free_list); + + return params; + +error: + *err = TRUE; + return NULL; +} + +/* Returns NULL if *input is not a valid message. This will also + * happen if we just haven't received enough data yet. + */ +static IrcMessage * +message_new_parsed (const char **input, + int *length) +{ + IrcMessage *msg = g_new (IrcMessage, 1); + const char *crlf; + gboolean err = FALSE; + + msg->free_list = NULL; + + if (*length < 2) + goto error; + + crlf = strstr (*input, CRLF); + + if (!crlf) + goto error; + + if (crlf - *input > 510) + goto error; + + msg->prefix = NULL; + if (**input == ':') + { + *input += 1; + *length -= 1; + + msg->prefix = parse_prefix (input, length, &msg->free_list, &err); + + if (err) + goto error; + + if (eat_spaces (input, length) < 1) + goto error; + } + + msg->code = parse_command (input, length, &msg->free_list, &err); + if (err) + goto error; + + msg->parameters = parse_params (input, length, &msg->free_list, &err); + + if (err) + goto error; + + if (*length < 2) + goto error; + + if (strncmp (*input, CRLF, 2) != 0) + goto error; + + *input += 2; + *length -= 2; + + return msg; + +error: + message_free (msg); + return NULL; +} + +static void +message_free (IrcMessage *msg) +{ + GList *list; + + for (list = msg->free_list; list != NULL; list = list->next->next) + { + GDestroyNotify free_func = list->data; + gpointer alloc = list->next->data; + + (* free_func) (alloc); + } + + g_list_free (msg->free_list); + + g_free (msg); +} + +typedef enum +{ + NOT_CONNECTED, + CONNECTING, + CONNECTED +} IrcServerState; + +struct IrcServer +{ + /* information about server */ + char * hostname; + gint port; + + /* information about user */ + char * local_user_name; + char * local_host_name; + char * local_domain_name; + char * real_name; + char * nickname; + char * password; + + /* state */ + IrcServerState state; + LacConnection * connection; + IrcServerFunc func; + gpointer data; + GString * unparsed; + GQueue * messages; +}; + +IrcServer * +irc_server_new (const char *hostname, + gint port, + IrcServerFunc func, + gpointer data) +{ + IrcServer *server; + + g_return_val_if_fail (hostname != NULL, NULL); + g_return_val_if_fail (port > 0, NULL); + g_return_val_if_fail (func != NULL, NULL); + + server = g_new (IrcServer, 1); + + server->hostname = g_strdup (hostname); + server->port = port; + + server->local_user_name = NULL; + server->local_host_name = NULL; + server->local_domain_name = NULL; + server->real_name = NULL; + server->nickname = NULL; + server->password = NULL; + + server->state = NOT_CONNECTED; + server->connection = NULL; + server->func = func; + server->data = data; + server->unparsed = g_string_new (""); + server->messages = g_queue_new (); + + return server; +} + +gpointer +irc_server_get_data (IrcServer *server) +{ + g_return_val_if_fail (server != NULL, NULL); + + return server->data; +} + +static void +server_emit_dns_complete (IrcServer *server) +{ +} + +static void +server_emit_error (IrcServer *server, + const GError *err) +{ + +} + +static void +server_emit_connect (IrcServer *server) +{ + +} + +static void +server_emit_close (IrcServer *server) +{ + +} + +static void +emit_malformed_error (IrcServer *server, const char *msg) +{ + GError *error = NULL; + + g_set_error (&error, + IRC_ERROR, + IRC_ERROR_MALFORMED_MESSAGE, + "Server %s sent an unparsable message (%s) " + "(This is most likely a bug in ChatterBox)", + server->hostname, + msg); + + server_emit_error (server, error); + + g_error_free (error); +} + +static gboolean +process_data (IrcServer *server) +{ + char *crlf = strstr (server->unparsed->str, CRLF); + + if (!crlf && server->unparsed->len > 510) + { + /* We have seen 510 bytes in a row without + * a CRLF. This means the server is sending + * garbage + */ + emit_malformed_error (server, "No CRLF"); + lac_connection_close (server->connection); + return FALSE; + } + else if (crlf) + { + IrcMessage *msg; + const char *s = server->unparsed->str; + int len = server->unparsed->len; + + msg = message_new_parsed (&s, &len); + + if (!msg) + { + emit_malformed_error (server, "parse error"); + lac_connection_close (server->connection); + return FALSE; + } + + g_string_erase (server->unparsed, 0, server->unparsed->len - len); + + /* process message */ +#if 0 + g_print ("\ncommand: %s - %d ", msg->prefix? msg->prefix : "", msg->code); + for (list = msg->parameters->head; list != NULL; list = list->next) + { + g_print ("%s ", (char *)list->data); + } + g_print ("\n"); +#endif + + message_free (msg); + return TRUE; + } + else + { + g_print ("no data/not enough data\n"); + /* No CRLF and less than 510 bytes: not enough + * data yet + */ + return FALSE; + } +} + +static void +server_handle_read (IrcServer *server, + const LacConnectionReadEvent *read) +{ + int i; + + for (i = 0; i < read->len; ++i) + g_print ("%c", read->data[i]); + + g_string_append_len (server->unparsed, read->data, read->len); + + while (process_data (server)) + ; +} + +static void +server_handle_disconnect (IrcServer *server) +{ + server->state = NOT_CONNECTED; + lac_connection_unref (server->connection); + server->connection = NULL; +} + +static void +server_send_command (IrcServer *server, + const char *prefix, + ReplyCode code, + const char *param, + ...) +{ + va_list var_args; + GQueue *params = g_queue_new (); + const char *command; + GList *list; + GString *message; + + command = code_to_command (code); + + g_return_if_fail (command != NULL); + + va_start (var_args, param); + + while (param != NULL) + { + g_queue_push_tail (params, (gpointer)param); + + param = va_arg (var_args, char *); + } + + va_end (var_args); + + message = g_string_new (""); + + if (prefix) + { + g_string_append (message, ":"); + g_string_append (message, prefix); + g_string_append (message, " "); + } + + g_string_append (message, command); + + for (list = params->head; list != NULL; list = list->next) + { + g_string_append (message, " "); + if (!list->next) + g_string_append (message, ":"); + g_string_append (message, list->data); + } + + g_string_append (message, CRLF); + + lac_connection_write (server->connection, message->str, message->len); + + g_string_free (message, TRUE); +} + +static void +server_send_registration (IrcServer *server) +{ + server_send_command (server, NULL, CMD_PASS, server->password, NULL); + server_send_command (server, NULL, CMD_NICK, server->nickname, NULL); + server_send_command ( + server, NULL, CMD_USER, + server->local_user_name? server->local_user_name : "unknown", + server->local_host_name? server->local_host_name : "unknown", + server->local_domain_name? server->local_domain_name : "unknown", + server->real_name? server->real_name : "Unknown", + NULL); +} + +static void +connection_callback (LacConnection *connection, + const LacConnectionEvent *event) +{ + IrcServer *server = lac_connection_get_data (connection); + + g_assert (server); + + switch (event->type) + { + case LAC_CONNECTION_EVENT_CONNECT: /* connect successful */ + server_emit_connect (server); + server->state = CONNECTED; + server_send_registration (server); + break; + + case LAC_CONNECTION_EVENT_READ: + server_handle_read (server, &(event->read)); + break; /* data to read */ + + case LAC_CONNECTION_EVENT_CLOSE: /* Connection closed */ + if (event->close.remote_closed) + server_emit_close (server); + server_handle_disconnect (server); + break; + + case LAC_CONNECTION_EVENT_ERROR: /* error */ + server_emit_error (server, event->error.err); + server_handle_disconnect (server); + break; + } +} + +static void +address_callback (const LacAddress * address, + gpointer data, + const GError * err) +{ + IrcServer *server = data; + + if (err) + { + server_emit_error (server, err); + } + else + { + server_emit_dns_complete (server); + server->connection = lac_connection_new ( + address, server->port, connection_callback, server); + } +} + +static char * +generate_password (void) +{ + return g_strdup_printf ("CH%x%x%x", + g_random_int(), + g_random_int(), + g_random_int()); +} + +void +irc_server_connect (IrcServer *server, + const char *nick, + const char *password) +{ + char *orig_pw, *orig_nick; + + g_return_if_fail (server != NULL); + g_return_if_fail (server->state == NOT_CONNECTED); + g_return_if_fail (nick != NULL); + + if (server->state != NOT_CONNECTED) + return; + + orig_pw = server->password; + orig_nick = server->nickname; + + if (password) + server->password = g_strdup (password); + else + server->password = generate_password (); + + server->nickname = g_strdup (nick); + + if (orig_pw) + g_free (orig_pw); + if (orig_nick) + g_free (orig_nick); + + server->state = CONNECTING; + lac_address_new_lookup_from_name ( + server->hostname, address_callback, server); +} + +void +irc_server_disconnect (IrcServer *server) +{ +} + +/* Tracing memory + */ + +static gpointer +trace (gpointer data, + gpointer free_func, + GList **free_list) +{ + *free_list = g_list_prepend (*free_list, data); + *free_list = g_list_prepend (*free_list, free_func); + return data; +} + +GQuark +irc_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) + q = g_quark_from_static_string ("irc-error-quark"); + + return q; +} + diff --git a/irc.h b/irc.h new file mode 100644 index 0000000..a9a3520 --- /dev/null +++ b/irc.h @@ -0,0 +1,70 @@ +#include + +/* + * Irc utilities + */ + +/* + * IrcServer + */ + +typedef struct IrcServer IrcServer; +typedef struct IrcChannel IrcChannel; + +#define IRC_ERROR irc_error_quark () + +typedef enum +{ + IRC_ERROR_MALFORMED_MESSAGE +} IrcError; + +GQuark irc_error_quark (void); + +typedef enum +{ + IRC_SERVER_LOOKING_UP_HOSTNAME, + IRC_SERVER_FOUND_HOST_NAME, + IRC_SERVER_CONNECTING, + IRC_SERVER_CONNECTED, + IRC_SERVER_DISCONNECT, + IRC_SERVER_ERROR +} IrcServerEvent; + +typedef enum +{ + IRC_CHANNEL_MESSAGE, + IRC_TOPIC_CHANGED, + IRC_USER_JOINED, +} IrcChannelEvent; + +typedef void (* IrcServerFunc) (IrcServer *server, + const IrcServerEvent *event); + +typedef void (* IrcChannelListFunc) (GList *channels, gpointer data); + +IrcServer *irc_server_new (const char *name, + gint port, + IrcServerFunc func, + gpointer data); +gpointer irc_server_get_data (IrcServer *server); + +void irc_server_list_channels (IrcServer *server, + IrcChannelListFunc func, + gpointer data); + +void irc_server_connect (IrcServer *server, + const char *nickname, + const char *password); +void irc_server_disconnect (IrcServer *server); + +typedef void (* IrcChannelFunc) (IrcChannel *channel, + const IrcChannelEvent *event, + gpointer data); + +IrcChannel *irc_server_join_channel (IrcServer *server, + const char *name, + IrcChannelFunc func, + gpointer data); + + +/* Channel operations */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..9df80f9 --- /dev/null +++ b/main.c @@ -0,0 +1,41 @@ +#include "channel-buffer.h" +#include "irc.h" + +static void +irc_func (IrcServer *server, + const IrcServerEvent *event) +{ + +} + +int +main (int argc, char **argv) +{ +#if 0 + GMainLoop *loop; + IrcServer *server = irc_server_new ("irc.gimp.org", 6667, irc_func, "hej"); + + loop = g_main_loop_new (NULL, FALSE); + + irc_server_connect (server, "ssp", "birnan"); + + g_main_loop_run (loop); +#endif + gtk_init (&argc, &argv); + + ChannelBuffer *buffer = channel_buffer_new ("ssp"); + + GtkWindow *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + GtkWidget *text = gtk_text_view_new (); + + gtk_text_view_set_buffer (GTK_TEXT_VIEW (text), + channel_buffer_get_text_buffer (buffer)); + + gtk_container_add (GTK_CONTAINER (window), text); + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +} -- cgit v1.2.3