about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG4
-rw-r--r--Makefile.am4
-rw-r--r--docs/profanity.12
-rw-r--r--src/chat_session.c186
-rw-r--r--src/chat_session.h26
-rw-r--r--src/chat_state.c172
-rw-r--r--src/chat_state.h61
-rw-r--r--src/command/command.c127
-rw-r--r--src/command/commands.c125
-rw-r--r--src/command/commands.h2
-rw-r--r--src/config/preferences.c39
-rw-r--r--src/config/preferences.h10
-rw-r--r--src/otr/otr.c3
-rw-r--r--src/profanity.c16
-rw-r--r--src/server_events.c89
-rw-r--r--src/server_events.h9
-rw-r--r--src/ui/console.c32
-rw-r--r--src/ui/core.c193
-rw-r--r--src/ui/inputwin.c20
-rw-r--r--src/ui/inputwin.h4
-rw-r--r--src/ui/titlebar.c21
-rw-r--r--src/ui/ui.h12
-rw-r--r--src/ui/window.c10
-rw-r--r--src/ui/window.h4
-rw-r--r--src/xmpp/message.c96
-rw-r--r--src/xmpp/stanza.c5
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h11
-rw-r--r--tests/helpers.c13
-rw-r--r--tests/helpers.h3
-rw-r--r--tests/test_chat_session.c51
-rw-r--r--tests/test_chat_session.h4
-rw-r--r--tests/test_cmd_disconnect.c37
-rw-r--r--tests/test_cmd_disconnect.h1
-rw-r--r--tests/test_cmd_otr.c2
-rw-r--r--tests/test_server_events.c52
-rw-r--r--tests/test_server_events.h4
-rw-r--r--tests/testsuite.c19
-rw-r--r--tests/ui/stub_ui.c28
-rw-r--r--tests/ui/stub_ui.h2
-rw-r--r--tests/xmpp/stub_xmpp.c9
41 files changed, 1064 insertions, 446 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 645b1d79..4148ce86 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,3 +9,7 @@
 - /account remove
 - Added default account for /connect
 - Additional readline style shortcuts
+- Improved chat session handling
+- Override resource during chat and resource display settings (/resource)
+- Disable terminal title by default, additonal title on exit
+- Dynamic input blocking timeout to use less CPU
diff --git a/Makefile.am b/Makefile.am
index 520e4c19..9d44c003 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,7 @@ core_sources = \
 	src/log.h src/profanity.c src/common.h \
 	src/profanity.h src/chat_session.c \
 	src/chat_session.h src/muc.c src/muc.h src/jid.h src/jid.c \
+	src/chat_state.h src/chat_state.c \
 	src/resource.c src/resource.h \
 	src/roster_list.c src/roster_list.h \
 	src/xmpp/xmpp.h src/xmpp/capabilities.c src/xmpp/connection.c \
@@ -39,6 +40,7 @@ tests_sources = \
 	src/profanity.h src/chat_session.c \
 	src/chat_session.h src/muc.c src/muc.h src/jid.h src/jid.c \
 	src/resource.c src/resource.h \
+	src/chat_state.h src/chat_state.c \
 	src/roster_list.c src/roster_list.h \
 	src/xmpp/xmpp.h src/xmpp/form.c \
 	src/ui/ui.h \
@@ -77,6 +79,7 @@ tests_sources = \
 	tests/test_cmd_statuses.c tests/test_cmd_statuses.h \
 	tests/test_cmd_sub.c tests/test_cmd_sub.h \
 	tests/test_cmd_win.c tests/test_cmd_win.h \
+	tests/test_cmd_disconnect.c tests/test_cmd_disconnect.h \
 	tests/test_common.c tests/test_common.h \
 	tests/test_contact.c tests/test_contact.h \
 	tests/test_form.c tests/test_form.h \
@@ -88,6 +91,7 @@ tests_sources = \
 	tests/test_roster_list.c tests/test_roster_list.h \
 	tests/test_server_events.c tests/test_server_events.h \
 	tests/test_autocomplete.c tests/test_autocomplete.h \
+	tests/test_chat_session.c tests/test_chat_session.h \
 	tests/testsuite.c
 
 main_source = src/main.c
diff --git a/docs/profanity.1 b/docs/profanity.1
index 9cdf2d31..a65281fb 100644
--- a/docs/profanity.1
+++ b/docs/profanity.1
@@ -6,7 +6,7 @@ Profanity \- a simple console based XMPP chat client.
 [\-vhd] [\-l level]
 .SH DESCRIPTION
 .B Profanity
-is a simple lightweight console based XMPP chat client.  It's emphasis is 
+is a simple lightweight console based XMPP chat client. Its emphasis is 
 on having a simple and configurable command driven UI, see the homepage
 at:
 .br
diff --git a/src/chat_session.c b/src/chat_session.c
index d27a3449..fbe06f76 100644
--- a/src/chat_session.c
+++ b/src/chat_session.c
@@ -34,6 +34,7 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <assert.h>
 
 #include <glib.h>
 
@@ -42,41 +43,22 @@
 #include "log.h"
 #include "xmpp/xmpp.h"
 
-#define PAUSED_TIMOUT 10.0
-#define INACTIVE_TIMOUT 30.0
-
-typedef enum {
-    CHAT_STATE_STARTED,
-    CHAT_STATE_ACTIVE,
-    CHAT_STATE_PAUSED,
-    CHAT_STATE_COMPOSING,
-    CHAT_STATE_INACTIVE,
-    CHAT_STATE_GONE
-} chat_state_t;
-
-typedef struct chat_session_t {
-    char *barejid;
-    char *resource;
-    gboolean supported;
-    chat_state_t state;
-    GTimer *active_timer;
-    gboolean sent;
-} ChatSession;
-
 static GHashTable *sessions;
 
-static ChatSession*
-_chat_session_new(const char * const barejid, gboolean supported)
+static void
+_chat_session_new(const char * const barejid, const char * const resource,
+    gboolean resource_override, gboolean send_states)
 {
+    assert(barejid != NULL);
+    assert(resource != NULL);
+
     ChatSession *new_session = malloc(sizeof(struct chat_session_t));
     new_session->barejid = strdup(barejid);
-    new_session->resource = NULL;
-    new_session->supported = supported;
-    new_session->state = CHAT_STATE_STARTED;
-    new_session->active_timer = g_timer_new();
-    new_session->sent = FALSE;
+    new_session->resource = strdup(resource);
+    new_session->resource_override = resource_override;
+    new_session->send_states = send_states;
 
-    return new_session;
+    g_hash_table_replace(sessions, strdup(barejid), new_session);
 }
 
 static void
@@ -84,10 +66,7 @@ _chat_session_free(ChatSession *session)
 {
     if (session != NULL) {
         free(session->barejid);
-        if (session->active_timer != NULL) {
-            g_timer_destroy(session->active_timer);
-            session->active_timer = NULL;
-        }
+        free(session->resource);
         free(session);
     }
 }
@@ -106,138 +85,75 @@ chat_sessions_clear(void)
         g_hash_table_remove_all(sessions);
 }
 
-gboolean
-chat_session_on_message_send(const char * const barejid)
+void
+chat_session_resource_override(const char * const barejid, const char * const resource)
 {
-    gboolean send_state = FALSE;
-    if (prefs_get_boolean(PREF_STATES)) {
-        ChatSession *session = g_hash_table_lookup(sessions, barejid);
-        if (!session) {
-            session = _chat_session_new(barejid, TRUE);
-            g_hash_table_insert(sessions, strdup(barejid), session);
-
-        }
-        if (session->supported) {
-            session->state = CHAT_STATE_ACTIVE;
-            g_timer_start(session->active_timer);
-            session->sent = TRUE;
-            send_state = TRUE;
-        }
-    }
+    _chat_session_new(barejid, resource, TRUE, TRUE);
+}
 
-    return send_state;
+ChatSession*
+chat_session_get(const char * const barejid)
+{
+    return g_hash_table_lookup(sessions, barejid);
 }
 
 void
