about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/chat_session.c63
-rw-r--r--src/chat_session.h24
-rw-r--r--src/command.c77
-rw-r--r--src/contact.c118
-rw-r--r--src/contact.h11
-rw-r--r--src/contact_list.c84
-rw-r--r--src/contact_list.h6
-rw-r--r--src/jabber.c106
-rw-r--r--src/jabber.h2
-rw-r--r--src/prof_autocomplete.c76
-rw-r--r--src/profanity.c54
-rw-r--r--src/profanity.h5
-rw-r--r--src/windows.c80
13 files changed, 405 insertions, 301 deletions
diff --git a/src/chat_session.c b/src/chat_session.c
index 6f1d624e..f352fea0 100644
--- a/src/chat_session.c
+++ b/src/chat_session.c
@@ -1,8 +1,8 @@
-/* 
+/*
  * chat_session.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
@@ -26,30 +26,37 @@
 #include <glib.h>
 
 #include "chat_session.h"
+#include "log.h"
 
-static ChatSession _chat_session_new(const char * const recipient);
+static ChatSession _chat_session_new(const char * const recipient,
+    gboolean recipient_supports);
 static void _chat_session_free(ChatSession session);
 
 struct chat_session_t {
     char *recipient;
-    chat_state_t state;
-    gboolean sent;
+    gboolean recipient_supports;
 };
 
 static GHashTable *sessions;
 
 void
-chat_session_init(void)
+chat_sessions_init(void)
 {
-    sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, 
+    sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
         (GDestroyNotify)_chat_session_free);
 }
 
 void
-chat_session_start(const char * const recipient)
+chat_sessions_clear(void)
+{
+    g_hash_table_remove_all(sessions);
+}
+
+void
+chat_session_start(const char * const recipient, gboolean recipient_supports)
 {
     char *key = strdup(recipient);
-    ChatSession session = _chat_session_new(key);
+    ChatSession session = _chat_session_new(key, recipient_supports);
     g_hash_table_insert(sessions, key, session);
 }
 
@@ -59,46 +66,26 @@ chat_session_end(const char * const recipient)
     g_hash_table_remove(sessions, recipient);
 }
 
-chat_state_t
-chat_session_get_state(const char * const recipient)
+gboolean
+chat_session_recipient_supports(const char * const recipient)
 {
     ChatSession session = g_hash_table_lookup(sessions, recipient);
+
     if (session == NULL) {
-        return SESSION_ERR;
+        log_error("No chat session found for %s.", recipient);
+        return FALSE;
     } else {
-        return session->state;
+        return session->recipient_supports;
     }
 }
 
-void
-chat_session_set_state(const char * const recipient, chat_state_t state)
-{
-    ChatSession session = g_hash_table_lookup(sessions, recipient);
-    session->state = state;
-}
-
-gboolean
-chat_session_get_sent(const char * const recipient)
-{
-    ChatSession session = g_hash_table_lookup(sessions, recipient);
-    return session->sent;
-}
-
-void
-chat_session_sent(const char * const recipient)
-{
-    ChatSession session = g_hash_table_lookup(sessions, recipient);
-    session->sent = TRUE;
-}
-
 static ChatSession
-_chat_session_new(const char * const recipient)
+_chat_session_new(const char * const recipient, gboolean recipient_supports)
 {
     ChatSession new_session = malloc(sizeof(struct chat_session_t));
     new_session->recipient = strdup(recipient);
-    new_session->state = ACTIVE;
-    new_session->sent = FALSE;
-    
+    new_session->recipient_supports = recipient_supports;
+
     return new_session;
 }
 
diff --git a/src/chat_session.h b/src/chat_session.h
index b17fb2ee..aecc47da 100644
--- a/src/chat_session.h
+++ b/src/chat_session.h
@@ -1,8 +1,8 @@
-/* 
+/*
  * chat_session.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
@@ -27,21 +27,11 @@
 
 typedef struct chat_session_t *ChatSession;
 
-typedef enum {
-    ACTIVE,
-    INACTIVE,
-    GONE,
-    COMPOSING,
-    PAUSED,
-    SESSION_ERR
-} chat_state_t;
-
-void chat_session_init(void);
-void chat_session_start(const char * const recipient);
+void chat_sessions_init(void);
+void chat_sessions_clear(void);
+void chat_session_start(const char * const recipient,
+    gboolean recipient_supports);
 void chat_session_end(const char * const recipient);
-chat_state_t chat_session_get_state(const char * const recipient);
-void chat_session_set_state(const char * const recipient, chat_state_t state);
-gboolean chat_session_get_sent(const char * const recipient);
-void chat_session_sent(const char * const recipient);
+gboolean chat_session_recipient_supports(const char * const recipient);
 
 #endif
diff --git a/src/command.c b/src/command.c
index e9f40409..f8c5b1e9 100644
--- a/src/command.c
+++ b/src/command.c
@@ -82,6 +82,7 @@ static gboolean _cmd_prefs(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_who(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_connect(const char * const inp, struct cmd_help_t help);
 static gboolean _cmd_disconnect(const char * const inp, struct cmd_help_t help);
+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);
@@ -178,6 +179,16 @@ static struct cmd_t main_commands[] =
           "Example : /msg boothj5@gmail.com Hey, here's a message!",
           NULL } } },
 
+    { "/sub",
+        _cmd_sub,
+        { "/sub user@host", "Subscribe to presence notifications of user.",
+        { "/sub user@host",
+          "------------------",
+          "Send a subscription request to the user to be informed of their presence.",
+          "",
+          "Example: /sub myfriend@jabber.org",
+          NULL  } } },
+
     { "/tiny",
         _cmd_tiny,
         { "/tiny url", "Send url as tinyurl in current chat.",
@@ -442,7 +453,7 @@ cmd_init(void)
         p_autocomplete_add(who_ac, (gchar *)strdup(pcmd->cmd+1));
     }
 
-    p_autocomplete_add(who_ac, "offline");
+    p_autocomplete_add(who_ac, strdup("offline"));
 
     history_init();
 }
@@ -692,6 +703,32 @@ _cmd_connect(const char * const inp, struct cmd_help_t help)
 }
 
 static gboolean
+_cmd_sub(const char * const inp, struct cmd_help_t help)
+{
+    gboolean result = FALSE;
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
+
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are currently not connected.");
+        result = TRUE;
+    } else if (strlen(inp) < 6) {
+        cons_show("Usage: %s", help.usage);
+        result = TRUE;
+    } else {
+        char *user, *lower;
+        user = strndup(inp+5, strlen(inp)-5);
+        lower = g_utf8_strdown(user, -1);
+
+        jabber_subscribe(lower);
+        cons_show("Sent subscription request to %s.", user);
+
+        result = TRUE;
+    }
+
+    return result;
+}
+
+static gboolean
 _cmd_disconnect(const char * const inp, struct cmd_help_t help)
 {
     if (jabber_get_connection_status() == JABBER_CONNECTED) {
@@ -788,16 +825,16 @@ _cmd_who(const char * const inp, struct cmd_help_t help)
 
         // get show
         strtok(inp_cpy, " ");
-        char *show = strtok(NULL, " ");
+        char *presence = strtok(NULL, " ");
 
         // bad arg
-        if ((show != NULL)
-                && (strcmp(show, "online") != 0)
-                && (strcmp(show, "offline") != 0)
-                && (strcmp(show, "away") != 0)
-                && (strcmp(show, "chat") != 0)
-                && (strcmp(show, "xa") != 0)
-                && (strcmp(show, "dnd") != 0)) {
+        if ((presence != NULL)
+                && (strcmp(presence, "online") != 0)
+                && (strcmp(presence, "offline") != 0)
+                && (strcmp(presence, "away") != 0)
+                && (strcmp(presence, "chat") != 0)
+                && (strcmp(presence, "xa") != 0)
+                && (strcmp(presence, "dnd") != 0)) {
             cons_show("Usage: %s", help.usage);
 
         // valid arg
@@ -805,23 +842,23 @@ _cmd_who(const char * const inp, struct cmd_help_t help)
             GSList *list = get_contact_list();
 
             // no arg, show all contacts
-            if (show == NULL) {
+            if (presence == NULL) {
                 cons_show("All contacts:");
                 cons_show_contacts(list);
 
             // online, show all status that indicate online
-            } else if (strcmp("online", show) == 0) {
-                cons_show("Contacts (%s):", show);
+            } else if (strcmp("online", presence) == 0) {
+                cons_show("Contacts (%s):", presence);
                 GSList *filtered = NULL;
 
                 while (list != NULL) {
                     PContact contact = list->data;
-                    const char * const contact_show = (p_contact_show(contact));
-                    if ((strcmp(contact_show, "online") == 0)
-                            || (strcmp(contact_show, "away") == 0)
-                            || (strcmp(contact_show, "dnd") == 0)
-                            || (strcmp(contact_show, "xa") == 0)
-                            || (strcmp(contact_show, "chat") == 0)) {
+                    const char * const contact_presence = (p_contact_presence(contact));
+                    if ((strcmp(contact_presence, "online") == 0)
+                            || (strcmp(contact_presence, "away") == 0)
+                            || (strcmp(contact_presence, "dnd") == 0)
+                            || (strcmp(contact_presence, "xa") == 0)
+                            || (strcmp(contact_presence, "chat") == 0)) {
                         filtered = g_slist_append(filtered, contact);
                     }
                     list = g_slist_next(list);
@@ -831,12 +868,12 @@ _cmd_who(const char * const inp, struct cmd_help_t help)
 
             // show specific status
             } else {
-                cons_show("Contacts (%s):", show);
+                cons_show("Contacts (%s):", presence);
                 GSList *filtered = NULL;
 
                 while (list != NULL) {
                     PContact contact = list->data;
-                    if (strcmp(p_contact_show(contact), show) == 0) {
+                    if (strcmp(p_contact_presence(contact), presence) == 0) {
                         filtered = g_slist_append(filtered, contact);
                     }
                     list = g_slist_next(list);
diff --git a/src/contact.c b/src/contact.c
index 12a2e500..3b0d1944 100644
--- a/src/contact.c
+++ b/src/contact.c
@@ -28,28 +28,42 @@
 #include "contact.h"
 
 struct p_contact_t {
+    char *jid;
     char *name;
-    char *show;
+    char *presence;
     char *status;
+    char *subscription;
 };
 
 PContact
-p_contact_new(const char * const name, const char * const show,
-    const char * const status)
+p_contact_new(const char * const jid, const char * const name,
+    const char * const presence, const char * const status,
+    const char * const subscription)
 {
     PContact contact = malloc(sizeof(struct p_contact_t));
-    contact->name = strdup(name);
+    contact->jid = strdup(jid);
 
-    if (show == NULL || (strcmp(show, "") == 0))
-        contact->show = strdup("online");
+    if (name != NULL) {
+        contact->name = strdup(name);
+    } else {
+        contact->name = NULL;
+    }
+
+    if (presence == NULL || (strcmp(presence, "") == 0))
+        contact->presence = strdup("online");
     else
-        contact->show = strdup(show);
+        contact->presence = strdup(presence);
 
     if (status != NULL)
         contact->status = strdup(status);
     else
         contact->status = NULL;
 
+    if (subscription != NULL)
+        contact->subscription = strdup(subscription);
+    else
+        contact->subscription = NULL;
+
     return contact;
 }
 
@@ -57,45 +71,77 @@ PContact
 p_contact_copy(PContact contact)
 {
     PContact copy = malloc(sizeof(struct p_contact_t));
-    copy->name = strdup(contact->name);
-    copy->show = strdup(contact->show);
+    copy->jid = strdup(contact->jid);
+
+    if (contact->name != NULL) {
+        copy->name = strdup(contact->name);
+    } else {
+        copy->name = NULL;
+    }
+
+    copy->presence = strdup(contact->presence);
 
     if (contact->status != NULL)
         copy->status = strdup(contact->status);
     else
         copy->status = NULL;
 
+    if (contact->subscription != NULL)
+        copy->subscription = strdup(contact->subscription);
+    else
+        copy->subscription = NULL;
+
     return copy;
 }
 
 void
 p_contact_free(PContact contact)
 {
-    free(contact->name);
+    if (contact->jid != NULL) {
+        free(contact->jid);
+        contact->jid = NULL;
+    }
 
-    if (contact->show != NULL) {
-        free(contact->show);
-        contact->show = NULL;
+    if (contact->name != NULL) {
+        free(contact->name);
+        contact->name = NULL;
     }
+
+    if (contact->presence != NULL) {
+        free(contact->presence);
+        contact->presence = NULL;
+    }
+
     if (contact->status != NULL) {
         free(contact->status);
         contact->status = NULL;
     }
 
+    if (contact->subscription != NULL) {
+        free(contact->subscription);
+        contact->subscription = NULL;
+    }
+
     free(contact);
     contact = NULL;
 }
 
 const char *
+p_contact_jid(const PContact contact)
+{
+    return contact->jid;
+}
+
+const char *
 p_contact_name(const PContact contact)
 {
     return contact->name;
 }
 
 const char *
-p_contact_show(const PContact contact)
+p_contact_presence(const PContact contact)
 {
-    return contact->show;
+    return contact->presence;
 }
 
 const char *
@@ -104,12 +150,50 @@ p_contact_status(const PContact contact)
     return contact->status;
 }
 
+const char *
+p_contact_subscription(const PContact contact)
+{
+    return contact->subscription;
+}
+
+void
+p_contact_set_presence(const PContact contact, const char * const presence)
+{
+    if (contact->presence != NULL) {
+        free(contact->presence);
+        contact->presence = NULL;
+    }
+
+    if (presence == NULL) {
+        contact->presence = NULL;
+    } else {
+        contact->presence = strdup(presence);
+    }
+}
+
+void
+p_contact_set_status(const PContact contact, const char * const status)
+{
+    if (contact->status != NULL) {
+        free(contact->status);
+        contact->status = NULL;
+    }
+
+    if (status == NULL) {
+        contact->status = NULL;
+    } else {
+        contact->status = strdup(status);
+    }
+}
+
 int
 p_contacts_equal_deep(const PContact c1, const PContact c2)
 {
+    int jid_eq = (g_strcmp0(c1->jid, c2->jid) == 0);
     int name_eq = (g_strcmp0(c1->name, c2->name) == 0);
-    int show_eq = (g_strcmp0(c1->show, c2->show) == 0);
+    int presence_eq = (g_strcmp0(c1->presence, c2->presence) == 0);
     int status_eq = (g_strcmp0(c1->status, c2->status) == 0);
+    int subscription_eq = (g_strcmp0(c1->subscription, c2->subscription) == 0);
 
-    return (name_eq && show_eq && status_eq);
+    return (jid_eq && name_eq && presence_eq && status_eq && subscription_eq);
 }
diff --git a/src/contact.h b/src/contact.h
index 8f778ca9..a4f487e8 100644
--- a/src/contact.h
+++ b/src/contact.h
@@ -25,13 +25,18 @@
 
 typedef struct p_contact_t *PContact;
 
-PContact p_contact_new(const char * const name, const char * const show,
-    const char * const status);
+PContact p_contact_new(const char * const jid, const char * const name,
+    const char * const presence, const char * const status,
+    const char * const subscription);
 PContact p_contact_copy(PContact contact);
 void p_contact_free(PContact contact);
+const char * p_contact_jid(PContact contact);
 const char * p_contact_name(PContact contact);
-const char * p_contact_show(PContact contact);
+const char * p_contact_presence(PContact contact);
 const char * p_contact_status(PContact contact);
+const char * p_contact_subscription(const PContact contact);
+void p_contact_set_presence(const PContact contact, const char * const presence);
+void p_contact_set_status(const PContact contact, const char * const status);
 int p_contacts_equal_deep(const PContact c1, const PContact c2);
 
 #endif
diff --git a/src/contact_list.c b/src/contact_list.c
index e266e25e..d05d7412 100644
--- a/src/contact_list.c
+++ b/src/contact_list.c
@@ -22,24 +22,30 @@
 
 #include <string.h>
 
+#include <glib.h>
+
 #include "contact.h"
+#include "log.h"
 #include "prof_autocomplete.h"
 
 static PAutocomplete ac;
+static GHashTable *contacts;
+
+static gboolean _key_equals(void *key1, void *key2);
 
 void
 contact_list_init(void)
 {
-    ac = p_obj_autocomplete_new((PStrFunc)p_contact_name,
-                            (PCopyFunc)p_contact_copy,
-                            (PEqualDeepFunc)p_contacts_equal_deep,
-                            (GDestroyNotify)p_contact_free);
+    ac = p_autocomplete_new();
+    contacts = g_hash_table_new_full(g_str_hash, (GEqualFunc)_key_equals, g_free,
+        (GDestroyNotify)p_contact_free);
 }
 
 void
 contact_list_clear(void)
 {
     p_autocomplete_clear(ac);
+    g_hash_table_remove_all(contacts);
 }
 
 void
@@ -49,22 +55,63 @@ contact_list_reset_search_attempts(void)
 }
 
 gboolean
-contact_list_remove(const char * const name)
+contact_list_add(const char * const jid, const char * const name,
+    const char * const presence, const char * const status,
+    const char * const subscription)
 {
-    return p_autocomplete_remove(ac, name);
+    gboolean added = FALSE;
+    PContact contact = g_hash_table_lookup(contacts, jid);
+
+    if (contact == NULL) {
+        contact = p_contact_new(jid, name, presence, status, subscription);
+        g_hash_table_insert(contacts, strdup(jid), contact);
+        p_autocomplete_add(ac, strdup(jid));
+        added = TRUE;
+    }
+
+    return added;
 }
 
 gboolean
-contact_list_add(const char * const name, const char * const show,
+contact_list_update_contact(const char * const jid, const char * const presence,
     const char * const status)
 {
-    return p_autocomplete_add(ac, p_contact_new(name, show, status));
+    gboolean changed = FALSE;
+    PContact contact = g_hash_table_lookup(contacts, jid);
+
+    if (contact == NULL) {
+        log_warning("Contact not in list: %s", jid);
+        return FALSE;
+    }
+
+    if (g_strcmp0(p_contact_presence(contact), presence) != 0) {
+        p_contact_set_presence(contact, presence);
+        changed = TRUE;
+    }
+
+    if (g_strcmp0(p_contact_status(contact), status) != 0) {
+        p_contact_set_status(contact, status);
+        changed = TRUE;
+    }
+
+    return changed;
 }
 
 GSList *
 get_contact_list(void)
 {
-    return p_autocomplete_get_list(ac);
+    GSList *result = NULL;
+    GHashTableIter iter;
+    gpointer key;
+    gpointer value;
+
+    g_hash_table_iter_init(&iter, contacts);
+    while (g_hash_table_iter_next(&iter, &key, &value)) {
+        result = g_slist_append(result, value);
+    }
+
+    // resturn all contact structs
+    return result;
 }
 
 char *
@@ -76,15 +123,14 @@ contact_list_find_contact(char *search_str)
 PContact
 contact_list_get_contact(const char const *jid)
 {
-    GSList *contacts = get_contact_list();
-
-    while (contacts != NULL) {
-        PContact contact = contacts->data;
-        if (strcmp(p_contact_name(contact), jid) == 0) {
-            return contact;
-        }
-        contacts = g_slist_next(contacts);
-    }
+    return g_hash_table_lookup(contacts, jid);
+}
+
+static
+gboolean _key_equals(void *key1, void *key2)
+{
+    gchar *str1 = (gchar *) key1;
+    gchar *str2 = (gchar *) key2;
 
-    return NULL;
+    return (g_strcmp0(str1, str2) == 0);
 }
diff --git a/src/contact_list.h b/src/contact_list.h
index bc239111..90ae947b 100644
--- a/src/contact_list.h
+++ b/src/contact_list.h
@@ -30,9 +30,11 @@
 void contact_list_init(void);
 void contact_list_clear(void);
 void contact_list_reset_search_attempts(void);
-gboolean contact_list_add(const char * const name, const char * const show,
+gboolean contact_list_add(const char * const jid, const char * const name,
+    const char * const presence, const char * const status,
+    const char * const subscription);
+gboolean contact_list_update_contact(const char * const jid, const char * const presence,
     const char * const status);
-gboolean contact_list_remove(const char * const name);
 GSList * get_contact_list(void);
 char * contact_list_find_contact(char *search_str);
 PContact contact_list_get_contact(const char const *jid);
diff --git a/src/jabber.c b/src/jabber.c
index 4913618e..472aa015 100644
--- a/src/jabber.c
+++ b/src/jabber.c
@@ -25,7 +25,9 @@
 
 #include <strophe.h>
 
+#include "chat_session.h"
 #include "common.h"
+#include "contact_list.h"
 #include "jabber.h"
 #include "log.h"
 #include "preferences.h"
@@ -49,6 +51,8 @@ static void _xmpp_file_logger(void * const userdata,
     const char * const msg);
 static xmpp_log_t * _xmpp_get_file_logger();
 
+static void _jabber_roster_request(void);
+
 // XMPP event handlers
 static void _connection_handler(xmpp_conn_t * const conn,
     const xmpp_conn_event_t status, const int error,
@@ -165,23 +169,16 @@ jabber_send(const char * const msg, const char * const recipient)
 }
 
 void
-jabber_roster_request(void)
+jabber_subscribe(const char * const recipient)
 {
-    xmpp_stanza_t *iq, *query;
-
-    iq = xmpp_stanza_new(jabber_conn.ctx);
-    xmpp_stanza_set_name(iq, "iq");
-    xmpp_stanza_set_type(iq, "get");
-    xmpp_stanza_set_id(iq, "roster");
-
-    query = xmpp_stanza_new(jabber_conn.ctx);
-    xmpp_stanza_set_name(query, "query");
-    xmpp_stanza_set_ns(query, XMPP_NS_ROSTER);
-
-    xmpp_stanza_add_child(iq, query);
-    xmpp_stanza_release(query);
-    xmpp_send(jabber_conn.conn, iq);
-    xmpp_stanza_release(iq);
+    xmpp_stanza_t *presence;
+
+    presence = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(presence, "presence");
+    xmpp_stanza_set_type(presence, "subscribe");
+    xmpp_stanza_set_attribute(presence, "to", recipient);
+    xmpp_send(jabber_conn.conn, presence);
+    xmpp_stanza_release(presence);
 }
 
 void
@@ -248,11 +245,32 @@ jabber_get_jid(void)
 void
 jabber_free_resources(void)
 {
+    chat_sessions_clear();
     xmpp_conn_release(jabber_conn.conn);
     xmpp_ctx_free(jabber_conn.ctx);
     xmpp_shutdown();
 }
 
+static void
+_jabber_roster_request(void)
+{
+    xmpp_stanza_t *iq, *query;
+
+    iq = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(iq, "iq");
+    xmpp_stanza_set_type(iq, "get");
+    xmpp_stanza_set_id(iq, "roster");
+
+    query = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(query, "query");
+    xmpp_stanza_set_ns(query, XMPP_NS_ROSTER);
+
+    xmpp_stanza_add_child(iq, query);
+    xmpp_stanza_release(query);
+    xmpp_send(jabber_conn.conn, iq);
+    xmpp_stanza_release(iq);
+}
+
 static int
 _message_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
@@ -319,21 +337,16 @@ _connection_handler(xmpp_conn_t * const conn,
     if (status == XMPP_CONN_CONNECT) {
         const char *jid = xmpp_conn_get_jid(conn);
         prof_handle_login_success(jid);
+        chat_sessions_init();
 
-        xmpp_stanza_t* pres;
         xmpp_handler_add(conn, _message_handler, NULL, "message", NULL, ctx);
         xmpp_handler_add(conn, _presence_handler, NULL, "presence", NULL, ctx);
         xmpp_id_handler_add(conn, _roster_handler, "roster", ctx);
         xmpp_timed_handler_add(conn, _ping_timed_handler, PING_INTERVAL, ctx);
 
-        pres = xmpp_stanza_new(ctx);
-        xmpp_stanza_set_name(pres, "presence");
-        xmpp_send(conn, pres);
-        xmpp_stanza_release(pres);
-
+        _jabber_roster_request();
         jabber_conn.conn_status = JABBER_CONNECTED;
         jabber_conn.presence = PRESENCE_ONLINE;
-        jabber_roster_request();
     } else {
 
         // received close stream response from server after disconnect
@@ -362,6 +375,7 @@ static int
 _roster_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
 {
+    xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
     xmpp_stanza_t *query, *item;
     char *type = xmpp_stanza_get_type(stanza);
 
@@ -369,27 +383,25 @@ _roster_handler(xmpp_conn_t * const conn,
         log_error("Roster query failed");
     else {
         query = xmpp_stanza_get_child_by_name(stanza, "query");
-        GSList *roster = NULL;
         item = xmpp_stanza_get_children(query);
 
         while (item != NULL) {
-            const char *name = xmpp_stanza_get_attribute(item, "name");
             const char *jid = xmpp_stanza_get_attribute(item, "jid");
+            const char *name = xmpp_stanza_get_attribute(item, "name");
+            const char *sub = xmpp_stanza_get_attribute(item, "subscription");
+            gboolean added = contact_list_add(jid, name, "offline", NULL, sub);
 
-            jabber_roster_entry *entry = malloc(sizeof(jabber_roster_entry));
-
-            if (name != NULL) {
-                entry->name = strdup(name);
-            } else {
-                entry->name = NULL;
+            if (!added) {
+                log_warning("Attempt to add contact twice: %s", jid);
             }
-            entry->jid = strdup(jid);
 
-            roster = g_slist_append(roster, entry);
             item = xmpp_stanza_get_next(item);
         }
-
-        prof_handle_roster(roster);
+        xmpp_stanza_t* pres;
+        pres = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(pres, "presence");
+        xmpp_send(conn, pres);
+        xmpp_stanza_release(pres);
     }
 
     return 1;
@@ -434,26 +446,28 @@ _presence_handler(xmpp_conn_t * const conn,
     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 *show = xmpp_stanza_get_child_by_name(stanza, "show");
-    if (show != NULL)
-        show_str = xmpp_stanza_get_text(show);
-    else
-        show_str = NULL;
-
     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 (strcmp(short_jid, short_from) !=0) {
-        if (type == NULL) {
+    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);
-        } else {
-            prof_handle_contact_offline(short_from, show_str, status_str);
         }
     }
 
diff --git a/src/jabber.h b/src/jabber.h
index d12d00b3..726fb1a5 100644
--- a/src/jabber.h
+++ b/src/jabber.h
@@ -44,8 +44,8 @@ void jabber_init(const int disable_tls);
 jabber_conn_status_t jabber_connect(const char * const user,
     const char * const passwd);
 void jabber_disconnect(void);
-void jabber_roster_request(void);
 void jabber_process_events(void);
+void jabber_subscribe(const char * const recipient);
 void jabber_send(const char * const msg, const char * const recipient);
 void jabber_update_presence(jabber_presence_t status, const char * const msg);
 const char * jabber_get_jid(void);
diff --git a/src/prof_autocomplete.c b/src/prof_autocomplete.c
index 79d44498..b41c5368 100644
--- a/src/prof_autocomplete.c
+++ b/src/prof_autocomplete.c
@@ -29,59 +29,25 @@ struct p_autocomplete_t {
     GSList *items;
     GSList *last_found;
     gchar *search_str;
-    PStrFunc str_func;
-    PCopyFunc copy_func;
-    PEqualDeepFunc equal_deep_func;
-    GDestroyNotify free_func;
 };
 
 static gchar * _search_from(PAutocomplete ac, GSList *curr);
-static const char *_str_func_default(const char *orig);
-static const char *_copy_func_default(const char *orig);
-static int _deep_equals_func_default(const char *o1, const char *o2);
 
 PAutocomplete
 p_autocomplete_new(void)
 {
-    return p_obj_autocomplete_new(NULL, NULL, NULL, NULL);
-}
-
-PAutocomplete
-p_obj_autocomplete_new(PStrFunc str_func, PCopyFunc copy_func,
-    PEqualDeepFunc equal_deep_func, GDestroyNotify free_func)
-{
     PAutocomplete new = malloc(sizeof(struct p_autocomplete_t));
     new->items = NULL;
     new->last_found = NULL;
     new->search_str = NULL;
 
-    if (str_func)
-        new->str_func = str_func;
-    else
-        new->str_func = (PStrFunc)_str_func_default;
-
-    if (copy_func)
-        new->copy_func = copy_func;
-    else
-        new->copy_func = (PCopyFunc)_copy_func_default;
-
-    if (free_func)
-        new->free_func = free_func;
-    else
-        new->free_func = (GDestroyNotify)free;
-
-    if (equal_deep_func)
-        new->equal_deep_func = equal_deep_func;
-    else
-        new->equal_deep_func = (PEqualDeepFunc)_deep_equals_func_default;
-
     return new;
 }
 
 void
 p_autocomplete_clear(PAutocomplete ac)
 {
-    g_slist_free_full(ac->items, ac->free_func);
+    g_slist_free_full(ac->items, free);
     ac->items = NULL;
 
     p_autocomplete_reset(ac);
@@ -109,16 +75,16 @@ p_autocomplete_add(PAutocomplete ac, void *item)
         while(curr) {
 
             // insert
-            if (g_strcmp0(ac->str_func(curr->data), ac->str_func(item)) > 0) {
+            if (g_strcmp0(curr->data, item) > 0) {
                 ac->items = g_slist_insert_before(ac->items,
                     curr, item);
                 return TRUE;
 
             // update
-            } else if (g_strcmp0(ac->str_func(curr->data), ac->str_func(item)) == 0) {
+            } else if (g_strcmp0(curr->data, item) == 0) {
                 // only update if data different
-                if (!ac->equal_deep_func(curr->data, item)) {
-                    ac->free_func(curr->data);
+                if (strcmp(curr->data, item) != 0) {
+                    free(curr->data);
                     curr->data = item;
                     return TRUE;
                 } else {
@@ -141,7 +107,7 @@ p_autocomplete_remove(PAutocomplete ac, const char * const item)
 {
     // reset last found if it points to the item to be removed
     if (ac->last_found != NULL)
-        if (g_strcmp0(ac->str_func(ac->last_found->data), item) == 0)
+        if (g_strcmp0(ac->last_found->data, item) == 0)
             ac->last_found = NULL;
 
     if (!ac->items) {
@@ -150,10 +116,10 @@ p_autocomplete_remove(PAutocomplete ac, const char * const item)
         GSList *curr = ac->items;
 
         while(curr) {
-            if (g_strcmp0(ac->str_func(curr->data), item) == 0) {
+            if (g_strcmp0(curr->data, item) == 0) {
                 void *current_item = curr->data;
                 ac->items = g_slist_remove(ac->items, curr->data);
-                ac->free_func(current_item);
+                free(current_item);
 
                 return TRUE;
             }
@@ -172,7 +138,7 @@ p_autocomplete_get_list(PAutocomplete ac)
     GSList *curr = ac->items;
 
     while(curr) {
-        copy = g_slist_append(copy, ac->copy_func(curr->data));
+        copy = g_slist_append(copy, strdup(curr->data));
         curr = g_slist_next(curr);
     }
 
@@ -221,17 +187,17 @@ _search_from(PAutocomplete ac, GSList *curr)
     while(curr) {
 
         // match found
-        if (strncmp(ac->str_func(curr->data),
+        if (strncmp(curr->data,
                 ac->search_str,
                 strlen(ac->search_str)) == 0) {
             gchar *result =
-                (gchar *) malloc((strlen(ac->str_func(curr->data)) + 1) * sizeof(gchar));
+                (gchar *) malloc((strlen(curr->data) + 1) * sizeof(gchar));
 
             // set pointer to last found
             ac->last_found = curr;
 
             // return the string, must be free'd by caller
-            strcpy(result, ac->str_func(curr->data));
+            strcpy(result, curr->data);
             return result;
         }
 
@@ -240,21 +206,3 @@ _search_from(PAutocomplete ac, GSList *curr)
 
     return NULL;
 }
-
-static const char *
-_str_func_default(const char *orig)
-{
-    return orig;
-}
-
-static const char *
-_copy_func_default(const char *orig)
-{
-    return strdup(orig);
-}
-
-static int
-_deep_equals_func_default(const char *o1, const char *o2)
-{
-    return (strcmp(o1, o2) == 0);
-}
diff --git a/src/profanity.c b/src/profanity.c
index 4dd618cb..885c55ea 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -41,7 +41,6 @@
 static log_level_t _get_log_level(char *log_level);
 static gboolean _process_input(char *inp);
 static void _create_config_directory();
-static void _free_roster_entry(jabber_roster_entry *entry);
 static void _init(const int disable_tls, char *log_level);
 static void _shutdown(void);
 
@@ -168,38 +167,33 @@ prof_handle_failed_login(void)
 void
 prof_handle_contact_online(char *contact, char *show, char *status)
 {
-    gboolean result = contact_list_add(contact, show, status);
-    if (result) {
-        win_contact_online(contact, show, status);
+    gboolean updated = contact_list_update_contact(contact, show, status);
+
+    if (updated) {
+        PContact result = contact_list_get_contact(contact);
+        if (p_contact_subscription(result) != NULL) {
+            if (strcmp(p_contact_subscription(result), "none") != 0) {
+                win_contact_online(contact, show, status);
+                win_page_off();
+            }
+        }
     }
-    win_page_off();
 }
 
 void
 prof_handle_contact_offline(char *contact, char *show, char *status)
 {
-    gboolean result = contact_list_add(contact, "offline", status);
-    if (result) {
-        win_contact_offline(contact, show, status);
-    }
-    win_page_off();
-}
-
-void
-prof_handle_roster(GSList *roster)
-{
-    while (roster != NULL) {
-        jabber_roster_entry *entry = roster->data;
-
-        // if contact not in contact list add them as offline
-        if (contact_list_find_contact(entry->jid) == NULL) {
-            contact_list_add(entry->jid, "offline", NULL);
+    gboolean updated = contact_list_update_contact(contact, "offline", status);
+
+    if (updated) {
+        PContact result = contact_list_get_contact(contact);
+        if (p_contact_subscription(result) != NULL) {
+            if (strcmp(p_contact_subscription(result), "none") != 0) {
+                win_contact_offline(contact, show, status);
+                win_page_off();
+            }
         }
-
-        roster = g_slist_next(roster);
     }
-
-    g_slist_free_full(roster, (GDestroyNotify)_free_roster_entry);
 }
 
 static void
@@ -211,16 +205,6 @@ _create_config_directory(void)
     g_string_free(dir, TRUE);
 }
 
-static void
-_free_roster_entry(jabber_roster_entry *entry)
-{
-    if (entry->name != NULL) {
-        free(entry->name);
-        entry->name = NULL;
-    }
-    free(entry->jid);
-}
-
 static log_level_t
 _get_log_level(char *log_level)
 {
diff --git a/src/profanity.h b/src/profanity.h
index 430148e2..a67fb2df 100644
--- a/src/profanity.h
+++ b/src/profanity.h
@@ -23,11 +23,6 @@
 #ifndef PROFANITY_H
 #define PROFANITY_H
 
-typedef struct roster_entry_t {
-    char *name;
-    char *jid;
-} jabber_roster_entry;
-
 void prof_run(const int disable_tls, char *log_level);
 
 void prof_handle_login_success(const char *jid);
diff --git a/src/windows.c b/src/windows.c
index f1c8f0fe..c7e49020 100644
--- a/src/windows.c
+++ b/src/windows.c
@@ -427,8 +427,8 @@ win_show_outgoing_msg(const char * const from, const char * const to,
         }
 
         if (contact != NULL) {
-            if (strcmp(p_contact_show(contact), "offline") == 0) {
-                const char const *show = p_contact_show(contact);
+            if (strcmp(p_contact_presence(contact), "offline") == 0) {
+                const char const *show = p_contact_presence(contact);
                 const char const *status = p_contact_status(contact);
                 _show_status_string(win, to, show, status, "--", "offline");
             }
@@ -703,44 +703,56 @@ cons_show_contacts(GSList *list)
 
     while(curr) {
         PContact contact = curr->data;
-        const char *show = p_contact_show(contact);
+        const char *jid = p_contact_jid(contact);
+        const char *name = p_contact_name(contact);
+        const char *presence = p_contact_presence(contact);
+        const char *status = p_contact_status(contact);
+        const char *sub = p_contact_subscription(contact);
 
-        _win_show_time(_cons_win);
+        if (strcmp(sub, "none") != 0) {
+            _win_show_time(_cons_win);
 
-        if (strcmp(show, "online") == 0) {
-            wattron(_cons_win, COLOUR_ONLINE);
-        } else if (strcmp(show, "away") == 0) {
-            wattron(_cons_win, COLOUR_AWAY);
-        } else if (strcmp(show, "chat") == 0) {
-            wattron(_cons_win, COLOUR_CHAT);
-        } else if (strcmp(show, "dnd") == 0) {
-            wattron(_cons_win, COLOUR_DND);
-        } else if (strcmp(show, "xa") == 0) {
-            wattron(_cons_win, COLOUR_XA);
-        } else {
-            wattron(_cons_win, COLOUR_OFFLINE);
-        }
+            if (strcmp(presence, "online") == 0) {
+                wattron(_cons_win, COLOUR_ONLINE);
+            } else if (strcmp(presence, "away") == 0) {
+                wattron(_cons_win, COLOUR_AWAY);
+            } else if (strcmp(presence, "chat") == 0) {
+                wattron(_cons_win, COLOUR_CHAT);
+            } else if (strcmp(presence, "dnd") == 0) {
+                wattron(_cons_win, COLOUR_DND);
+            } else if (strcmp(presence, "xa") == 0) {
+                wattron(_cons_win, COLOUR_XA);
+            } else {
+                wattron(_cons_win, COLOUR_OFFLINE);
+            }
+
+            wprintw(_cons_win, "%s", jid);
 
-        wprintw(_cons_win, "%s", p_contact_name(contact));
-        wprintw(_cons_win, " is %s", show);
+            if (name != NULL) {
+                wprintw(_cons_win, " (%s)", name);
+            }
 
-        if (p_contact_status(contact))
-            wprintw(_cons_win, ", \"%s\"", p_contact_status(contact));
+            wprintw(_cons_win, " is %s", presence);
 
-        wprintw(_cons_win, "\n");
+            if (status != NULL) {
+                wprintw(_cons_win, ", \"%s\"", p_contact_status(contact));
+            }
 
-        if (strcmp(show, "online") == 0) {
-            wattroff(_cons_win, COLOUR_ONLINE);
-        } else if (strcmp(show, "away") == 0) {
-            wattroff(_cons_win, COLOUR_AWAY);
-        } else if (strcmp(show, "chat") == 0) {
-            wattroff(_cons_win, COLOUR_CHAT);
-        } else if (strcmp(show, "dnd") == 0) {
-            wattroff(_cons_win, COLOUR_DND);
-        } else if (strcmp(show, "xa") == 0) {
-            wattroff(_cons_win, COLOUR_XA);
-        } else {
-            wattroff(_cons_win, COLOUR_OFFLINE);
+            wprintw(_cons_win, "\n");
+
+            if (strcmp(presence, "online") == 0) {
+                wattroff(_cons_win, COLOUR_ONLINE);
+            } else if (strcmp(presence, "away") == 0) {
+                wattroff(_cons_win, COLOUR_AWAY);
+            } else if (strcmp(presence, "chat") == 0) {
+                wattroff(_cons_win, COLOUR_CHAT);
+            } else if (strcmp(presence, "dnd") == 0) {
+                wattroff(_cons_win, COLOUR_DND);
+            } else if (strcmp(presence, "xa") == 0) {
+                wattroff(_cons_win, COLOUR_XA);
+            } else {
+                wattroff(_cons_win, COLOUR_OFFLINE);
+            }
         }
 
         curr = g_slist_next(curr);