about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am3
-rw-r--r--src/command.c64
-rw-r--r--src/input_win.c6
-rw-r--r--src/jabber.c264
-rw-r--r--src/jabber.h3
-rw-r--r--src/room_chat.c108
-rw-r--r--src/room_chat.h28
-rw-r--r--src/ui.h9
-rw-r--r--src/windows.c111
9 files changed, 512 insertions, 84 deletions
diff --git a/Makefile.am b/Makefile.am
index 529124a9..29ccad96 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,7 +7,8 @@ profanity_SOURCES = src/command.c src/contact.c src/history.c src/jabber.h \
 	src/prof_history.c src/ui.h src/common.h src/ contact_list.h src/jabber.c \
 	src/main.c src/profanity.h src/prof_history.h src/chat_log.c \
 	src/chat_log.h src/tinyurl.c src/tinyurl.h src/chat_session.c \
-	src/chat_session.h src/release.c src/release.h
+	src/chat_session.h src/release.c src/release.h src/room_chat.c \
+	src/room_chat.h
 
 TESTS = tests/testsuite
 check_PROGRAMS = tests/testsuite
diff --git a/src/command.c b/src/command.c
index f5fe2177..785b133c 100644
--- a/src/command.c
+++ b/src/command.c
@@ -87,6 +87,7 @@ static gboolean _cmd_sub(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_msg(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_tiny(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_close(const char * const inp, struct cmd_help_t help);
+static gboolean _cmd_join(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_set_beep(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_set_notify(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_set_intype(const char * const inp, struct cmd_help_t help);
@@ -182,6 +183,19 @@ static struct cmd_t main_commands[] =
           "Example : /msg boothj5@gmail.com Hey, here's a message!",
           NULL } } },
 
+    { "/join",
+        _cmd_join,
+        { "/join room@server [nick]", "Join a chat room.",
+        { "/join room@server [nick]",
+          "------------------------",
+          "Join a chat room at the conference server.",
+          "If nick is specified you will join with this nickname,",
+          "otherwise the first part of your JID (before the @) will be used.",
+          "",
+          "Example : /join jdev@conference.jabber.org",
+          "Example : /join jdev@conference.jabber.org mynick",
+          NULL } } },
+
     { "/sub",
         _cmd_sub,
         { "/sub user@host", "Subscribe to presence notifications of user.",
@@ -597,7 +611,11 @@ cmd_execute(const char * const command, const char * const inp)
 gboolean
 cmd_execute_default(const char * const inp)
 {
-    if (win_in_chat()) {
+    if (win_in_groupchat()) {
+        char *recipient = win_get_recipient();
+        jabber_send_groupchat(inp, recipient);
+        free(recipient);
+    } else if (win_in_chat()) {
         char *recipient = win_get_recipient();
         jabber_send(inp, recipient);
 
@@ -969,6 +987,46 @@ _cmd_msg(const char * const inp, struct cmd_help_t help)
 }
 
 static gboolean
+_cmd_join(const char * const inp, struct cmd_help_t help)
+{
+    char *room_jid = NULL;
+    char *nick = NULL;
+
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
+
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are not currently connected.");
+    } else {
+        // copy input
+        char inp_cpy[strlen(inp) + 1];
+        strcpy(inp_cpy, inp);
+
+        // get room jid
+        strtok(inp_cpy, " ");
+        room_jid = strtok(NULL, " ");
+        if (room_jid == NULL) {
+            cons_show("Usage: %s", help.usage);
+        } else {
+            if ((strlen(inp) > (6 + strlen(room_jid) + 1))) {
+                nick = strndup(inp+6+strlen(room_jid)+1, strlen(inp)-(6+strlen(room_jid)+1));
+            }
+
+            // if no nick, set to first part of jid
+            if (nick == NULL) {
+                const char *jid = jabber_get_jid();
+                char jid_cpy[strlen(jid) + 1];
+                strcpy(jid_cpy, jid);
+                nick = strdup(strtok(jid_cpy, "@"));
+            }
+            jabber_join(room_jid, nick);
+            win_join_chat(room_jid, nick);
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
 _cmd_tiny(const char * const inp, struct cmd_help_t help)
 {
     if (strlen(inp) > 6) {
@@ -1024,6 +1082,10 @@ _cmd_tiny(const char * const inp, struct cmd_help_t help)
 static gboolean
 _cmd_close(const char * const inp, struct cmd_help_t help)
 {
+    if (win_in_groupchat()) {
+        char *room_jid = win_get_recipient();
+        jabber_leave_chat_room(room_jid);
+    }
     if (win_in_chat()) {
 
         if (prefs_get_states()) {
diff --git a/src/input_win.c b/src/input_win.c
index c5de7538..eb7ebe19 100644
--- a/src/input_win.c
+++ b/src/input_win.c
@@ -149,7 +149,7 @@ inp_get_char(int *ch, char *input, int *size)
         // if not got char, and in chat window, flag as no activity
         // send inactive or gone, depending how long inactive
         if (*ch == ERR) {
-            if (win_in_chat()) {
+            if (win_in_chat() && !win_in_groupchat()) {
                 char *recipient = win_get_recipient();
                 chat_session_no_activity(recipient);
 
@@ -168,8 +168,8 @@ inp_get_char(int *ch, char *input, int *size)
         }
 
         // if got char and in chat window, chat session active
-        if (prefs_get_outtype() && (*ch != ERR) && win_in_chat() && !in_command &&
-                _printable(*ch)) {
+        if (prefs_get_outtype() && (*ch != ERR) && win_in_chat() &&
+                !win_in_groupchat() && !in_command && _printable(*ch)) {
             char *recipient = win_get_recipient();
             chat_session_set_composing(recipient);
             if (!chat_session_get_sent(recipient) ||
diff --git a/src/jabber.c b/src/jabber.c
index 766c78bf..d294ba0a 100644
--- a/src/jabber.c
+++ b/src/jabber.c
@@ -32,6 +32,10 @@
 #include "log.h"
 #include "preferences.h"
 #include "profanity.h"
+#include "room_chat.h"
+
+// TODO REMOVE
+#include "ui.h"
 
 #define PING_INTERVAL 120000 // 2 minutes
 
@@ -183,6 +187,36 @@ jabber_send(const char * const msg, const char * const recipient)
 }
 
 void
+jabber_send_groupchat(const char * const msg, const char * const recipient)
+{
+    char *coded_msg = str_replace(msg, "&", "&");
+    char *coded_msg2 = str_replace(coded_msg, "<", "&lt;");
+    char *coded_msg3 = str_replace(coded_msg2, ">", "&gt;");
+
+    xmpp_stanza_t *reply, *body, *text;
+
+    reply = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(reply, "message");
+    xmpp_stanza_set_type(reply, "groupchat");
+    xmpp_stanza_set_attribute(reply, "to", recipient);
+
+    body = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(body, "body");
+
+    text = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_text(text, coded_msg3);
+
+    xmpp_stanza_add_child(body, text);
+    xmpp_stanza_add_child(reply, body);
+
+    xmpp_send(jabber_conn.conn, reply);
+    xmpp_stanza_release(reply);
+    free(coded_msg);
+    free(coded_msg2);
+    free(coded_msg3);
+}
+
+void
 jabber_send_inactive(const char * const recipient)
 {
     xmpp_stanza_t *message, *inactive;
@@ -280,6 +314,42 @@ jabber_subscribe(const char * const recipient)
 }
 
 void
+jabber_join(const char * const room_jid, const char * const nick)
+{
+    xmpp_stanza_t *presence = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(presence, "presence");
+
+    GString *to = g_string_new(room_jid);
+    g_string_append(to, "/");
+    g_string_append(to, nick);
+
+    xmpp_stanza_set_attribute(presence, "to", to->str);
+    xmpp_send(jabber_conn.conn, presence);
+    xmpp_stanza_release(presence);
+
+    room_join(room_jid, nick);
+}
+
+void
+jabber_leave_chat_room(const char * const room_jid)
+{
+    char *nick = room_get_nick_for_room(room_jid);
+    GString *full_jid = g_string_new(room_jid);
+    g_string_append(full_jid, "/");
+    g_string_append(full_jid, nick);
+
+    xmpp_stanza_t *presence = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(presence, "presence");
+    xmpp_stanza_set_type(presence, "unavailable");
+    xmpp_send(jabber_conn.conn, presence);
+    xmpp_stanza_release(presence);
+
+    g_string_free(full_jid, TRUE);
+
+    room_leave(room_jid);
+}
+
+void
 jabber_update_presence(jabber_presence_t status, const char * const msg)
 {
     jabber_conn.presence = status;
@@ -379,83 +449,113 @@ _message_handler(xmpp_conn_t * const conn,
     type = xmpp_stanza_get_attribute(stanza, "type");
     from = xmpp_stanza_get_attribute(stanza, "from");
 
-    if (type != NULL) {
-        if (strcmp(type, "error") == 0) {
-            char *err_msg = NULL;
-            xmpp_stanza_t *error = xmpp_stanza_get_child_by_name(stanza, "error");
-            if (error == NULL) {
-                log_debug("error message without <error/> received");
-                return 1;
+    if (room_jid_is_room_chat(from)) {
+        xmpp_stanza_t *delay = xmpp_stanza_get_child_by_name(stanza, "delay");
+        if (delay != NULL) {
+            char *utc_stamp = xmpp_stanza_get_attribute(delay, "stamp");
+            GTimeVal tv_stamp;
+            if(g_time_val_from_iso8601(utc_stamp, &tv_stamp)) {
+                xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, "body");
+                if (body != NULL) {
+                    char *message = xmpp_stanza_get_text(body);
+                    char **tokens = g_strsplit(from, "/", 0);
+                    char *room_jid = tokens[0];
+                    char *nick = tokens[1];
+                    win_show_room_history(room_jid, nick, tv_stamp, message);
+                }
             } else {
-                xmpp_stanza_t *err_cond = xmpp_stanza_get_children(error);
-                if (err_cond == NULL) {
-                    log_debug("error message without <defined-condition/> received");
+                log_error("Couldn't parse datetime string receiving room history: %s", utc_stamp);
+            }
+        } else {
+            xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, "body");
+            if (body != NULL) {
+                char *message = xmpp_stanza_get_text(body);
+                char **tokens = g_strsplit(from, "/", 0);
+                char *room_jid = tokens[0];
+                char *nick = tokens[1];
+                win_show_room_message(room_jid, nick, message);
+            }
+        }
+
+    } else {
+
+        if (type != NULL) {
+            if (strcmp(type, "error") == 0) {
+                char *err_msg = NULL;
+                xmpp_stanza_t *error = xmpp_stanza_get_child_by_name(stanza, "error");
+                if (error == NULL) {
+                    log_debug("error message without <error/> received");
                     return 1;
                 } else {
-                    err_msg = xmpp_stanza_get_name(err_cond);
+                    xmpp_stanza_t *err_cond = xmpp_stanza_get_children(error);
+                    if (err_cond == NULL) {
+                        log_debug("error message without <defined-condition/> received");
+                        return 1;
+                    } else {
+                        err_msg = xmpp_stanza_get_name(err_cond);
+                    }
+                    // TODO: process 'type' attribute from <error/> [RFC6120, 8.3.2]
                 }
-                // TODO: process 'type' attribute from <error/> [RFC6120, 8.3.2]
+                prof_handle_error_message(from, err_msg);
+                return 1;
             }
-            prof_handle_error_message(from, err_msg);
-            return 1;
         }
-    }
-
 
-    char from_cpy[strlen(from) + 1];
-    strcpy(from_cpy, from);
-    char *short_from = strtok(from_cpy, "/");
+        char from_cpy[strlen(from) + 1];
+        strcpy(from_cpy, from);
+        char *short_from = strtok(from_cpy, "/");
 
-    //determine chatstate support of recipient
-    gboolean recipient_supports = FALSE;
+        //determine chatstate support of recipient
+        gboolean recipient_supports = FALSE;
 
-    if ((xmpp_stanza_get_child_by_name(stanza, "active") != NULL) ||
-            (xmpp_stanza_get_child_by_name(stanza, "composing") != NULL) ||
-            (xmpp_stanza_get_child_by_name(stanza, "paused") != NULL) ||
-            (xmpp_stanza_get_child_by_name(stanza, "gone") != NULL) ||
-            (xmpp_stanza_get_child_by_name(stanza, "inactive") != NULL)) {
-        recipient_supports = TRUE;
-    }
+        if ((xmpp_stanza_get_child_by_name(stanza, "active") != NULL) ||
+                (xmpp_stanza_get_child_by_name(stanza, "composing") != NULL) ||
+                (xmpp_stanza_get_child_by_name(stanza, "paused") != NULL) ||
+                (xmpp_stanza_get_child_by_name(stanza, "gone") != NULL) ||
+                (xmpp_stanza_get_child_by_name(stanza, "inactive") != NULL)) {
+            recipient_supports = TRUE;
+        }
 
-    // create of update session
-    if (!chat_session_exists(short_from)) {
-        chat_session_start(short_from, recipient_supports);
-    } else {
-        chat_session_set_recipient_supports(short_from, recipient_supports);
-    }
+        // create of update session
+        if (!chat_session_exists(short_from)) {
+            chat_session_start(short_from, recipient_supports);
+        } else {
+            chat_session_set_recipient_supports(short_from, recipient_supports);
+        }
 
-    // deal with chat states
-    if (recipient_supports) {
+        // deal with chat states
+        if (recipient_supports) {
 
-        // handle <composing/>
-        if (xmpp_stanza_get_child_by_name(stanza, "composing") != NULL) {
-            if (prefs_get_notify_typing() || prefs_get_intype()) {
-                prof_handle_typing(short_from);
-            }
+            // handle <composing/>
+            if (xmpp_stanza_get_child_by_name(stanza, "composing") != NULL) {
+                if (prefs_get_notify_typing() || prefs_get_intype()) {
+                    prof_handle_typing(short_from);
+                }
 
-        // handle <paused/>
-        } else if (xmpp_stanza_get_child_by_name(stanza, "paused") != NULL) {
-            // do something
+            // handle <paused/>
+            } else if (xmpp_stanza_get_child_by_name(stanza, "paused") != NULL) {
+                // do something
 
-        // handle <inactive/>
-        } else if (xmpp_stanza_get_child_by_name(stanza, "inactive") != NULL) {
-            // do something
+            // handle <inactive/>
+            } else if (xmpp_stanza_get_child_by_name(stanza, "inactive") != NULL) {
+                // do something
 
-        // handle <gone/>
-        } else if (xmpp_stanza_get_child_by_name(stanza, "gone") != NULL) {
-            prof_handle_gone(short_from);
+            // handle <gone/>
+            } else if (xmpp_stanza_get_child_by_name(stanza, "gone") != NULL) {
+                prof_handle_gone(short_from);
 
-        // handle <active/>
-        } else {
-            // do something
+            // handle <active/>
+            } else {
+                // do something
+            }
         }
-    }
 
-    // check for and deal with message
-    xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, "body");
-    if (body != NULL) {
-        char *message = xmpp_stanza_get_text(body);
-        prof_handle_incoming_message(short_from, message);
+        // check for and deal with message
+        xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, "body");
+        if (body != NULL) {
+            char *message = xmpp_stanza_get_text(body);
+            prof_handle_incoming_message(short_from, message);
+        }
     }
 
     return 1;
@@ -578,30 +678,40 @@ _presence_handler(xmpp_conn_t * const conn,
     char *short_jid = strtok(jid_cpy, "/");
 
     char *from = xmpp_stanza_get_attribute(stanza, "from");
-    char *short_from = strtok(from, "/");
-    char *type = xmpp_stanza_get_attribute(stanza, "type");
-    char *show_str, *status_str;
-
-    xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, "status");
-    if (status != NULL)
-        status_str = xmpp_stanza_get_text(status);
-    else
-        status_str = NULL;
 
-    if ((type != NULL) && (strcmp(type, "unavailable") == 0)) {
-        if (strcmp(short_jid, short_from) !=0) {
-            prof_handle_contact_offline(short_from, "offline", status_str);
+    if (room_jid_is_room_chat(from)) {
+        char **tokens = g_strsplit(from, "/", 0);
+        char *room_jid = tokens[0];
+        char *nick = tokens[1];
+        if (strcmp(room_get_nick_for_room(room_jid), nick) != 0) {
+            win_show_chat_room_member(room_jid, nick);
         }
     } else {
+        char *short_from = strtok(from, "/");
+        char *type = xmpp_stanza_get_attribute(stanza, "type");
+        char *show_str, *status_str;
 
-        xmpp_stanza_t *show = xmpp_stanza_get_child_by_name(stanza, "show");
-        if (show != NULL)
-            show_str = xmpp_stanza_get_text(show);
+        xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, "status");
+        if (status != NULL)
+            status_str = xmpp_stanza_get_text(status);
         else
-            show_str = "online";
+            status_str = NULL;
 
-        if (strcmp(short_jid, short_from) !=0) {
-            prof_handle_contact_online(short_from, show_str, status_str);
+        if ((type != NULL) && (strcmp(type, "unavailable") == 0)) {
+            if (strcmp(short_jid, short_from) !=0) {
+                prof_handle_contact_offline(short_from, "offline", status_str);
+            }
+        } else {
+
+            xmpp_stanza_t *show = xmpp_stanza_get_child_by_name(stanza, "show");
+            if (show != NULL)
+                show_str = xmpp_stanza_get_text(show);
+            else
+                show_str = "online";
+
+            if (strcmp(short_jid, short_from) !=0) {
+                prof_handle_contact_online(short_from, show_str, status_str);
+            }
         }
     }
 
diff --git a/src/jabber.h b/src/jabber.h
index 3ae84610..7abb4631 100644
--- a/src/jabber.h
+++ b/src/jabber.h
@@ -46,7 +46,10 @@ jabber_conn_status_t jabber_connect(const char * const user,
 void jabber_disconnect(void);
 void jabber_process_events(void);
 void jabber_subscribe(const char * const recipient);
+void jabber_join(const char * const room_jid, const char * const nick);
+void jabber_leave_chat_room(const char * const room_jid);
 void jabber_send(const char * const msg, const char * const recipient);
+void jabber_send_groupchat(const char * const msg, const char * const recipient);
 void jabber_send_inactive(const char * const recipient);
 void jabber_send_composing(const char * const recipient);
 void jabber_send_paused(const char * const recipient);
diff --git a/src/room_chat.c b/src/room_chat.c
new file mode 100644
index 00000000..a8125874
--- /dev/null
+++ b/src/room_chat.c
@@ -0,0 +1,108 @@
+/*
+ * room_chat.c
+ *
+ * Copyright (C) 2012 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+typedef struct _muc_room_t {
+    char *jid;
+    char *nick;
+} muc_room;
+
+GHashTable *rooms = NULL;
+
+static void _room_free(muc_room *room);
+
+void
+room_join(const char * const jid, const char * const nick)
+{
+    if (rooms == NULL) {
+        rooms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+            (GDestroyNotify)_room_free);
+    }
+
+    muc_room *new_room = malloc(sizeof(muc_room));
+    new_room->jid = strdup(jid);
+    new_room->nick = strdup(nick);
+
+    g_hash_table_insert(rooms, strdup(jid), new_room);
+}
+
+void
+room_leave(const char * const jid)
+{
+    g_hash_table_remove(rooms, jid);
+}
+
+gboolean
+room_jid_is_room_chat(const char * const jid)
+{
+    char **tokens = g_strsplit(jid, "/", 0);
+    char *jid_part = tokens[0];
+
+    if (rooms != NULL) {
+        muc_room *room = g_hash_table_lookup(rooms, jid_part);
+
+        if (room != NULL) {
+            return TRUE;
+        } else {
+            return FALSE;
+        }
+    } else {
+        return FALSE;
+    }
+}
+
+char *
+room_get_nick_for_room(const char * const jid)
+{
+    if (rooms != NULL) {
+        muc_room *room = g_hash_table_lookup(rooms, jid);
+
+        if (room != NULL) {
+            return room->nick;
+        } else {
+            return NULL;
+        }
+    } else {
+        return NULL;
+    }
+}
+
+static void
+_room_free(muc_room *room)
+{
+    if (room != NULL) {
+        if (room->jid != NULL) {
+            g_free(room->jid);
+            room->jid = NULL;
+        }
+        if (room->nick != NULL) {
+            g_free(room->nick);
+            room->nick = NULL;
+        }
+        g_free(room);
+    }
+    room = NULL;
+}
diff --git a/src/room_chat.h b/src/room_chat.h
new file mode 100644
index 00000000..c8be1f5b
--- /dev/null
+++ b/src/room_chat.h
@@ -0,0 +1,28 @@
+/*
+ * room_chat.h
+ *
+ * Copyright (C) 2012 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+void room_join(const char * const jid, const char * const nick);
+void room_leave(const char * const jid);
+gboolean room_jid_is_room_chat(const char * const jid);
+char * room_get_nick_for_room(const char * const jid);
diff --git a/src/ui.h b/src/ui.h
index 2c60428d..35c8e0b5 100644
--- a/src/ui.h
+++ b/src/ui.h
@@ -103,6 +103,15 @@ void win_show(const char * const msg);
 void win_bad_show(const char * const msg);
 void win_remind(void);
 
+void win_join_chat(const char * const room_jid, const char * const nick);
+void win_show_chat_room_member(const char * const room_jid,
+    const char * const nick);
+int win_in_groupchat(void);
+void win_show_room_history(const char * const room_jid, const char * const nick,
+    GTimeVal tv_stamp, const char * const message);
+void win_show_room_message(const char * const room_jid, const char * const nick,
+    const char * const message);
+
 // console window actions
 void cons_about(void);
 void cons_help(void);
diff --git a/src/windows.c b/src/windows.c
index cda57fa8..a6438779 100644
--- a/src/windows.c
+++ b/src/windows.c
@@ -43,6 +43,7 @@
 #include "log.h"
 #include "preferences.h"
 #include "release.h"
+#include "room_chat.h"
 #include "ui.h"
 
 #define CONS_WIN_TITLE "_cons"
@@ -93,7 +94,7 @@ static gboolean _new_release(char *found_version);
 static void _win_notify(const char * const message, int timeout,
     const char * const category);
 static void _win_notify_remind(gint unread);
-static void _win_notify_message(char * short_from);
+static void _win_notify_message(const char * const short_from);
 static void _win_notify_typing(char * short_from);
 #endif
 
@@ -196,6 +197,16 @@ win_in_chat(void)
         (strcmp(_wins[_curr_prof_win].from, "") != 0));
 }
 
+int
+win_in_groupchat(void)
+{
+    if (room_jid_is_room_chat(_wins[_curr_prof_win].from)) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
 char *
 win_get_recipient(void)
 {
@@ -407,7 +418,7 @@ _win_notify_remind(gint unread)
 }
 
 static void
-_win_notify_message(char * short_from)
+_win_notify_message(const char * const short_from)
 {
     char message[strlen(short_from) + 1 + 10];
     sprintf(message, "%s: message.", short_from);
@@ -471,6 +482,102 @@ win_show_outgoing_msg(const char * const from, const char * const to,
 }
 
 void
+win_join_chat(const char * const room_jid, const char * const nick)
+{
+    int win_index = _find_prof_win_index(room_jid);
+
+    // create new window
+    if (win_index == NUM_WINS) {
+        win_index = _new_prof_win(room_jid);
+    }
+
+    _win_switch_if_active(win_index);
+}
+
+void
+win_show_chat_room_member(const char * const room_jid, const char * const nick)
+{
+    int win_index = _find_prof_win_index(room_jid);
+    WINDOW *win = _wins[win_index].win;
+
+    wattron(win, COLOUR_ONLINE);
+    wprintw(win, "%s\n", nick);
+    wattroff(win, COLOUR_ONLINE);
+
+    if (win_index == _curr_prof_win)
+        dirty = TRUE;
+}
+
+void
+win_show_room_history(const char * const room_jid, const char * const nick,
+    GTimeVal tv_stamp, const char * const message)
+{
+    int win_index = _find_prof_win_index(room_jid);
+    WINDOW *win = _wins[win_index].win;
+
+    GDateTime *time = g_date_time_new_from_timeval_utc(&tv_stamp);
+    gchar *date_fmt = g_date_time_format(time, "%H:%M:%S");
+    wprintw(win, "%s - ", date_fmt);
+    g_date_time_unref(time);
+    g_free(date_fmt);
+
+    wprintw(win, "%s: ", nick);
+    _win_show_message(win, message);
+
+    if (win_index == _curr_prof_win)
+        dirty = TRUE;
+}
+
+void
+win_show_room_message(const char * const room_jid, const char * const nick,
+    const char * const message)
+{
+    int win_index = _find_prof_win_index(room_jid);
+    WINDOW *win = _wins[win_index].win;
+
+    _win_show_time(win);
+    if (strcmp(nick, room_get_nick_for_room(room_jid)) != 0) {
+        _win_show_user(win, nick, 1);
+    } else {
+        _win_show_user(win, nick, 0);
+    }
+    _win_show_message(win, message);
+
+    // currently in groupchat window
+    if (win_index == _curr_prof_win) {
+        status_bar_active(win_index);
+        dirty = TRUE;
+
+    // not currenlty on groupchat window
+    } else {
+        status_bar_new(win_index);
+        _cons_show_incoming_message(nick, win_index);
+        if (_curr_prof_win == 0) {
+            dirty = TRUE;
+        }
+
+        if (strcmp(nick, room_get_nick_for_room(room_jid)) != 0) {
+            if (prefs_get_flash()) {
+                flash();
+            }
+        }
+
+        _wins[win_index].unread++;
+    }
+
+    if (strcmp(nick, room_get_nick_for_room(room_jid)) != 0) {
+        if (prefs_get_beep()) {
+            beep();
+        }
+#ifdef HAVE_LIBNOTIFY
+        if (prefs_get_notify_message()) {
+            _win_notify_message(nick);
+        }
+#endif
+    }
+}
+
+void
 win_show(const char * const msg)
 {
     WINDOW *win = _wins[_curr_prof_win].win;