-chat_session_on_incoming_message(const char * const barejid, gboolean supported)
+chat_session_recipient_gone(const char * const barejid, const char * const resource)
 {
+    assert(barejid != NULL);
+    assert(resource != NULL);
+
     ChatSession *session = g_hash_table_lookup(sessions, barejid);
-    if (!session) {
-        session = _chat_session_new(barejid, supported);
-        g_hash_table_insert(sessions, strdup(barejid), session);
-    } else {
-        session->supported = supported;
+    if (session && g_strcmp0(session->resource, resource) == 0) {
+        if (!session->resource_override) {
+            chat_session_remove(barejid);
+        }
     }
 }
 
 void
-chat_session_on_window_open(const char * const barejid)
+chat_session_recipient_typing(const char * const barejid, const char * const resource)
 {
-    if (prefs_get_boolean(PREF_STATES)) {
-        ChatSession *session = g_hash_table_lookup(sessions, barejid);
-        if (!session) {
-            session = _chat_session_new(barejid, TRUE);
-            g_hash_table_insert(sessions, strdup(barejid), session);
-        }
-    }
+    chat_session_recipient_active(barejid, resource, TRUE);
 }
 
 void
-chat_session_on_window_close(const char * const barejid)
+chat_session_recipient_paused(const char * const barejid, const char * const resource)
 {
-    if (prefs_get_boolean(PREF_STATES)) {
-        ChatSession *session = g_hash_table_lookup(sessions, barejid);
-        // send <gone/> chat state before closing
-        if (session->supported) {
-            session->state = CHAT_STATE_GONE;
-            message_send_gone(barejid);
-            session->sent = TRUE;
-            g_hash_table_remove(sessions, barejid);
-        }
-    }
+    chat_session_recipient_active(barejid, resource, TRUE);
 }
 
 void
-chat_session_on_cancel(const char * const jid)
+chat_session_recipient_inactive(const char * const barejid, const char * const resource)
 {
-    if (prefs_get_boolean(PREF_STATES)) {
-        ChatSession *session = g_hash_table_lookup(sessions, jid);
-        if (session) {
-            session->supported = FALSE;
-        }
-    }
+    chat_session_recipient_active(barejid, resource, TRUE);
 }
 
 void
-chat_session_on_activity(const char * const barejid)
+chat_session_recipient_active(const char * const barejid, const char * const resource,
+    gboolean send_states)
 {
+    assert(barejid != NULL);
+    assert(resource != NULL);
+
     ChatSession *session = g_hash_table_lookup(sessions, barejid);
     if (session) {
-        if (session->supported) {
-            if (session->state != CHAT_STATE_COMPOSING) {
-                session->sent = FALSE;
-            }
-
-            session->state = CHAT_STATE_COMPOSING;
-            g_timer_start(session->active_timer);
-
-            if (!session->sent || session->state == CHAT_STATE_PAUSED) {
-                message_send_composing(barejid);
-                session->sent = TRUE;
-            }
+        // session exists with resource, update chat_states
+        if (g_strcmp0(session->resource, resource) == 0) {
+            session->send_states = send_states;
+        // session exists with differet resource and no override, replace
+        } else if (!session->resource_override) {
+            _chat_session_new(barejid, resource, FALSE, send_states);
         }
+
+    // no session, create one
+    } else {
+        _chat_session_new(barejid, resource, FALSE, send_states);
     }
 }
 
 void
-chat_session_on_inactivity(const char * const barejid)
+chat_session_remove(const char * const barejid)
 {
-    ChatSession *session = g_hash_table_lookup(sessions, barejid);
-    if (session && session->supported) {
-        if (session->active_timer != NULL) {
-            gdouble elapsed = g_timer_elapsed(session->active_timer, NULL);
-
-            if ((prefs_get_gone() != 0) && (elapsed > (prefs_get_gone() * 60.0))) {
-                if (session->state != CHAT_STATE_GONE) {
-                    session->sent = FALSE;
-                }
-                session->state = CHAT_STATE_GONE;
-
-            } else if (elapsed > INACTIVE_TIMOUT) {
-                if (session->state != CHAT_STATE_INACTIVE) {
-                    session->sent = FALSE;
-                }
-                session->state = CHAT_STATE_INACTIVE;
-
-            } else if (elapsed > PAUSED_TIMOUT) {
-                if (session->state == CHAT_STATE_COMPOSING) {
-                    session->sent = FALSE;
-                    session->state = CHAT_STATE_PAUSED;
-                }
-            }
-        }
-
-        if (session->sent == FALSE) {
-            if (session->state == CHAT_STATE_GONE) {
-                message_send_gone(barejid);
-                session->sent = TRUE;
-            } else if (session->state == CHAT_STATE_INACTIVE) {
-                message_send_inactive(barejid);
-                session->sent = TRUE;
-            } else if (session->state == CHAT_STATE_PAUSED && prefs_get_boolean(PREF_OUTTYPE)) {
-                message_send_paused(barejid);
-                session->sent = TRUE;
-            }
-        }
-    }
+    g_hash_table_remove(sessions, barejid);
 }
\ No newline at end of file
diff --git a/src/chat_session.h b/src/chat_session.h
index d1815f44..585a523b 100644
--- a/src/chat_session.h
+++ b/src/chat_session.h
@@ -37,15 +37,27 @@
 
 #include <glib.h>
 
+typedef struct chat_session_t {
+    char *barejid;
+    char *resource;
+    gboolean resource_override;
+    gboolean send_states;
+
+} ChatSession;
+
 void chat_sessions_init(void);
 void chat_sessions_clear(void);
 
-gboolean chat_session_on_message_send(const char * const barejid);
-void chat_session_on_window_open(const char * const barejid);
-void chat_session_on_window_close(const char * const barejid);
-void chat_session_on_incoming_message(const char * const barejid, gboolean supported);
-void chat_session_on_cancel(const char * const jid);
-void chat_session_on_activity(const char * const barejid);
-void chat_session_on_inactivity(const char * const recipient);
+void chat_session_resource_override(const char * const barejid, const char * const resource);
+ChatSession* chat_session_get(const char * const barejid);
+
+void chat_session_recipient_active(const char * const barejid, const char * const resource,
+    gboolean send_states);
+void chat_session_recipient_typing(const char * const barejid, const char * const resource);
+void chat_session_recipient_paused(const char * const barejid, const char * const resource);
+void chat_session_recipient_gone(const char * const barejid, const char * const resource);
+void chat_session_recipient_inactive(const char * const barejid, const char * const resource);
+
+void chat_session_remove(const char * const barejid);
 
 #endif
diff --git a/src/chat_state.c b/src/chat_state.c
new file mode 100644
index 00000000..99a83f43
--- /dev/null
+++ b/src/chat_state.c
@@ -0,0 +1,172 @@
+/*
+ * chat_state.c
+ *
+ * Copyright (C) 2012 - 2014 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/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include "chat_state.h"
+#include "chat_session.h"
+#include "xmpp/xmpp.h"
+#include "config/preferences.h"
+
+#define PAUSED_TIMEOUT 10.0
+#define INACTIVE_TIMEOUT 30.0
+
+static void _send_if_supported(const char * const barejid, void(*send_func)(const char * const));
+
+ChatState*
+chat_state_new(void)
+{
+    ChatState *new_state = malloc(sizeof(struct prof_chat_state_t));
+    new_state->type = CHAT_STATE_GONE;
+    new_state->timer = g_timer_new();
+
+    return new_state;
+}
+
+void
+chat_state_free(ChatState *state)
+{
+    if (state && state->timer!= NULL) {
+        g_timer_destroy(state->timer);
+    }
+    free(state);
+}
+
+void
+chat_state_handle_idle(const char * const barejid, ChatState *state)
+{
+    gdouble elapsed = g_timer_elapsed(state->timer, NULL);
+
+    // TYPING -> PAUSED
+    if (state->type == CHAT_STATE_COMPOSING && elapsed > PAUSED_TIMEOUT) {
+        state->type = CHAT_STATE_PAUSED;
+        g_timer_start(state->timer);
+        if (prefs_get_boolean(PREF_STATES) && prefs_get_boolean(PREF_OUTTYPE)) {
+            _send_if_supported(barejid, message_send_paused);
+        }
+        return;
+    }
+
+    // PAUSED|ACTIVE -> INACTIVE
+    if ((state->type == CHAT_STATE_PAUSED || state->type == CHAT_STATE_ACTIVE) && elapsed > INACTIVE_TIMEOUT) {
+        state->type = CHAT_STATE_INACTIVE;
+        g_timer_start(state->timer);
+        if (prefs_get_boolean(PREF_STATES)) {
+            _send_if_supported(barejid, message_send_inactive);
+        }
+        return;
+
+    }
+
+    // INACTIVE -> GONE
+    if (state->type == CHAT_STATE_INACTIVE) {
+        if (prefs_get_gone() != 0 && (elapsed > (prefs_get_gone() * 60.0))) {
+            ChatSession *session = chat_session_get(barejid);
+            if (session) {
+                // never move to GONE when resource override
+                if (!session->resource_override) {
+                    if (prefs_get_boolean(PREF_STATES)) {
+                        _send_if_supported(barejid, message_send_gone);
+                    }
+                    chat_session_remove(barejid);
+                    state->type = CHAT_STATE_GONE;
+                    g_timer_start(state->timer);
+                }
+            } else {
+                if (prefs_get_boolean(PREF_STATES)) {
+                    message_send_gone(barejid);
+                }
+                state->type = CHAT_STATE_GONE;
+                g_timer_start(state->timer);
+            }
+            return;
+        }
+    }
+}
+
+void
+chat_state_handle_typing(const char * const barejid, ChatState *state)
+{
+    // ACTIVE|INACTIVE|PAUSED|GONE -> COMPOSING
+    if (state->type != CHAT_STATE_COMPOSING) {
+        state->type = CHAT_STATE_COMPOSING;
+        g_timer_start(state->timer);
+        if (prefs_get_boolean(PREF_STATES) && prefs_get_boolean(PREF_OUTTYPE)) {
+            _send_if_supported(barejid, message_send_composing);
+        }
+    }
+}
+
+void
+chat_state_active(ChatState *state)
+{
+    state->type = CHAT_STATE_ACTIVE;
+    g_timer_start(state->timer);
+}
+
+void
+chat_state_gone(const char * const barejid, ChatState *state)
+{
+    if (state->type != CHAT_STATE_GONE) {
+        if (prefs_get_boolean(PREF_STATES)) {
+            _send_if_supported(barejid, message_send_gone);
+        }
+        state->type = CHAT_STATE_GONE;
+        g_timer_start(state->timer);
+    }
+}
+
+static void
+_send_if_supported(const char * const barejid, void(*send_func)(const char * const))
+{
+    gboolean send = TRUE;
+    GString *jid = g_string_new(barejid);
+    ChatSession *session = chat_session_get(barejid);
+    if (session) {
+        if (session->send_states) {
+            g_string_append(jid, "/");
+            g_string_append(jid, session->resource);
+        } else {
+            send = FALSE;
+        }
+    }
+
+    if (send) {
+        send_func(jid->str);
+    }
+
+    g_string_free(jid, TRUE);
+}
\ No newline at end of file
diff --git a/src/chat_state.h b/src/chat_state.h
new file mode 100644
index 00000000..ba394a56
--- /dev/null
+++ b/src/chat_state.h
@@ -0,0 +1,61 @@
+/*
+ * chat_state.h
+ *
+ * Copyright (C) 2012 - 2014 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/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef CHAT_STATE_H
+#define CHAT_STATE_H
+
+#include <glib.h>
+
+typedef enum {
+    CHAT_STATE_ACTIVE,
+    CHAT_STATE_COMPOSING,
+    CHAT_STATE_PAUSED,
+    CHAT_STATE_INACTIVE,
+    CHAT_STATE_GONE
+} chat_state_type_t;
+
+typedef struct prof_chat_state_t {
+    chat_state_type_t type;
+    GTimer *timer;
+} ChatState;
+
+ChatState* chat_state_new(void);
+void chat_state_free(ChatState *state);
+
+void chat_state_handle_idle(const char * const barejid, ChatState *state);
+void chat_state_handle_typing(const char * const barejid, ChatState *state);
+void chat_state_active(ChatState *state);
+void chat_state_gone(const char * const barejid, ChatState *state);
+
+#endif
diff --git a/src/command/command.c b/src/command/command.c
index e7d8547a..86185f59 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -96,6 +96,8 @@ static char * _ban_autocomplete(char *input, int *size);
 static char * _affiliation_autocomplete(char *input, int *size);
 static char * _role_autocomplete(char *input, int *size);
 static char * _resource_autocomplete(char *input, int *size);
+static char * _titlebar_autocomplete(char *input, int *size);
+static char * _inpblock_autocomplete(char *input, int *size);
 
 GHashTable *commands = NULL;
 
@@ -278,13 +280,15 @@ static struct cmd_t command_defs[] =
           NULL } } },
 
     { "/resource",
-        cmd_resource, parse_args, 1, 2, NULL,
-        { "/resource set|off [resource]", "Set the contact's resource.",
-        { "/resource set|off [resource]",
-          "----------------------------",
-          "Set the resource to use when chatting to a contact.",
-          "set resource - Set the resource.",
-          "off          - Let the server choose which resource to route messages to.",
+        cmd_resource, parse_args, 1, 2, &cons_resource_setting,
+        { "/resource set|off|title|message [resource]", "Set the contact's resource.",
+        { "/resource set|off|title|message [resource]",
+          "------------------------------------------",
+          "Set the resource to use when chatting to a contact and manage resource display settings.",
+          "set resource   - Set the resource.",
+          "off            - Let the server choose which resource to route messages to.",
+          "title on|off   - Show or hide the current resource in the titlebar.",
+          "message on|off - Show or hide the resource from which a message was recieved.",
           NULL } } },
 
     { "/join",
@@ -620,14 +624,18 @@ static struct cmd_t command_defs[] =
           NULL } } },
 
     { "/inpblock",
-        cmd_inpblock, parse_args, 1, 1, &cons_inpblock_setting,
-        { "/inpblock millis", "Input blocking delay.",
-        { "/inpblock millis",
-          "----------------",
-          "Time to wait in milliseconds before reading input from the terminal buffer, defaults to 20.",
-          "Valid values are 1-1000.",
-          "A higher value will result in less CPU usage, but a noticable delay for response to input.",
-          "A lower value will result in higher CPU usage, but faster response to input.",
+        cmd_inpblock, parse_args, 2, 2, &cons_inpblock_setting,
+        { "/inpblock timeout|dynamic [millis|on|off]", "Input blocking delay (dynamic or static).",
+        { "/inpblock timeout|dynamic [millis|on|off]",
+          "-----------------------------------------",
+          "How long to wait for input before checking for new messages or checking for state changes such as 'idle'.",
+          "timeout : Time to wait in milliseconds before reading input from the terminal buffer, defaults to 500.",
+          "        : Valid values are 1-1000.",
+          "dynamic : Start with a 0 timeout and increase the value dynamically up to the specified 'timeout'.",
+          "        : on|off",
+          "A higher timeout will result in less CPU usage, but a noticable delay for response to input.",
+          "A lower timeout will result in higher CPU usage, but faster response to input.",
+          "Using the dynamic setting, higher CPU usage will occur during activity, but over time the CPU usage will decrease whilst there is no activity.",
           NULL } } },
 
     { "/notify",
@@ -719,11 +727,13 @@ static struct cmd_t command_defs[] =
           NULL  } } },
 
     { "/titlebar",
-        cmd_titlebar, parse_args, 1, 1, &cons_titlebar_setting,
-        { "/titlebar on|off", "Show information in the window title bar.",
-        { "/titlebar on|off",
-          "----------------",
-          "Show information in the window title bar.",
+        cmd_titlebar, parse_args, 2, 2, &cons_titlebar_setting,
+        { "/titlebar show|goodbye on|off", "Manage the terminal window title.",
+        { "/titlebar show|goodbye on|off",
+          "---------------------",
+          "Show or hide a title and exit message in the terminal window title.",
+          "show    - Show current logged in user, and unread messages in the title.",
+          "goodbye - Show a message in the title when exiting profanity.",
           NULL  } } },
 
     { "/mouse",
@@ -1110,6 +1120,7 @@ static Autocomplete occupants_ac;
 static Autocomplete occupants_default_ac;
 static Autocomplete time_ac;
 static Autocomplete resource_ac;
+static Autocomplete inpblock_ac;
 
 /*
  * Initialise command autocompleter and history
@@ -1205,7 +1216,8 @@ cmd_init(void)
     autocomplete_add(sub_ac, "received");
 
     titlebar_ac = autocomplete_new();
-    autocomplete_add(titlebar_ac, "version");
+    autocomplete_add(titlebar_ac, "show");
+    autocomplete_add(titlebar_ac, "goodbye");
 
     log_ac = autocomplete_new();
     autocomplete_add(log_ac, "maxsize");
@@ -1458,6 +1470,12 @@ cmd_init(void)
     resource_ac = autocomplete_new();
     autocomplete_add(resource_ac, "set");
     autocomplete_add(resource_ac, "off");
+    autocomplete_add(resource_ac, "title");
+    autocomplete_add(resource_ac, "message");
+
+    inpblock_ac = autocomplete_new();
+    autocomplete_add(inpblock_ac, "timeout");
+    autocomplete_add(inpblock_ac, "dynamic");
 
     cmd_history_init();
 }
@@ -1515,6 +1533,7 @@ cmd_uninit(void)
     autocomplete_free(occupants_default_ac);
     autocomplete_free(time_ac);
     autocomplete_free(resource_ac);
+    autocomplete_free(inpblock_ac);
 }
 
 gboolean
@@ -1660,6 +1679,7 @@ cmd_reset_autocomplete()
     autocomplete_reset(roster_option_ac);
     autocomplete_reset(roster_by_ac);
     autocomplete_reset(group_ac);
+    autocomplete_reset(titlebar_ac);
     autocomplete_reset(bookmark_ac);
     autocomplete_reset(bookmark_property_ac);
     autocomplete_reset(otr_ac);
@@ -1682,6 +1702,7 @@ cmd_reset_autocomplete()
     autocomplete_reset(occupants_default_ac);
     autocomplete_reset(time_ac);
     autocomplete_reset(resource_ac);
+    autocomplete_reset(inpblock_ac);
 
     if (ui_current_win_type() == WIN_CHAT) {
         ProfChatWin *chatwin = wins_get_current_chat();
@@ -1817,8 +1838,7 @@ cmd_execute_default(const char * inp)
                 if (otr_is_secure(chatwin->barejid)) {
                     char *encrypted = otr_encrypt_message(chatwin->barejid, inp);
                     if (encrypted != NULL) {
-                        gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                        message_send_chat(chatwin->barejid, chatwin->resource, encrypted, send_state);
+                        message_send_chat(chatwin->barejid, encrypted);
                         otr_free_message(encrypted);
                         if (prefs_get_boolean(PREF_CHLOG)) {
                             const char *jid = jabber_get_fulljid();
@@ -1838,8 +1858,7 @@ cmd_execute_default(const char * inp)
                         cons_show_error("Failed to send message.");
                     }
                 } else {
-                    gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                    message_send_chat(chatwin->barejid, chatwin->resource, inp, send_state);
+                    message_send_chat(chatwin->barejid, inp);
                     if (prefs_get_boolean(PREF_CHLOG)) {
                         const char *jid = jabber_get_fulljid();
                         Jid *jidp = jid_create(jid);
@@ -1850,8 +1869,7 @@ cmd_execute_default(const char * inp)
                     ui_outgoing_chat_msg("me", chatwin->barejid, inp);
                 }
 #else
-                gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                message_send_chat(chatwin->barejid, chatwin->resource, inp, send_state);
+                message_send_chat(chatwin->barejid, inp);
                 if (prefs_get_boolean(PREF_CHLOG)) {
                     const char *jid = jabber_get_fulljid();
                     Jid *jidp = jid_create(jid);
@@ -1894,7 +1912,7 @@ _cmd_complete_parameters(char *input, int *size)
 
     // autocomplete boolean settings
     gchar *boolean_choices[] = { "/beep", "/intype", "/states", "/outtype",
-        "/flash", "/splash", "/chlog", "/grlog", "/mouse", "/history", "/titlebar",
+        "/flash", "/splash", "/chlog", "/grlog", "/mouse", "/history",
         "/vercheck", "/privileges", "/presence", "/wrap" };
 
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
@@ -2004,6 +2022,8 @@ _cmd_complete_parameters(char *input, int *size)
     g_hash_table_insert(ac_funcs, "/affiliation",   _affiliation_autocomplete);
     g_hash_table_insert(ac_funcs, "/role",          _role_autocomplete);
     g_hash_table_insert(ac_funcs, "/resource",      _resource_autocomplete);
+    g_hash_table_insert(ac_funcs, "/titlebar",      _titlebar_autocomplete);
+    g_hash_table_insert(ac_funcs, "/inpblock",      _inpblock_autocomplete);
 
     char parsed[*size+1];
     i = 0;
@@ -2468,6 +2488,16 @@ _resource_autocomplete(char *input, int *size)
         }
     }
 
+    found = autocomplete_param_with_func(input, size, "/resource title", prefs_autocomplete_boolean_choice);
+    if (found != NULL) {
+        return found;
+    }
+
+    found = autocomplete_param_with_func(input, size, "/resource message", prefs_autocomplete_boolean_choice);
+    if (found != NULL) {
+        return found;
+    }
+
     found = autocomplete_param_with_ac(input, size, "/resource", resource_ac, FALSE);
     if (found != NULL) {
         return found;
@@ -2477,6 +2507,47 @@ _resource_autocomplete(char *input, int *size)
 }
 
 static char *
+_titlebar_autocomplete(char *input, int *size)
+{
+    char *found = NULL;
+
+    found = autocomplete_param_with_func(input, size, "/titlebar show", prefs_autocomplete_boolean_choice);
+    if (found != NULL) {
+        return found;
+    }
+
+    found = autocomplete_param_with_func(input, size, "/titlebar goodbye", prefs_autocomplete_boolean_choice);
+    if (found != NULL) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, size, "/titlebar", titlebar_ac, FALSE);
+    if (found != NULL) {
+        return found;
+    }
+
+    return NULL;
+}
+
+static char *
+_inpblock_autocomplete(char *input, int *size)
+{
+    char *found = NULL;
+
+    found = autocomplete_param_with_func(input, size, "/inpblock dynamic", prefs_autocomplete_boolean_choice);
+    if (found != NULL) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, size, "/inpblock", inpblock_ac, FALSE);
+    if (found != NULL) {
+        return found;
+    }
+
+    return NULL;
+}
+
+static char *
 _form_autocomplete(char *input, int *size)
 {
     ProfWin *current = wins_get_current();
diff --git a/src/command/commands.c b/src/command/commands.c
index 1680c27b..3647bb22 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -1219,24 +1219,12 @@ cmd_msg(gchar **args, struct cmd_help_t help)
             barejid = usr;
         }
 
-        // if msg to current recipient, and resource specified, set resource
-        char *resource = NULL;
-        ProfWin *current = wins_get_current();
-        if (current->type == WIN_CHAT) {
-            ProfChatWin *chatwin = (ProfChatWin*)current;
-            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-            if ((g_strcmp0(chatwin->barejid, barejid) == 0) && (chatwin->resource)) {
-                resource = chatwin->resource;
-            }
-        }
-
         if (msg != NULL) {
 #ifdef HAVE_LIBOTR
             if (otr_is_secure(barejid)) {
                 char *encrypted = otr_encrypt_message(barejid, msg);
                 if (encrypted != NULL) {
-                    gboolean send_state = chat_session_on_message_send(barejid);
-                    message_send_chat(barejid, resource, encrypted, send_state);
+                    message_send_chat(barejid, encrypted);
                     otr_free_message(encrypted);
                     ui_outgoing_chat_msg("me", barejid, msg);
 
@@ -1265,13 +1253,11 @@ cmd_msg(gchar **args, struct cmd_help_t help)
                     GString *otr_message = g_string_new(msg);
                     g_string_append(otr_message, OTRL_MESSAGE_TAG_BASE);
                     g_string_append(otr_message, OTRL_MESSAGE_TAG_V2);
-                    gboolean send_state = chat_session_on_message_send(barejid);
-                    message_send_chat(barejid, resource, otr_message->str, send_state);
+                    message_send_chat(barejid, otr_message->str);
 
                     g_string_free(otr_message, TRUE);
                 } else {
-                    gboolean send_state = chat_session_on_message_send(barejid);
-                    message_send_chat(barejid, resource, msg, send_state);
+                    message_send_chat(barejid, msg);
                 }
                 ui_outgoing_chat_msg("me", barejid, msg);
 
@@ -1284,8 +1270,7 @@ cmd_msg(gchar **args, struct cmd_help_t help)
             }
             return TRUE;
 #else
-            gboolean send_state = chat_session_on_message_send(barejid);
-            message_send_chat(barejid, resource, msg, send_state);
+            message_send_chat(barejid, msg);
             ui_outgoing_chat_msg("me", barejid, msg);
 
             if (((win_type == WIN_CHAT) || (win_type == WIN_CONSOLE)) && prefs_get_boolean(PREF_CHLOG)) {
@@ -1298,7 +1283,6 @@ cmd_msg(gchar **args, struct cmd_help_t help)
 #endif
 
         } else { // msg == NULL
-            chat_session_on_window_open(barejid);
             ui_new_chat_win(barejid);
 #ifdef HAVE_LIBOTR
             if (otr_is_secure(barejid)) {
@@ -1603,16 +1587,33 @@ cmd_roster(gchar **args, struct cmd_help_t help)
 gboolean
 cmd_resource(gchar **args, struct cmd_help_t help)
 {
+    char *cmd = args[0];
+    char *setting = NULL;
+    if (g_strcmp0(cmd, "message") == 0) {
+        setting = args[1];
+        if (!setting) {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        } else {
+            return _cmd_set_boolean_preference(setting, help, "Message resource", PREF_RESOURCE_MESSAGE);
+        }
+    } else if (g_strcmp0(cmd, "title") == 0) {
+        setting = args[1];
+        if (!setting) {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        } else {
+            return _cmd_set_boolean_preference(setting, help, "Title resource", PREF_RESOURCE_TITLE);
+        }
+    }
+
     ProfWin *current = wins_get_current();
     if (current->type != WIN_CHAT) {
-        cons_show("The /resource command is only valid in chat windows.");
+        cons_show("Resource can only be changed in chat windows.");
         return TRUE;
     }
-
     ProfChatWin *chatwin = (ProfChatWin*)current;
 
-    char *cmd = args[0];
-
     if (g_strcmp0(cmd, "set") == 0) {
         char *resource = args[1];
         if (!resource) {
@@ -1638,11 +1639,17 @@ cmd_resource(gchar **args, struct cmd_help_t help)
             return TRUE;
         }
 
-        chatwin->resource = strdup(resource);
+        chatwin->resource_override = strdup(resource);
+        chat_state_free(chatwin->state);
+        chatwin->state = chat_state_new();
+        chat_session_resource_override(chatwin->barejid, resource);
         return TRUE;
 
     } else if (g_strcmp0(cmd, "off") == 0) {
-        FREE_SET_NULL(chatwin->resource);
+        FREE_SET_NULL(chatwin->resource_override);
+        chat_state_free(chatwin->state);
+        chatwin->state = chat_state_new();
+        chat_session_remove(chatwin->barejid);
         return TRUE;
     } else {
         cons_show("Usage: %s", help.usage);
@@ -3025,8 +3032,7 @@ cmd_tiny(gchar **args, struct cmd_help_t help)
                 if (otr_is_secure(chatwin->barejid)) {
                     char *encrypted = otr_encrypt_message(chatwin->barejid, tiny);
                     if (encrypted != NULL) {
-                        gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                        message_send_chat(chatwin->barejid, chatwin->resource, encrypted, send_state);
+                        message_send_chat(chatwin->barejid, encrypted);
                         otr_free_message(encrypted);
                         if (prefs_get_boolean(PREF_CHLOG)) {
                             const char *jid = jabber_get_fulljid();
@@ -3046,8 +3052,7 @@ cmd_tiny(gchar **args, struct cmd_help_t help)
                         cons_show_error("Failed to send message.");
                     }
                 } else {
-                    gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                    message_send_chat(chatwin->barejid, chatwin->resource, tiny, send_state);
+                    message_send_chat(chatwin->barejid, tiny);
                     if (prefs_get_boolean(PREF_CHLOG)) {
                         const char *jid = jabber_get_fulljid();
                         Jid *jidp = jid_create(jid);
@@ -3058,8 +3063,7 @@ cmd_tiny(gchar **args, struct cmd_help_t help)
                     ui_outgoing_chat_msg("me", chatwin->barejid, tiny);
                 }
 #else
-                gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                message_send_chat(chatwin->barejid, chatwin->resource, tiny, send_state);
+                message_send_chat(chatwin->barejid, tiny);
                 if (prefs_get_boolean(PREF_CHLOG)) {
                     const char *jid = jabber_get_fulljid();
                     Jid *jidp = jid_create(jid);
@@ -3264,10 +3268,18 @@ cmd_states(gchar **args, struct cmd_help_t help)
 gboolean
 cmd_titlebar(gchar **args, struct cmd_help_t help)
 {
-    if (g_strcmp0(args[0], "off") == 0) {
+    if (g_strcmp0(args[0], "show") != 0 && g_strcmp0(args[0], "goodbye") != 0) {
+        cons_show("Usage: %s", help.usage);
+        return TRUE;
+    }
+    if (g_strcmp0(args[0], "show") == 0 && g_strcmp0(args[1], "off") == 0) {
         ui_clear_win_title();
     }
-    return _cmd_set_boolean_preference(args[0], help, "Titlebar", PREF_TITLEBAR);
+    if (g_strcmp0(args[0], "show") == 0) {
+        return _cmd_set_boolean_preference(args[1], help, "Titlebar show", PREF_TITLEBAR_SHOW);
+    } else {
+        return _cmd_set_boolean_preference(args[1], help, "Titlebar goodbye", PREF_TITLEBAR_GOODBYE);
+    }
 }
 
 gboolean
@@ -3454,13 +3466,41 @@ cmd_notify(gchar **args, struct cmd_help_t help)
 gboolean
 cmd_inpblock(gchar **args, struct cmd_help_t help)
 {
-    char *value = args[0];
+    char *subcmd = args[0];
+    char *value = args[1];
     int intval;
-    if (_strtoi(value, &intval, 1, 1000) == 0) {
-        cons_show("Input blocking set to %d milliseconds.", intval);
-        prefs_set_inpblock(intval);
-        ui_input_nonblocking();
+
+    if (g_strcmp0(subcmd, "timeout") == 0) {
+        if (value == NULL) {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        }
+
+        if (_strtoi(value, &intval, 1, 1000) == 0) {
+            cons_show("Input blocking set to %d milliseconds.", intval);
+            prefs_set_inpblock(intval);
+            ui_input_nonblocking(FALSE);
+        }
+
+        return TRUE;
+    }
+
+    if (g_strcmp0(subcmd, "dynamic") == 0) {
+        if (value == NULL) {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        }
+
+        if (g_strcmp0(value, "on") != 0 && g_strcmp0(value, "off") != 0) {
+            cons_show("Dynamic must be one of 'on' or 'off'");
+            return TRUE;
+        }
+
+        return _cmd_set_boolean_preference(value, help, "Dynamic input blocking", PREF_INPBLOCK_DYNAMIC);
     }
+
+    cons_show("Usage: %s", help.usage);
+
     return TRUE;
 }
 
@@ -3967,7 +4007,6 @@ cmd_otr(gchar **args, struct cmd_help_t help)
                 barejid = contact;
             }
 
-            chat_session_on_window_open(barejid);
             ui_new_chat_win(barejid);
 
             if (ui_current_win_is_otr()) {
@@ -3977,8 +4016,7 @@ cmd_otr(gchar **args, struct cmd_help_t help)
                     ui_current_print_formatted_line('!', 0, "You have not generated or loaded a private key, use '/otr gen'");
                 } else if (!otr_is_secure(barejid)) {
                     char *otr_query_message = otr_start_query();
-                    gboolean send_state = chat_session_on_message_send(barejid);
-                    message_send_chat(barejid, NULL, otr_query_message, send_state);
+                    message_send_chat(barejid, otr_query_message);
                 } else {
                     ui_gone_secure(barejid, otr_is_trusted(barejid));
                 }
@@ -3996,8 +4034,7 @@ cmd_otr(gchar **args, struct cmd_help_t help)
                 } else {
                     ProfChatWin *chatwin = ui_get_current_chat();
                     char *otr_query_message = otr_start_query();
-                    gboolean send_state = chat_session_on_message_send(chatwin->barejid);
-                    message_send_chat(chatwin->barejid, NULL, otr_query_message, send_state);
+                    message_send_chat(chatwin->barejid, otr_query_message);
                 }
             }
         }
diff --git a/src/command/commands.h b/src/command/commands.h
index dd09fae0..f4e040a9 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -140,4 +140,4 @@ gboolean cmd_inpblock(gchar **args, struct cmd_help_t help);
 
 gboolean cmd_form_field(char *tag, gchar **args);
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 22e80874..5b683426 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -52,6 +52,7 @@
 #include "preferences.h"
 #include "tools/autocomplete.h"
 
+// preference groups refer to the sections in .profrc, for example [ui]
 #define PREF_GROUP_LOGGING "logging"
 #define PREF_GROUP_CHATSTATES "chatstates"
 #define PREF_GROUP_UI "ui"
@@ -127,6 +128,16 @@ prefs_load(void)
         g_error_free(err);
     }
 
+    // move pre 0.4.6 titlebar preference
+    err = NULL;
+    gchar *old_titlebar = g_key_file_get_string(prefs, PREF_GROUP_UI, "titlebar", &err);
+    if (err == NULL) {
+        g_key_file_set_string(prefs, PREF_GROUP_UI, _get_key(PREF_TITLEBAR_SHOW), old_titlebar);
+        g_key_file_remove_key(prefs, PREF_GROUP_UI, "titlebar", NULL);
+    } else {
+        g_error_free(err);
+    }
+
     _save_prefs();
 
     boolean_choice_ac = autocomplete_new();
@@ -487,6 +498,9 @@ _get_preferences_file(void)
     return result;
 }
 
+// get the preference group for a specific preference
+// for example the PREF_BEEP setting ("beep" in .profrc, see _get_key) belongs
+// to the [ui] section.
 static const char *
 _get_group(preference_t pref)
 {
@@ -496,7 +510,8 @@ _get_group(preference_t pref)
         case PREF_BEEP:
         case PREF_THEME:
         case PREF_VERCHECK:
-        case PREF_TITLEBAR:
+        case PREF_TITLEBAR_SHOW:
+        case PREF_TITLEBAR_GOODBYE:
         case PREF_FLASH:
         case PREF_INTYPE:
         case PREF_HISTORY:
@@ -514,6 +529,9 @@ _get_group(preference_t pref)
         case PREF_ROSTER_OFFLINE:
         case PREF_ROSTER_RESOURCE:
         case PREF_ROSTER_BY:
+        case PREF_RESOURCE_TITLE:
+        case PREF_RESOURCE_MESSAGE:
+        case PREF_INPBLOCK_DYNAMIC:
             return PREF_GROUP_UI;
         case PREF_STATES:
         case PREF_OUTTYPE:
@@ -550,6 +568,8 @@ _get_group(preference_t pref)
     }
 }
 
+// get the key used in .profrc for the preference
+// for example the PREF_AUTOAWAY_MODE maps to "autoaway.mode" in .profrc
 static const char *
 _get_key(preference_t pref)
 {
@@ -563,8 +583,10 @@ _get_key(preference_t pref)
             return "theme";
         case PREF_VERCHECK:
             return "vercheck";
-        case PREF_TITLEBAR:
-            return "titlebar";
+        case PREF_TITLEBAR_SHOW:
+            return "titlebar.show";
+        case PREF_TITLEBAR_GOODBYE:
+            return "titlebar.goodbye";
         case PREF_FLASH:
             return "flash";
         case PREF_INTYPE:
@@ -647,11 +669,19 @@ _get_key(preference_t pref)
             return "roster.resource";
         case PREF_ROSTER_BY:
             return "roster.by";
+        case PREF_RESOURCE_TITLE:
+            return "resource.title";
+        case PREF_RESOURCE_MESSAGE:
+            return "resource.message";
+        case PREF_INPBLOCK_DYNAMIC:
+            return "inpblock.dynamic";
         default:
             return NULL;
     }
 }
 
+// the default setting for a boolean type preference
+// if it is not specified in .profrc
 static gboolean
 _get_default_boolean(preference_t pref)
 {
@@ -669,12 +699,15 @@ _get_default_boolean(preference_t pref)
         case PREF_MUC_PRIVILEGES:
         case PREF_PRESENCE:
         case PREF_WRAP:
+        case PREF_INPBLOCK_DYNAMIC:
             return TRUE;
         default:
             return FALSE;
     }
 }
 
+// the default setting for a string type preference
+// if it is not specified in .profrc
 static char *
 _get_default_string(preference_t pref)
 {
diff --git a/src/config/preferences.h b/src/config/preferences.h
index c8b206ef..9590eb64 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -47,12 +47,15 @@
 #define PREFS_MIN_LOG_SIZE 64
 #define PREFS_MAX_LOG_SIZE 1048580
 
+// represents all settings in .profrc
+// each enum value is mapped to a group and key in .profrc (see preferences.c)
 typedef enum {
     PREF_SPLASH,
     PREF_BEEP,
     PREF_VERCHECK,
     PREF_THEME,
-    PREF_TITLEBAR,
+    PREF_TITLEBAR_SHOW,
+    PREF_TITLEBAR_GOODBYE,
     PREF_FLASH,
     PREF_INTYPE,
     PREF_HISTORY,
@@ -95,7 +98,10 @@ typedef enum {
     PREF_LOG_SHARED,
     PREF_OTR_LOG,
     PREF_OTR_WARN,
-    PREF_OTR_POLICY
+    PREF_OTR_POLICY,
+    PREF_RESOURCE_TITLE,
+    PREF_RESOURCE_MESSAGE,
+    PREF_INPBLOCK_DYNAMIC
 } preference_t;
 
 typedef struct prof_alias_t {
diff --git a/src/otr/otr.c b/src/otr/otr.c
index 680c4c10..bd1c2ce3 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -110,8 +110,7 @@ static void
 cb_inject_message(void *opdata, const char *accountname,
     const char *protocol, const char *recipient, const char *message)
 {
-    gboolean send_state = chat_session_on_message_send(recipient);
-    message_send_chat(recipient, NULL, message, send_state);
+    message_send_chat(recipient, message);
 }
 
 static void
diff --git a/src/profanity.c b/src/profanity.c
index e53ac176..b28eae20 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -46,6 +46,7 @@
 
 #include "profanity.h"
 #include "chat_session.h"
+#include "chat_state.h"
 #include "config/accounts.h"
 #include "config/preferences.h"
 #include "config/theme.h"
@@ -75,7 +76,7 @@ prof_run(const int disable_tls, char *log_level, char *account_name)
 {
     _init(disable_tls, log_level);
     log_info("Starting main event loop");
-    ui_input_nonblocking();
+    ui_input_nonblocking(TRUE);
     GTimer *timer = g_timer_new();
     gboolean cmd_result = TRUE;
     jabber_conn_status_t conn_status = jabber_get_connection_status();
@@ -142,7 +143,8 @@ prof_handle_idle(void)
 
         while (curr != NULL) {
             char *barejid = curr->data;
-            chat_session_on_inactivity(barejid);
+            ProfChatWin *chatwin = wins_get_chat(barejid);
+            chat_state_handle_idle(chatwin->barejid, chatwin->state);
             curr = g_slist_next(curr);
         }
 
@@ -160,7 +162,7 @@ prof_handle_activity(void)
 
     if ((status == JABBER_CONNECTED) && (win_type == WIN_CHAT)) {
         ProfChatWin *chatwin = wins_get_current_chat();
-        chat_session_on_activity(chatwin->barejid);
+        chat_state_handle_typing(chatwin->barejid, chatwin->state);
     }
 }
 
@@ -290,7 +292,13 @@ _init(const int disable_tls, char *log_level)
 static void
 _shutdown(void)
 {
-    ui_clear_win_title();
+    if (prefs_get_boolean(PREF_TITLEBAR_SHOW)) {
+        if (prefs_get_boolean(PREF_TITLEBAR_GOODBYE)) {
+            ui_goodbye_title();
+        } else {
+            ui_clear_win_title();
+        }
+    }
     ui_close_all_wins();
     jabber_disconnect();
     jabber_shutdown();
diff --git a/src/server_events.c b/src/server_events.c
index 9d7883d6..fbf534ac 100644
--- a/src/server_events.c
+++ b/src/server_events.c
@@ -86,8 +86,9 @@ handle_message_error(const char * const jid, const char * const type,
 
     // handle recipient not found ('from' contains a value and type is 'cancel')
     } else if (type != NULL && (strcmp(type, "cancel") == 0)) {
-        ui_handle_recipient_not_found(jid, err_msg);
-        chat_session_on_cancel(jid);
+        log_info("Recipient %s not found: %s", jid, err_msg);
+        Jid *jidp = jid_create(jid);
+        chat_session_remove(jidp->barejid);
 
     // handle any other error from recipient
     } else {
@@ -296,7 +297,7 @@ handle_incoming_private_message(char *fulljid, char *message)
 }
 
 void
-handle_incoming_message(char *barejid, char *message)
+handle_incoming_message(char *barejid, char *resource, char *message)
 {
 #ifdef HAVE_LIBOTR
     gboolean was_decrypted = FALSE;
@@ -318,8 +319,7 @@ handle_incoming_message(char *barejid, char *message)
                 memmove(whitespace_base, whitespace_base+tag_length, tag_length);
                 char *otr_query_message = otr_start_query();
                 cons_show("OTR Whitespace pattern detected. Attempting to start OTR session...");
-                gboolean send_state = chat_session_on_message_send(barejid);
-                message_send_chat(barejid, NULL, otr_query_message, send_state);
+                message_send_chat(barejid, otr_query_message);
             }
         }
     }
@@ -333,11 +333,10 @@ handle_incoming_message(char *barejid, char *message)
     if (policy == PROF_OTRPOLICY_ALWAYS && !was_decrypted && !whitespace_base) {
         char *otr_query_message = otr_start_query();
         cons_show("Attempting to start OTR session...");
-        gboolean send_state = chat_session_on_message_send(barejid);
-        message_send_chat(barejid, NULL, otr_query_message, send_state);
+        message_send_chat(barejid, otr_query_message);
     }
 
-    ui_incoming_msg(barejid, newmessage, NULL);
+    ui_incoming_msg(barejid, resource, newmessage, NULL);
 
     if (prefs_get_boolean(PREF_CHLOG)) {
         const char *jid = jabber_get_fulljid();
@@ -356,7 +355,7 @@ handle_incoming_message(char *barejid, char *message)
 
     otr_free_message(newmessage);
 #else
-    ui_incoming_msg(barejid, message, NULL);
+    ui_incoming_msg(barejid, resource, message, NULL);
 
     if (prefs_get_boolean(PREF_CHLOG)) {
         const char *jid = jabber_get_fulljid();
@@ -376,7 +375,7 @@ handle_delayed_private_message(char *fulljid, char *message, GTimeVal tv_stamp)
 void
 handle_delayed_message(char *barejid, char *message, GTimeVal tv_stamp)
 {
-    ui_incoming_msg(barejid, message, &tv_stamp);
+    ui_incoming_msg(barejid, NULL, message, &tv_stamp);
 
     if (prefs_get_boolean(PREF_CHLOG)) {
         const char *jid = jabber_get_fulljid();
@@ -387,15 +386,45 @@ handle_delayed_message(char *barejid, char *message, GTimeVal tv_stamp)
 }
 
 void
-handle_typing(char *from)
+handle_typing(char *barejid, char *resource)
+{
+    ui_contact_typing(barejid, resource);
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_typing(barejid, resource);
+    }
+}
+
+void
+handle_paused(char *barejid, char *resource)
+{
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_paused(barejid, resource);
+    }
+}
+
+void
+handle_inactive(char *barejid, char *resource)
+{
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_inactive(barejid, resource);
+    }
+}
+
+void
+handle_gone(const char * const barejid, const char * const resource)
 {
-    ui_contact_typing(from);
+    ui_recipient_gone(barejid, resource);
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_gone(barejid, resource);
+    }
 }
 
 void
-handle_gone(const char * const from)
+handle_activity(const char * const barejid, const char * const resource, gboolean send_states)
 {
-    ui_recipient_gone(from);
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_active(barejid, resource, send_states);
+    }
 }
 
 void
@@ -433,38 +462,11 @@ handle_contact_offline(char *barejid, char *resource, char *status)
     gboolean updated = roster_contact_offline(barejid, resource, status);
 
     if (resource != NULL && updated) {
-        char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
-        char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
-        Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
-        PContact contact = roster_get_contact(barejid);
-        if (p_contact_subscription(contact) != NULL) {
-            if (strcmp(p_contact_subscription(contact), "none") != 0) {
-
-                // show in console if "all"
-                if (g_strcmp0(show_console, "all") == 0) {
-                    cons_show_contact_offline(contact, resource, status);
-
-                // show in console of "online"
-                } else if (g_strcmp0(show_console, "online") == 0) {
-                    cons_show_contact_offline(contact, resource, status);
-                }
-
-                // show in chat win if "all"
-                if (g_strcmp0(show_chat_win, "all") == 0) {
-                    ui_chat_win_contact_offline(contact, resource, status);
-
-                // show in char win if "online" and presence online
-                } else if (g_strcmp0(show_chat_win, "online") == 0) {
-                    ui_chat_win_contact_offline(contact, resource, status);
-                }
-            }
-        }
-        prefs_free_string(show_console);
-        prefs_free_string(show_chat_win);
-        jid_destroy(jid);
+        ui_contact_offline(barejid, resource, status);
     }
 
     rosterwin_roster();
+    chat_session_remove(barejid);
 }
 
 void
@@ -507,6 +509,7 @@ handle_contact_online(char *barejid, Resource *resource,
     }
 
     rosterwin_roster();
+    chat_session_remove(barejid);
 }
 
 void
diff --git a/src/server_events.h b/src/server_events.h
index 32ac62f3..6a12dc6e 100644
--- a/src/server_events.h
+++ b/src/server_events.h
@@ -69,12 +69,15 @@ void handle_room_role_list(const char * const from, const char * const role, GSL
 void handle_room_role_set_error(const char * const room, const char * const nick, const char * const role,
     const char * const error);
 void handle_room_kick_result_error(const char * const room, const char * const nick, const char * const error);
-void handle_incoming_message(char *barejid, char *message);
+void handle_incoming_message(char *barejid, char *resource, char *message);
 void handle_incoming_private_message(char *fulljid, char *message);
 void handle_delayed_message(char *fulljid, char *message, GTimeVal tv_stamp);
 void handle_delayed_private_message(char *fulljid, char *message, GTimeVal tv_stamp);
-void handle_typing(char *from);
-void handle_gone(const char * const from);
+void handle_typing(char *barejid, char *resource);
+void handle_paused(char *barejid, char *resource);
+void handle_inactive(char *barejid, char *resource);
+void handle_activity(char *barejid, char *resource, gboolean send_states);
+void handle_gone(const char * const barejid, const char * const resource);
 void handle_subscription(const char *from, jabber_subscr_t type);
 void handle_contact_offline(char *contact, char *resource, char *status);
 void handle_contact_online(char *contact, Resource *resource,
diff --git a/src/ui/console.c b/src/ui/console.c
index 3d3a5d8b..8d1f3173 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -841,6 +841,19 @@ cons_beep_setting(void)
 }
 
 void
+cons_resource_setting(void)
+{
+    if (prefs_get_boolean(PREF_RESOURCE_TITLE))
+        cons_show("Resource title (/resource)    : ON");
+    else
+        cons_show("Resource title (/resource)    : OFF");
+    if (prefs_get_boolean(PREF_RESOURCE_MESSAGE))
+        cons_show("Message title (/resource)     : ON");
+    else
+        cons_show("Message title (/resource)     : OFF");
+}
+
+void
 cons_wrap_setting(void)
 {
     if (prefs_get_boolean(PREF_WRAP))
@@ -951,10 +964,15 @@ cons_statuses_setting(void)
 void
 cons_titlebar_setting(void)
 {
-    if (prefs_get_boolean(PREF_TITLEBAR)) {
-        cons_show("Titlebar display (/titlebar)  : ON");
+    if (prefs_get_boolean(PREF_TITLEBAR_SHOW)) {
+        cons_show("Titlebar show (/titlebar)     : ON");
+    } else {
+        cons_show("Titlebar show (/titlebar)     : OFF");
+    }
+    if (prefs_get_boolean(PREF_TITLEBAR_GOODBYE)) {
+        cons_show("Titlebar goodbye (/titlebar)  : ON");
     } else {
-        cons_show("Titlebar display (/titlebar)  : OFF");
+        cons_show("Titlebar goodbye (/titlebar)  : OFF");
     }
 }
 
@@ -991,6 +1009,7 @@ cons_show_ui_prefs(void)
     cons_splash_setting();
     cons_wrap_setting();
     cons_time_setting();
+    cons_resource_setting();
     cons_vercheck_setting();
     cons_mouse_setting();
     cons_statuses_setting();
@@ -1163,7 +1182,12 @@ cons_show_chat_prefs(void)
 void
 cons_inpblock_setting(void)
 {
-    cons_show("Input block (/inpblock)       : %d milliseconds", prefs_get_inpblock());
+    cons_show("Input timeout (/inpblock)     : %d milliseconds", prefs_get_inpblock());
+    if (prefs_get_boolean(PREF_INPBLOCK_DYNAMIC)) {
+        cons_show("Dynamic timeout (/inpblock)   : ON");
+    } else {
+        cons_show("Dynamic timeout (/inpblock)   : OFF");
+    }
 }
 
 void
diff --git a/src/ui/core.c b/src/ui/core.c
index 2e8ae9ff..e0f46f6e 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -122,7 +122,7 @@ ui_update(void)
 
     win_update_virtual(current);
 
-    if (prefs_get_boolean(PREF_TITLEBAR)) {
+    if (prefs_get_boolean(PREF_TITLEBAR_SHOW)) {
         _ui_draw_term_title();
     }
     title_bar_update_virtual();
@@ -180,7 +180,11 @@ ui_get_char(char *input, int *size, int *result)
     wint_t ch = inp_get_char(input, size, result);
     if (ch != ERR && *result != ERR) {
         ui_reset_idle_time();
+        ui_input_nonblocking(TRUE);
+    } else {
+        ui_input_nonblocking(FALSE);
     }
+
     return ch;
 }
 
@@ -197,9 +201,34 @@ ui_replace_input(char *input, const char * const new_input, int *size)
 }
 
 void
-ui_input_nonblocking(void)
+ui_input_nonblocking(gboolean reset)
 {
-    inp_non_block();
+    static gint timeout = 0;
+    static gint no_input_count = 0;
+
+    if (! prefs_get_boolean(PREF_INPBLOCK_DYNAMIC)) {
+        inp_non_block(prefs_get_inpblock());
+        return;
+    }
+
+    if (reset) {
+        timeout = 0;
+        no_input_count = 0;
+    }
+
+    if (timeout < prefs_get_inpblock()) {
+        no_input_count++;
+
+        if (no_input_count % 10 == 0) {
+            timeout += no_input_count;
+
+            if (timeout > prefs_get_inpblock()) {
+                timeout = prefs_get_inpblock();
+            }
+        }
+    }
+
+    inp_non_block(timeout);
 }
 
 void
@@ -272,11 +301,19 @@ ui_handle_stanza(const char * const msg)
     }
 }
 
+gboolean
+ui_chat_win_exists(const char * const barejid)
+{
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    return (chatwin != NULL);
+}
+
 void
-ui_contact_typing(const char * const barejid)
+ui_contact_typing(const char * const barejid, const char * const resource)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     ProfWin *window = (ProfWin*) chatwin;
+    ChatSession *session = chat_session_get(barejid);
 
     if (prefs_get_boolean(PREF_INTYPE)) {
         // no chat window for user
@@ -287,8 +324,8 @@ ui_contact_typing(const char * const barejid)
         } else if (!wins_is_current(window)) {
             cons_show_typing(barejid);
 
-        // in chat window with user
-        } else {
+        // in chat window with user, no session or session with resource
+        } else if (!session || (session && g_strcmp0(session->resource, resource) == 0)) {
             title_bar_set_typing(TRUE);
 
             int num = wins_get_num(window);
@@ -328,20 +365,25 @@ ui_get_current_chat(void)
 }
 
 void
-ui_incoming_msg(const char * const barejid, const char * const message, GTimeVal *tv_stamp)
+ui_incoming_msg(const char * const barejid, const char * const resource, const char * const message, GTimeVal *tv_stamp)
 {
     gboolean win_created = FALSE;
-    char *display_from = NULL;
+    GString *user = g_string_new("");
 
     PContact contact = roster_get_contact(barejid);
     if (contact != NULL) {
         if (p_contact_name(contact) != NULL) {
-            display_from = strdup(p_contact_name(contact));
+            g_string_append(user, p_contact_name(contact));
         } else {
-            display_from = strdup(barejid);
+            g_string_append(user, barejid);
         }
     } else {
-        display_from = strdup(barejid);
+        g_string_append(user, barejid);
+    }
+
+    if (resource && prefs_get_boolean(PREF_RESOURCE_MESSAGE)) {
+        g_string_append(user, "/");
+        g_string_append(user, resource);
     }
 
     ProfChatWin *chatwin = wins_get_chat(barejid);
@@ -362,14 +404,14 @@ ui_incoming_msg(const char * const barejid, const char * const message, GTimeVal
 
     // currently viewing chat window with sender
     if (wins_is_current(window)) {
-        win_print_incoming_message(window, tv_stamp, display_from, message);
+        win_print_incoming_message(window, tv_stamp, user->str, message);
         title_bar_set_typing(FALSE);
         status_bar_active(num);
 
     // not currently viewing chat window with sender
     } else {
         status_bar_new(num);
-        cons_show_incoming_message(display_from, num);
+        cons_show_incoming_message(user->str, num);
 
         if (prefs_get_boolean(PREF_FLASH)) {
             flash();
@@ -388,7 +430,7 @@ ui_incoming_msg(const char * const barejid, const char * const message, GTimeVal
             }
         }
 
-        win_print_incoming_message(window, tv_stamp, display_from, message);
+        win_print_incoming_message(window, tv_stamp, user->str, message);
     }
 
     int ui_index = num;
@@ -404,14 +446,14 @@ ui_incoming_msg(const char * const barejid, const char * const message, GTimeVal
         gboolean is_current = wins_is_current(window);
         if ( !is_current || (is_current && prefs_get_boolean(PREF_NOTIFY_MESSAGE_CURRENT)) ) {
             if (prefs_get_boolean(PREF_NOTIFY_MESSAGE_TEXT)) {
-                notify_message(display_from, ui_index, message);
+                notify_message(user->str, ui_index, message);
             } else {
-                notify_message(display_from, ui_index, NULL);
+                notify_message(user->str, ui_index, NULL);
             }
         }
     }
 
-    free(display_from);
+    g_string_free(user, TRUE);
 }
 
 void
@@ -586,14 +628,6 @@ ui_update_presence(const resource_presence_t resource_presence,
 void
 ui_handle_recipient_not_found(const char * const recipient, const char * const err_msg)
 {
-    // unknown chat recipient
-    ProfChatWin *chatwin = wins_get_chat(recipient);
-    if (chatwin) {
-        cons_show_error("Recipient %s not found: %s", recipient, err_msg);
-        win_save_vprint((ProfWin*) chatwin, '!', NULL, 0, THEME_ERROR, "", "Recipient %s not found: %s", recipient, err_msg);
-        return;
-    }
-
     // intended recipient was invalid chat room
     ProfMucWin *mucwin = wins_get_muc(recipient);
     if (mucwin) {
@@ -601,17 +635,6 @@ ui_handle_recipient_not_found(const char * const recipient, const char * const e
         win_save_vprint((ProfWin*) mucwin, '!', NULL, 0, THEME_ERROR, "", "Room %s not found: %s", recipient, err_msg);
         return;
     }
-
-    // unknown private recipient
-    ProfPrivateWin *privatewin = wins_get_private(recipient);
-    if (privatewin) {
-        cons_show_error("Recipient %s not found: %s", recipient, err_msg);
-        win_save_vprint((ProfWin*) privatewin, '!', NULL, 0, THEME_ERROR, "", "Recipient %s not found: %s", recipient, err_msg);
-        return;
-    }
-
-    // no window
-    cons_show_error("Recipient %s not found: %s", recipient, err_msg);
 }
 
 void
@@ -705,7 +728,8 @@ ui_close_connected_win(int index)
                 otr_end_session(chatwin->barejid);
             }
 #endif
-            chat_session_on_window_close(chatwin->barejid);
+            chat_state_gone(chatwin->barejid, chatwin->state);
+            chat_session_remove(chatwin->barejid);
         }
     }
 }
@@ -945,8 +969,6 @@ ui_gone_secure(const char * const barejid, gboolean trusted)
         chatwin = (ProfChatWin*)window;
     }
 
-    FREE_SET_NULL(chatwin->resource);
-
     chatwin->is_otr = TRUE;
     chatwin->is_trusted = trusted;
     if (trusted) {
@@ -1162,7 +1184,7 @@ ui_prune_wins(void)
         if (window->type == WIN_CHAT) {
             if (conn_status == JABBER_CONNECTED) {
                 ProfChatWin *chatwin = (ProfChatWin*)window;
-                chat_session_on_window_close(chatwin->barejid);
+                chat_session_remove(chatwin->barejid);
             }
         }
 
@@ -1280,26 +1302,36 @@ ui_print_system_msg_from_recipient(const char * const barejid, const char *messa
 }
 
 void
-ui_recipient_gone(const char * const barejid)
+ui_recipient_gone(const char * const barejid, const char * const resource)
 {
     if (barejid == NULL)
         return;
+    if (resource == NULL)
+        return;
 
-    const char * display_usr = NULL;
-    PContact contact = roster_get_contact(barejid);
-    if (contact != NULL) {
-        if (p_contact_name(contact) != NULL) {
-            display_usr = p_contact_name(contact);
-        } else {
-            display_usr = barejid;
+    gboolean show_message = TRUE;
+
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    if (chatwin) {
+        ChatSession *session = chat_session_get(barejid);
+        if (session && g_strcmp0(session->resource, resource) != 0) {
+            show_message = FALSE;
         }
-    } else {
-        display_usr = barejid;
-    }
+        if (show_message) {
+            const char * display_usr = NULL;
+            PContact contact = roster_get_contact(barejid);
+            if (contact != NULL) {
+                if (p_contact_name(contact) != NULL) {
+                    display_usr = p_contact_name(contact);
+                } else {
+                    display_usr = barejid;
+                }
+            } else {
+                display_usr = barejid;
+            }
 
-    ProfWin *window = (ProfWin*)wins_get_chat(barejid);
-    if (window != NULL) {
-        win_save_vprint(window, '!', NULL, 0, THEME_GONE, "", "<- %s has left the conversation.", display_usr);
+            win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, THEME_GONE, "", "<- %s has left the conversation.", display_usr);
+        }
     }
 }
 
@@ -1381,9 +1413,9 @@ ui_outgoing_chat_msg(const char * const from, const char * const barejid,
     // create new window
     if (window == NULL) {
         window = wins_new_chat(barejid);
+        ProfChatWin *chatwin = (ProfChatWin*)window;
 #ifdef HAVE_LIBOTR
         if (otr_is_secure(barejid)) {
-            ProfChatWin *chatwin = (ProfChatWin*)window;
             chatwin->is_otr = TRUE;
         }
 #endif
@@ -1405,6 +1437,8 @@ ui_outgoing_chat_msg(const char * const from, const char * const barejid,
     } else {
         num = wins_get_num(window);
     }
+    ProfChatWin *chatwin = (ProfChatWin*)window;
+    chat_state_active(chatwin->state);
 
     win_save_print(window, '-', NULL, 0, THEME_TEXT_ME, from, message);
     ui_switch_win(num);
@@ -2213,7 +2247,7 @@ ui_ask_password(void)
   status_bar_update_virtual();
   inp_block();
   inp_get_password(passwd);
-  inp_non_block();
+  inp_non_block(prefs_get_inpblock());
 
   return passwd;
 }
@@ -2251,12 +2285,59 @@ ui_chat_win_contact_offline(PContact contact, char *resource, char *status)
 }
 
 void
+ui_contact_offline(char *barejid, char *resource, char *status)
+{
+    char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
+    char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
+    Jid *jid = jid_create_from_bare_and_resource(barejid, resource);
+    PContact contact = roster_get_contact(barejid);
+    if (p_contact_subscription(contact) != NULL) {
+        if (strcmp(p_contact_subscription(contact), "none") != 0) {
+
+            // show in console if "all"
+            if (g_strcmp0(show_console, "all") == 0) {
+                cons_show_contact_offline(contact, resource, status);
+
+            // show in console of "online"
+            } else if (g_strcmp0(show_console, "online") == 0) {
+                cons_show_contact_offline(contact, resource, status);
+            }
+
+            // show in chat win if "all"
+            if (g_strcmp0(show_chat_win, "all") == 0) {
+                ui_chat_win_contact_offline(contact, resource, status);
+
+            // show in char win if "online" and presence online
+            } else if (g_strcmp0(show_chat_win, "online") == 0) {
+                ui_chat_win_contact_offline(contact, resource, status);
+            }
+        }
+    }
+
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    if (chatwin && chatwin->resource_override && (g_strcmp0(resource, chatwin->resource_override) == 0)) {
+        FREE_SET_NULL(chatwin->resource_override);
+    }
+
+    prefs_free_string(show_console);
+    prefs_free_string(show_chat_win);
+    jid_destroy(jid);
+}
+
+void
 ui_clear_win_title(void)
 {
     printf("%c]0;%c", '\033', '\007');
 }
 
 void
+ui_goodbye_title(void)
+{
+    int result = system("/bin/echo -ne \"\033]0;Thanks for using Profanity\007\"");
+    if(result == -1) log_error("Error printing title on shutdown");
+}
+
+void
 ui_statusbar_new(const int win)
 {
     status_bar_new(win);
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index 4165bd3f..a6877a86 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -120,9 +120,9 @@ inp_win_resize(void)
 }
 
 void
-inp_non_block(void)
+inp_non_block(gint timeout)
 {
-    wtimeout(inp_win, prefs_get_inpblock());
+    wtimeout(inp_win, timeout);
 }
 
 void
@@ -151,17 +151,11 @@ inp_get_char(char *input, int *size, int *result)
         in_command = TRUE;
     }
 
-    if (prefs_get_boolean(PREF_STATES)) {
-        if (*result == ERR) {
-            prof_handle_idle();
-        }
-        if (prefs_get_boolean(PREF_OUTTYPE)
-                && (*result != ERR)
-                && (*result != KEY_CODE_YES)
-                && !in_command
-                && _printable(ch)) {
-            prof_handle_activity();
-        }
+    if (*result == ERR) {
+        prof_handle_idle();
+    }
+    if ((*result != ERR) && (*result != KEY_CODE_YES) && !in_command && _printable(ch)) {
+        prof_handle_activity();
     }
 
     // if it wasn't an arrow key etc
diff --git a/src/ui/inputwin.h b/src/ui/inputwin.h
index eae20a51..b5a26c10 100644
--- a/src/ui/inputwin.h
+++ b/src/ui/inputwin.h
@@ -40,9 +40,9 @@ wint_t inp_get_char(char *input, int *size, int *result);
 void inp_win_reset(void);
 void inp_win_resize(void);
 void inp_put_back(void);
-void inp_non_block(void);
+void inp_non_block(gint);
 void inp_block(void);
 void inp_get_password(char *passwd);
 void inp_replace_input(char *input, const char * const new_input, int *size);
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index 15f8efca..326dbf8b 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -47,6 +47,7 @@
 #include "ui/windows.h"
 #include "ui/window.h"
 #include "roster_list.h"
+#include "chat_session.h"
 
 static WINDOW *win;
 static contact_presence_t current_presence;
@@ -307,9 +308,17 @@ static void
 _show_contact_presence(ProfChatWin *chatwin)
 {
     int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
-    if (chatwin && chatwin->resource) {
+    char *resource = NULL;
+
+    ChatSession *session = chat_session_get(chatwin->barejid);
+    if (chatwin->resource_override) {
+        resource = chatwin->resource_override;
+    } else if (session && session->resource) {
+        resource = session->resource;
+    }
+    if (resource && prefs_get_boolean(PREF_RESOURCE_TITLE)) {
         wprintw(win, "/");
-        wprintw(win, chatwin->resource);
+        wprintw(win, resource);
     }
 
     if (prefs_get_boolean(PREF_PRESENCE)) {
@@ -318,10 +327,10 @@ _show_contact_presence(ProfChatWin *chatwin)
 
         PContact contact = roster_get_contact(chatwin->barejid);
         if (contact) {
-            if (chatwin && chatwin->resource) {
-                Resource *resource = p_contact_get_resource(contact, chatwin->resource);
-                if (resource) {
-                    presence = string_from_resource_presence(resource->presence);
+            if (resource) {
+                Resource *resourcep = p_contact_get_resource(contact, resource);
+                if (resourcep) {
+                    presence = string_from_resource_presence(resourcep->presence);
                 }
             } else {
                 presence = p_contact_presence(contact);
diff --git a/src/ui/ui.h b/src/ui/ui.h
index e7b46766..e28914ff 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -115,12 +115,12 @@ char * ui_ask_password(void);
 void ui_handle_stanza(const char * const msg);
 
 // ui events
-void ui_contact_typing(const char * const from);
-void ui_incoming_msg(const char * const from, const char * const message, GTimeVal *tv_stamp);
+void ui_contact_typing(const char * const barejid, const char * const resource);
+void ui_incoming_msg(const char * const from, const char * const resource,  const char * const message, GTimeVal *tv_stamp);
 void ui_incoming_private_msg(const char * const fulljid, const char * const message, GTimeVal *tv_stamp);
 
 void ui_disconnected(void);
-void ui_recipient_gone(const char * const barejid);
+void ui_recipient_gone(const char * const barejid, const char * const resource);
 
 void ui_outgoing_chat_msg(const char * const from, const char * const barejid,
     const char * const message);
@@ -186,10 +186,12 @@ void ui_group_added(const char * const contact, const char * const group);
 void ui_group_removed(const char * const contact, const char * const group);
 void ui_chat_win_contact_online(PContact contact, Resource *resource, GDateTime *last_activity);
 void ui_chat_win_contact_offline(PContact contact, char *resource, char *status);
+void ui_contact_offline(char *barejid, char *resource, char *status);
 void ui_handle_recipient_not_found(const char * const recipient, const char * const err_msg);
 void ui_handle_recipient_error(const char * const recipient, const char * const err_msg);
 void ui_handle_error(const char * const err_msg);
 void ui_clear_win_title(void);
+void ui_goodbye_title(void);
 void ui_handle_room_join_error(const char * const roomjid, const char * const err);
 void ui_handle_room_configuration(const char * const roomjid, DataForm *form);
 void ui_handle_room_configuration_form_error(const char * const roomjid, const char * const message);
@@ -213,6 +215,7 @@ void ui_show_lines(ProfWin *window, const gchar** lines);
 void ui_redraw_all_room_rosters(void);
 void ui_show_all_room_rosters(void);
 void ui_hide_all_room_rosters(void);
+gboolean ui_chat_win_exists(const char * const barejid);
 
 void ui_tidy_wins(void);
 void ui_prune_wins(void);
@@ -229,7 +232,7 @@ void ui_statusbar_new(const int win);
 
 wint_t ui_get_char(char *input, int *size, int *result);
 void ui_input_clear(void);
-void ui_input_nonblocking(void);
+void ui_input_nonblocking(gboolean);
 void ui_replace_input(char *input, const char * const new_input, int *size);
 
 void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void));
@@ -286,6 +289,7 @@ void cons_show_received_subs(void);
 void cons_show_sent_subs(void);
 void cons_alert(void);
 void cons_theme_setting(void);
+void cons_resource_setting(void);
 void cons_privileges_setting(void);
 void cons_beep_setting(void);
 void cons_flash_setting(void);
diff --git a/src/ui/window.c b/src/ui/window.c
index dd459ece..3a45ab01 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -132,11 +132,12 @@ win_create_chat(const char * const barejid)
     new_win->window.layout = _win_create_simple_layout();
 
     new_win->barejid = strdup(barejid);
-    new_win->resource = NULL;
+    new_win->resource_override = NULL;
     new_win->is_otr = FALSE;
     new_win->is_trusted = FALSE;
     new_win->history_shown = FALSE;
     new_win->unread = 0;
+    new_win->state = chat_state_new();
 
     new_win->memcheck = PROFCHATWIN_MEMCHECK;
 
@@ -333,7 +334,8 @@ win_free(ProfWin* window)
     if (window->type == WIN_CHAT) {
         ProfChatWin *chatwin = (ProfChatWin*)window;
         free(chatwin->barejid);
-        free(chatwin->resource);
+        free(chatwin->resource_override);
+        free(chatwin->state);
     }
 
     if (window->type == WIN_MUC) {
@@ -738,6 +740,8 @@ win_save_print(ProfWin *window, const char show_char, GTimeVal *tstamp,
 
     buffer_push(window->layout->buffer, show_char, time, flags, theme_item, from, message);
     _win_print(window, show_char, time, flags, theme_item, from, message);
+    // TODO: cross-reference.. this should be replaced by a real event-based system
+    ui_input_nonblocking(TRUE);
 }
 
 void
@@ -950,4 +954,4 @@ win_printline_nowrap(WINDOW *win, char *msg)
     waddnstr(win, msg, maxx);
 
     wmove(win, cury+1, 0);
-}
\ No newline at end of file
+}
diff --git a/src/ui/window.h b/src/ui/window.h
index 0f04faae..b6bd0298 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -47,6 +47,7 @@
 #include "muc.h"
 #include "ui/buffer.h"
 #include "xmpp/xmpp.h"
+#include "chat_state.h"
 
 #define NO_ME           1
 #define NO_DATE         2
@@ -109,9 +110,10 @@ typedef struct prof_chat_win_t {
     ProfWin window;
     char *barejid;
     int unread;
+    ChatState *state;
     gboolean is_otr;
     gboolean is_trusted;
-    char *resource;
+    char *resource_override;
     gboolean history_shown;
     unsigned long memcheck;
 } ProfChatWin;
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index d87c5fb2..e96c1a74 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -80,27 +80,31 @@ message_add_handlers(void)
 }
 
 void
-message_send_chat(const char * const barejid, const char * const resource, const char * const msg, gboolean send_state)
+message_send_chat(const char * const barejid, const char * const msg)
 {
     xmpp_stanza_t *message;
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
 
-    GString *jid = g_string_new(barejid);
-    if (resource) {
-        g_string_append(jid, "/");
-        g_string_append(jid, resource);
-    }
-
-    if (send_state) {
-        message = stanza_create_message(ctx, jid->str, STANZA_TYPE_CHAT, msg, STANZA_NAME_ACTIVE);
+    ChatSession *session = chat_session_get(barejid);
+    if (session) {
+        char *state = NULL;
+        if (prefs_get_boolean(PREF_STATES) && session->send_states) {
+            state = STANZA_NAME_ACTIVE;
+        }
+        Jid *jidp = jid_create_from_bare_and_resource(session->barejid, session->resource);
+        message = stanza_create_message(ctx, jidp->fulljid, STANZA_TYPE_CHAT, msg, state);
+        jid_destroy(jidp);
     } else {
-        message = stanza_create_message(ctx, jid->str, STANZA_TYPE_CHAT, msg, NULL);
+        char *state = NULL;
+        if (prefs_get_boolean(PREF_STATES)) {
+            state = STANZA_NAME_ACTIVE;
+        }
+        message = stanza_create_message(ctx, barejid, STANZA_TYPE_CHAT, msg, state);
     }
 
     xmpp_send(conn, message);
     xmpp_stanza_release(message);
-    g_string_free(jid, TRUE);
 }
 
 void
@@ -149,49 +153,44 @@ message_send_invite(const char * const roomjid, const char * const contact,
 }
 
 void
-message_send_composing(const char * const barejid)
+message_send_composing(const char * const jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, barejid,
-        STANZA_NAME_COMPOSING);
 
+    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_COMPOSING);
     xmpp_send(conn, stanza);
     xmpp_stanza_release(stanza);
+
 }
 
 void
-message_send_paused(const char * const barejid)
+message_send_paused(const char * const jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, barejid,
-        STANZA_NAME_PAUSED);
-
+    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_PAUSED);
     xmpp_send(conn, stanza);
     xmpp_stanza_release(stanza);
 }
 
 void
-message_send_inactive(const char * const barejid)
+message_send_inactive(const char * const jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, barejid,
-        STANZA_NAME_INACTIVE);
+    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_INACTIVE);
 
     xmpp_send(conn, stanza);
     xmpp_stanza_release(stanza);
 }
 
 void
-message_send_gone(const char * const barejid)
+message_send_gone(const char * const jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, barejid,
-        STANZA_NAME_GONE);
-
+    xmpp_stanza_t *stanza = stanza_create_chat_state(ctx, jid, STANZA_NAME_GONE);
     xmpp_send(conn, stanza);
     xmpp_stanza_release(stanza);
 }
@@ -462,34 +461,10 @@ _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
     // standard chat message, use jid without resource
     } else {
-        // determine chatstate support of recipient
-        gboolean recipient_supports = FALSE;
-        if (stanza_contains_chat_state(stanza)) {
-            recipient_supports = TRUE;
-        }
-
-        // create or update chat session
-        chat_session_on_incoming_message(jid->barejid, recipient_supports);
-
         // determine if the notifications happened whilst offline
         GTimeVal tv_stamp;
         gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
 
-        // deal with chat states if recipient supports them
-        if (recipient_supports && (!delayed)) {
-            if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_COMPOSING) != NULL) {
-                handle_typing(jid->barejid);
-            } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_GONE) != NULL) {
-                handle_gone(jid->barejid);
-            } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PAUSED) != NULL) {
-                // do something
-            } else if (xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_INACTIVE) != NULL) {
-                // do something
-            } else { // handle <active/>
-                // do something
-            }
-        }
-
         // check for and deal with message
         xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
         if (body != NULL) {
@@ -498,12 +473,33 @@ _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
                 if (delayed) {
                     handle_delayed_message(jid->barejid, message, tv_stamp);
                 } else {
-                    handle_incoming_message(jid->barejid, message);
+                    handle_incoming_message(jid->barejid, jid->resourcepart, message);
                 }
                 xmpp_free(ctx, message);
             }
         }
 
+        // handle chat sessions and states
+        if (!delayed && jid->resourcepart) {
+            gboolean gone = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_GONE) != NULL;
+            gboolean typing = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_COMPOSING) != NULL;
+            gboolean paused = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PAUSED) != NULL;
+            gboolean inactive = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_INACTIVE) != NULL;
+            if (gone) {
+                handle_gone(jid->barejid, jid->resourcepart);
+            } else if (typing) {
+                handle_typing(jid->barejid, jid->resourcepart);
+            } else if (paused) {
+                handle_paused(jid->barejid, jid->resourcepart);
+            } else if (inactive) {
+                handle_inactive(jid->barejid, jid->resourcepart);
+            } else if (stanza_contains_chat_state(stanza)) {
+                handle_activity(jid->barejid, jid->resourcepart, TRUE);
+            } else {
+                handle_activity(jid->barejid, jid->resourcepart, FALSE);
+            }
+        }
+
         jid_destroy(jid);
         return 1;
     }
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 1766fe26..4f1d412d 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -199,15 +199,14 @@ stanza_create_bookmarks_pubsub_add(xmpp_ctx_t *ctx, const char * const jid,
 #endif
 
 xmpp_stanza_t *
-stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const recipient,
-    const char * const state)
+stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const fulljid, const char * const state)
 {
     xmpp_stanza_t *msg, *chat_state;
 
     msg = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(msg, STANZA_NAME_MESSAGE);
     xmpp_stanza_set_type(msg, STANZA_TYPE_CHAT);
-    xmpp_stanza_set_attribute(msg, STANZA_ATTR_TO, recipient);
+    xmpp_stanza_set_attribute(msg, STANZA_ATTR_TO, fulljid);
     char *id = create_unique_id(NULL);
     xmpp_stanza_set_id(msg, id);
     free(id);
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index e60e9fe1..84282401 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -179,7 +179,7 @@ typedef enum {
 xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx);
 
 xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx,
-    const char * const recipient, const char * const state);
+    const char * const fulljid, const char * const state);
 
 xmpp_stanza_t* stanza_create_message(xmpp_ctx_t *ctx,
     const char * const recipient, const char * const type,
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 2499d008..161eebdf 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -145,16 +145,15 @@ char* jabber_get_account_name(void);
 GList * jabber_get_available_resources(void);
 
 // message functions
-void message_send_chat(const char * const barejid, const char * const resource, const char * const msg,
-    gboolean send_state);
+void message_send_chat(const char * const barejid, const char * const msg);
 void message_send_private(const char * const fulljid, const char * const msg);
 void message_send_groupchat(const char * const roomjid, const char * const msg);
 void message_send_groupchat_subject(const char * const roomjid, const char * const subject);
 
-void message_send_inactive(const char * const barejid);
-void message_send_composing(const char * const barejid);
-void message_send_paused(const char * const barejid);
-void message_send_gone(const char * const barejid);
+void message_send_inactive(const char * const jid);
+void message_send_composing(const char * const jid);
+void message_send_paused(const char * const jid);
+void message_send_gone(const char * const jid);
 
 void message_send_invite(const char * const room, const char * const contact,
     const char * const reason);
diff --git a/tests/helpers.c b/tests/helpers.c
index a6a473e4..10310886 100644
--- a/tests/helpers.c
+++ b/tests/helpers.c
@@ -10,6 +10,7 @@
 #include "common.h"
 #include "helpers.h"
 #include "config/preferences.h"
+#include "chat_session.h"
 
 void create_config_dir(void **state)
 {
@@ -72,6 +73,18 @@ void close_preferences(void **state)
     rmdir("./tests/files");
 }
 
+void init_chat_sessions(void **state)
+{
+    load_preferences(NULL);
+    chat_sessions_init();
+}
+
+void close_chat_sessions(void **state)
+{
+    chat_sessions_clear();
+    close_preferences(NULL);
+}
+
 static GCompareFunc cmp_func;
 
 void
diff --git a/tests/helpers.h b/tests/helpers.h
index 17d8329c..2d7af6e7 100644
--- a/tests/helpers.h
+++ b/tests/helpers.h
@@ -3,5 +3,8 @@
 void load_preferences(void **state);
 void close_preferences(void **state);
 
+void init_chat_sessions(void **state);
+void close_chat_sessions(void **state);
+
 void glist_set_cmp(GCompareFunc func);
 int glist_contents_equal(const void *actual, const void *expected);
\ No newline at end of file
diff --git a/tests/test_chat_session.c b/tests/test_chat_session.c
new file mode 100644
index 00000000..b5e1f7b6
--- /dev/null
+++ b/tests/test_chat_session.c
@@ -0,0 +1,51 @@
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "chat_session.h"
+
+void returns_false_when_chat_session_does_not_exist(void **state)
+{
+    ChatSession *session = chat_session_get("somejid@server.org");
+    assert_null(session);
+}
+
+void creates_chat_session_on_recipient_activity(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource = "tablet";
+
+    chat_session_recipient_active(barejid, resource, FALSE);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_non_null(session);
+    assert_string_equal(session->resource, resource);
+}
+
+void replaces_chat_session_on_recipient_activity_with_different_resource(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource1 = "tablet";
+    char *resource2 = "mobile";
+
+    chat_session_recipient_active(barejid, resource1, FALSE);
+    chat_session_recipient_active(barejid, resource2, FALSE);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_string_equal(session->resource, resource2);
+}
+
+void removes_chat_session(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource1 = "laptop";
+
+    chat_session_recipient_active(barejid, resource1, FALSE);
+    chat_session_remove(barejid);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_null(session);
+}
\ No newline at end of file
diff --git a/tests/test_chat_session.h b/tests/test_chat_session.h
new file mode 100644
index 00000000..4ce03fd5
--- /dev/null
+++ b/tests/test_chat_session.h
@@ -0,0 +1,4 @@
+void returns_false_when_chat_session_does_not_exist(void **state);
+void creates_chat_session_on_recipient_activity(void **state);
+void replaces_chat_session_on_recipient_activity_with_different_resource(void **state);
+void removes_chat_session(void **state);
\ No newline at end of file
diff --git a/tests/test_cmd_disconnect.c b/tests/test_cmd_disconnect.c
new file mode 100644
index 00000000..68253820
--- /dev/null
+++ b/tests/test_cmd_disconnect.c
@@ -0,0 +1,37 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "chat_session.h"
+#include "command/commands.h"
+#include "xmpp/xmpp.h"
+#include "roster_list.h"
+
+#include "ui/stub_ui.h"
+
+void clears_chat_sessions(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    chat_sessions_init();
+    roster_init();
+    chat_session_recipient_active("bob@server.org", "laptop", FALSE);
+    chat_session_recipient_active("mike@server.org", "work", FALSE);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_fulljid, "myjid@myserver.com");
+    expect_any_cons_show();
+
+    gboolean result = cmd_disconnect(NULL, *help);
+
+    assert_true(result);
+
+    ChatSession *session1 = chat_session_get("bob@server.org");
+    ChatSession *session2 = chat_session_get("mike@server.org");
+    assert_null(session1);
+    assert_null(session2);
+    free(help);
+}
\ No newline at end of file
diff --git a/tests/test_cmd_disconnect.h b/tests/test_cmd_disconnect.h
new file mode 100644
index 00000000..856b501e
--- /dev/null
+++ b/tests/test_cmd_disconnect.h
@@ -0,0 +1 @@
+void clears_chat_sessions(void **state);
diff --git a/tests/test_cmd_otr.c b/tests/test_cmd_otr.c
index 470615e6..c6c6f7cf 100644
--- a/tests/test_cmd_otr.c
+++ b/tests/test_cmd_otr.c
@@ -552,9 +552,7 @@ cmd_otr_start_sends_otr_query_message_to_current_recipeint(void **state)
     will_return(otr_start_query, query_message);
 
     expect_string(message_send_chat, barejid, chatwin->barejid);
-    expect_value(message_send_chat, resource, NULL);
     expect_string(message_send_chat, msg, query_message);
-    expect_any(message_send_chat, send_state);
 
     gboolean result = cmd_otr(args, *help);
     assert_true(result);
diff --git a/tests/test_server_events.c b/tests/test_server_events.c
index 435493a2..ce54b4f2 100644
--- a/tests/test_server_events.c
+++ b/tests/test_server_events.c
@@ -11,6 +11,7 @@
 #include "chat_session.h"
 #include "config/preferences.h"
 #include "ui/ui.h"
+#include "ui/stub_ui.h"
 #include "muc.h"
 
 void console_doesnt_show_online_presence_when_set_none(void **state)
@@ -120,29 +121,24 @@ void handle_message_error_when_recipient_cancel(void **state)
     prefs_set_boolean(PREF_STATES, FALSE);
     chat_sessions_init();
 
-    expect_string(ui_handle_recipient_not_found, recipient, from);
-    expect_string(ui_handle_recipient_not_found, err_msg, err_msg);
-
     handle_message_error(from, type, err_msg);
 }
 
 void handle_message_error_when_recipient_cancel_disables_chat_session(void **state)
 {
     char *err_msg = "Some error.";
-    char *from = "bob@server.com";
+    char *barejid = "bob@server.com";
+    char *resource = "resource";
     char *type = "cancel";
 
     prefs_set_boolean(PREF_STATES, TRUE);
     chat_sessions_init();
-    chat_session_on_incoming_message(from, TRUE);
+    chat_session_recipient_active(barejid, resource, FALSE);
 
-    expect_any(ui_handle_recipient_not_found, recipient);
-    expect_any(ui_handle_recipient_not_found, err_msg);
+    handle_message_error(barejid, type, err_msg);
+    ChatSession *session = chat_session_get(barejid);
 
-    handle_message_error(from, type, err_msg);
-    gboolean chat_session_supported = chat_session_on_message_send(from);
-
-    assert_false(chat_session_supported);
+    assert_null(session);
     chat_sessions_clear();
 }
 
@@ -180,3 +176,37 @@ void handle_presence_error_when_from_recipient(void **state)
 
     handle_presence_error(from, type, err_msg);
 }
+
+void handle_offline_removes_chat_session(void **state)
+{
+    chat_sessions_init();
+    char *barejid = "friend@server.chat.com";
+    char *resource = "home";
+    roster_init();
+    roster_add(barejid, "bob", NULL, "both", FALSE);
+    Resource *resourcep = resource_new(resource, RESOURCE_ONLINE, NULL, 10);
+    roster_update_presence(barejid, resourcep, NULL);
+    chat_session_recipient_active(barejid, resource, FALSE);
+    handle_contact_offline(barejid, resource, NULL);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_null(session);
+
+    roster_clear();
+    chat_sessions_clear();
+}
+
+void lost_connection_clears_chat_sessions(void **state)
+{
+    chat_sessions_init();
+    chat_session_recipient_active("bob@server.org", "laptop", FALSE);
+    chat_session_recipient_active("steve@server.org", "mobile", FALSE);
+    expect_any_cons_show_error();
+
+    handle_lost_connection();
+
+    ChatSession *session1 = chat_session_get("bob@server.org");
+    ChatSession *session2 = chat_session_get("steve@server.org");
+    assert_null(session1);
+    assert_null(session2);
+}
diff --git a/tests/test_server_events.h b/tests/test_server_events.h
index 68e78557..81a436f4 100644
--- a/tests/test_server_events.h
+++ b/tests/test_server_events.h
@@ -9,4 +9,6 @@ void handle_message_error_when_recipient_cancel(void **state);
 void handle_message_error_when_recipient_cancel_disables_chat_session(void **state);
 void handle_message_error_when_recipient_and_no_type(void **state);
 void handle_presence_error_when_no_recipient(void **state);
-void handle_presence_error_when_from_recipient(void **state);
\ No newline at end of file
+void handle_presence_error_when_from_recipient(void **state);
+void handle_offline_removes_chat_session(void **state);
+void lost_connection_clears_chat_sessions(void **state);
diff --git a/tests/testsuite.c b/tests/testsuite.c
index fb439331..eb039cbe 100644
--- a/tests/testsuite.c
+++ b/tests/testsuite.c
@@ -8,9 +8,11 @@
 #include <sys/stat.h>
 
 #include "config.h"
+#include "chat_session.h"
 
 #include "helpers.h"
 #include "test_autocomplete.h"
+#include "test_chat_session.h"
 #include "test_common.h"
 #include "test_contact.h"
 #include "test_cmd_connect.h"
@@ -31,6 +33,7 @@
 #include "test_muc.h"
 #include "test_cmd_roster.h"
 #include "test_cmd_win.h"
+#include "test_cmd_disconnect.h"
 #include "test_form.h"
 
 int main(int argc, char* argv[]) {
@@ -204,6 +207,18 @@ int main(int argc, char* argv[]) {
         unit_test(find_five_times_finds_fifth),
         unit_test(find_twice_returns_first_when_two_match_and_reset),
 
+        unit_test_setup_teardown(returns_false_when_chat_session_does_not_exist,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(creates_chat_session_on_recipient_activity,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(replaces_chat_session_on_recipient_activity_with_different_resource,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(removes_chat_session,
+            init_chat_sessions,
+            close_chat_sessions),
         unit_test_setup_teardown(cmd_connect_shows_message_when_disconnecting,
             load_preferences,
             close_preferences),
@@ -439,6 +454,8 @@ int main(int argc, char* argv[]) {
         unit_test(handle_message_error_when_recipient_and_no_type),
         unit_test(handle_presence_error_when_no_recipient),
         unit_test(handle_presence_error_when_from_recipient),
+        unit_test(handle_offline_removes_chat_session),
+        unit_test(lost_connection_clears_chat_sessions),
 
         unit_test(cmd_alias_add_shows_usage_when_no_args),
         unit_test(cmd_alias_add_shows_usage_when_no_value),
@@ -594,6 +611,8 @@ int main(int argc, char* argv[]) {
         unit_test(remove_text_multi_value_does_nothing_when_doesnt_exist),
         unit_test(remove_text_multi_value_removes_when_one),
         unit_test(remove_text_multi_value_removes_when_many),
+
+        unit_test(clears_chat_sessions),
     };
 
     return run_tests(all_tests);
diff --git a/tests/ui/stub_ui.c b/tests/ui/stub_ui.c
index 5361c08d..76b71265 100644
--- a/tests/ui/stub_ui.c
+++ b/tests/ui/stub_ui.c
@@ -20,12 +20,24 @@ expect_cons_show(char *expected)
 }
 
 void
+expect_any_cons_show(void)
+{
+    expect_any(cons_show, output);
+}
+
+void
 expect_cons_show_error(char *expected)
 {
     expect_string(cons_show_error, output, expected);
 }
 
 void
+expect_any_cons_show_error(void)
+{
+    expect_any(cons_show_error, output);
+}
+
+void
 expect_ui_current_print_line(char *message)
 {
     expect_string(ui_current_print_line, output, message);
@@ -176,12 +188,12 @@ char * ui_ask_password(void)
 void ui_handle_stanza(const char * const msg) {}
 
 // ui events
-void ui_contact_typing(const char * const from) {}
-void ui_incoming_msg(const char * const from, const char * const message, GTimeVal *tv_stamp) {}
+void ui_contact_typing(const char * const barejid, const char * const resource) {}
+void ui_incoming_msg(const char * const from, const char * const resource, const char * const message, GTimeVal *tv_stamp) {}
 void ui_incoming_private_msg(const char * const fulljid, const char * const message, GTimeVal *tv_stamp) {}
 
 void ui_disconnected(void) {}
-void ui_recipient_gone(const char * const barejid) {}
+void ui_recipient_gone(const char * const barejid, const char * const resource) {}
 
 void ui_outgoing_chat_msg(const char * const from, const char * const barejid,
     const char * const message) {}
@@ -247,6 +259,12 @@ void ui_group_added(const char * const contact, const char * const group) {}
 void ui_group_removed(const char * const contact, const char * const group) {}
 void ui_chat_win_contact_online(PContact contact, Resource *resource, GDateTime *last_activity) {}
 void ui_chat_win_contact_offline(PContact contact, char *resource, char *status) {}
+gboolean ui_chat_win_exists(const char * const barejid)
+{
+    return TRUE;
+}
+
+void ui_contact_offline(char *barejid, char *resource, char *status) {}
 
 void ui_handle_recipient_not_found(const char * const recipient, const char * const err_msg)
 {
@@ -266,6 +284,7 @@ void ui_handle_error(const char * const err_msg)
 }
 
 void ui_clear_win_title(void) {}
+void ui_goodbye_title(void) {}
 void ui_handle_room_join_error(const char * const roomjid, const char * const err) {}
 void ui_handle_room_configuration(const char * const roomjid, DataForm *form) {}
 void ui_handle_room_configuration_form_error(const char * const roomjid, const char * const message) {}
@@ -312,7 +331,7 @@ wint_t ui_get_char(char *input, int *size, int *result)
 }
 
 void ui_input_clear(void) {}
-void ui_input_nonblocking(void) {}
+void ui_input_nonblocking(gboolean reset) {}
 void ui_replace_input(char *input, const char * const new_input, int *size) {}
 
 void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void)) {}
@@ -424,6 +443,7 @@ void cons_beep_setting(void) {}
 void cons_flash_setting(void) {}
 void cons_splash_setting(void) {}
 void cons_vercheck_setting(void) {}
+void cons_resource_setting(void) {}
 void cons_occupants_setting(void) {}
 void cons_roster_setting(void) {}
 void cons_presence_setting(void) {}
diff --git a/tests/ui/stub_ui.h b/tests/ui/stub_ui.h
index f64eba02..81357a86 100644
--- a/tests/ui/stub_ui.h
+++ b/tests/ui/stub_ui.h
@@ -1,4 +1,6 @@
 void expect_cons_show(char *expected);
+void expect_any_cons_show(void);
 void expect_cons_show_error(char *expected);
+void expect_any_cons_show_error(void);
 void expect_ui_current_print_line(char *message);
 void expect_ui_current_print_formatted_line(char show_char, int attrs, char *message);
\ No newline at end of file
diff --git a/tests/xmpp/stub_xmpp.c b/tests/xmpp/stub_xmpp.c
index 580a6c61..cc5ad5fc 100644
--- a/tests/xmpp/stub_xmpp.c
+++ b/tests/xmpp/stub_xmpp.c
@@ -29,7 +29,7 @@ void jabber_shutdown(void) {}
 void jabber_process_events(void) {}
 const char * jabber_get_fulljid(void)
 {
-    return NULL;
+    return (char *)mock();
 }
 
 const char * jabber_get_domain(void)
@@ -58,13 +58,10 @@ GList * jabber_get_available_resources(void)
 }
 
 // message functions
-void message_send_chat(const char * const barejid, const char * const resource, const char * const msg,
-    gboolean send_state)
+void message_send_chat(const char * const barejid, const char * const msg)
 {
     check_expected(barejid);
-    check_expected(resource);
     check_expected(msg);
-    check_expected(send_state);
 }
 
 void message_send_private(const char * const fulljid, const char * const msg) {}
@@ -218,4 +215,4 @@ void roster_send_add_new(const char * const barejid, const char * const name)
 void roster_send_remove(const char * const barejid)
 {
     check_expected(barejid);
-}
\ No newline at end of file
+}