about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorWill Song <incertia9474@gmail.com>2015-05-29 19:53:37 -0500
committerWill Song <incertia9474@gmail.com>2015-05-29 19:53:37 -0500
commit9463c6719036957c8468e1363a48afbfe751fd3e (patch)
treeaed6ccd050fc182a0d4d111bef6f743d28a42a73
parent7f436d614b11d72893d856f1c3d817ab34b0b9eb (diff)
parent304e08a9c0bfa27ed84dc1ff0a2e2d32b36529f8 (diff)
downloadprofani-tty-9463c6719036957c8468e1363a48afbfe751fd3e.tar.gz
fix conflicts
-rw-r--r--.gitignore7
-rw-r--r--.travis.yml10
-rw-r--r--CHANGELOG32
-rw-r--r--LICENSE.txt2
-rw-r--r--Makefile.am18
-rw-r--r--configure.ac21
-rw-r--r--docs/profanity.126
-rwxr-xr-xinstall-all.sh10
-rw-r--r--src/chat_session.c8
-rw-r--r--src/chat_session.h2
-rw-r--r--src/chat_state.c2
-rw-r--r--src/chat_state.h2
-rw-r--r--src/command/command.c1439
-rw-r--r--src/command/command.h4
-rw-r--r--src/command/commands.c975
-rw-r--r--src/command/commands.h7
-rw-r--r--src/common.c93
-rw-r--r--src/common.h12
-rw-r--r--src/config/account.c58
-rw-r--r--src/config/account.h5
-rw-r--r--src/config/accounts.c38
-rw-r--r--src/config/accounts.h2
-rw-r--r--src/config/preferences.c76
-rw-r--r--src/config/preferences.h7
-rw-r--r--src/config/theme.c172
-rw-r--r--src/config/theme.h3
-rw-r--r--src/contact.c53
-rw-r--r--src/contact.h4
-rw-r--r--src/event/client_events.c95
-rw-r--r--src/event/client_events.h47
-rw-r--r--src/event/server_events.c522
-rw-r--r--src/event/server_events.h91
-rw-r--r--src/event/ui_events.c56
-rw-r--r--src/event/ui_events.h (renamed from src/tools/history.h)19
-rw-r--r--src/jid.c21
-rw-r--r--src/jid.h2
-rw-r--r--src/log.c104
-rw-r--r--src/log.h12
-rw-r--r--src/main.c17
-rw-r--r--src/muc.c63
-rw-r--r--src/muc.h16
-rw-r--r--src/otr/otr.c117
-rw-r--r--src/otr/otr.h9
-rw-r--r--src/otr/otrlib.h2
-rw-r--r--src/otr/otrlibv3.c8
-rw-r--r--src/otr/otrlibv4.c61
-rw-r--r--src/profanity.c49
-rw-r--r--src/profanity.h2
-rw-r--r--src/resource.c6
-rw-r--r--src/resource.h2
-rw-r--r--src/roster_list.c96
-rw-r--r--src/roster_list.h3
-rw-r--r--src/server_events.c822
-rw-r--r--src/server_events.h125
-rw-r--r--src/tools/autocomplete.c2
-rw-r--r--src/tools/autocomplete.h2
-rw-r--r--src/tools/history.c283
-rw-r--r--src/tools/parser.c20
-rw-r--r--src/tools/parser.h2
-rw-r--r--src/tools/tinyurl.c4
-rw-r--r--src/tools/tinyurl.h2
-rw-r--r--src/ui/buffer.c27
-rw-r--r--src/ui/buffer.h14
-rw-r--r--src/ui/console.c502
-rw-r--r--src/ui/core.c1311
-rw-r--r--src/ui/inputwin.c1002
-rw-r--r--src/ui/inputwin.h18
-rw-r--r--src/ui/notifier.c44
-rw-r--r--src/ui/occupantswin.c19
-rw-r--r--src/ui/rosterwin.c2
-rw-r--r--src/ui/statusbar.c74
-rw-r--r--src/ui/statusbar.h2
-rw-r--r--src/ui/titlebar.c15
-rw-r--r--src/ui/titlebar.h2
-rw-r--r--src/ui/ui.h38
-rw-r--r--src/ui/window.c508
-rw-r--r--src/ui/window.h28
-rw-r--r--src/ui/windows.c78
-rw-r--r--src/ui/windows.h2
-rw-r--r--src/xmpp/bookmark.c26
-rw-r--r--src/xmpp/bookmark.h2
-rw-r--r--src/xmpp/capabilities.c46
-rw-r--r--src/xmpp/capabilities.h2
-rw-r--r--src/xmpp/connection.c43
-rw-r--r--src/xmpp/connection.h2
-rw-r--r--src/xmpp/form.c4
-rw-r--r--src/xmpp/form.h2
-rw-r--r--src/xmpp/iq.c407
-rw-r--r--src/xmpp/iq.h2
-rw-r--r--src/xmpp/message.c559
-rw-r--r--src/xmpp/message.h2
-rw-r--r--src/xmpp/presence.c107
-rw-r--r--src/xmpp/presence.h2
-rw-r--r--src/xmpp/roster.c109
-rw-r--r--src/xmpp/roster.h2
-rw-r--r--src/xmpp/stanza.c200
-rw-r--r--src/xmpp/stanza.h23
-rw-r--r--src/xmpp/xmpp.h11
-rw-r--r--tests/helpers.c18
-rw-r--r--tests/helpers.h2
-rw-r--r--tests/log/stub_log.c10
-rw-r--r--tests/otr/stub_otr.c9
-rw-r--r--tests/test_cmd_account.c6
-rw-r--r--tests/test_cmd_join.c17
-rw-r--r--tests/test_cmd_join.h1
-rw-r--r--tests/test_cmd_otr.c4
-rw-r--r--tests/test_cmd_win.c41
-rw-r--r--tests/test_cmd_win.h2
-rw-r--r--tests/test_common.c44
-rw-r--r--tests/test_common.h6
-rw-r--r--tests/test_history.c219
-rw-r--r--tests/test_history.h13
-rw-r--r--tests/test_keyhandlers.c734
-rw-r--r--tests/test_keyhandlers.h47
-rw-r--r--tests/test_muc.c14
-rw-r--r--tests/test_server_events.c154
-rw-r--r--tests/testsuite.c45
-rw-r--r--tests/ui/stub_ui.c65
-rw-r--r--tests/xmpp/stub_xmpp.c16
-rw-r--r--theme_template74
-rw-r--r--themes/advanced69
-rw-r--r--themes/aqua35
-rw-r--r--themes/batman19
-rw-r--r--themes/boothj568
-rw-r--r--themes/complex24
-rw-r--r--themes/forest39
-rw-r--r--themes/hacker41
-rw-r--r--themes/headache40
-rw-r--r--themes/joker42
-rw-r--r--themes/minimal62
-rw-r--r--themes/mono41
-rw-r--r--themes/orange36
-rw-r--r--themes/original41
-rw-r--r--themes/original_bright41
-rw-r--r--themes/shade42
-rw-r--r--themes/simple66
-rw-r--r--themes/spawn41
-rw-r--r--themes/whiteness44
138 files changed, 7295 insertions, 6044 deletions
diff --git a/.gitignore b/.gitignore
index be11f141..ba909849 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
 # *.[oa]
 # *~
 profanity
+profanity.sh
 *.o
 *.log
 *.swp
@@ -25,7 +26,7 @@ configure.scan
 stamp-h1
 *~
 *dirstamp
-valgrind.out
+valgrind*.out*
 core
 bugs/
 TODO
@@ -64,3 +65,7 @@ profanity.workspace
 m4/
 test.sh
 clean-test.sh
+callgrind.out.*
+gen_docs.sh
+main_fragment.html
+toc_fragment.html
diff --git a/.travis.yml b/.travis.yml
index c87d68b7..e546d8b3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 language: c
 install:
     - sudo apt-get update
-    - sudo apt-get -y install libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev
+    - sudo apt-get -y install libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr2-dev uuid-dev
     - git clone git://github.com/strophe/libstrophe.git
     - cd libstrophe
     - mkdir m4
@@ -11,15 +11,15 @@ install:
     - sudo make install
     - cd ..
     - rm -rf libstrophe
-    - wget https://open.cryptomilk.org/attachments/download/42/cmocka-0.4.1.tar.xz
-    - tar -xvf cmocka-0.4.1.tar.xz
-    - cd cmocka-0.4.1
+    - wget https://cmocka.org/files/1.0/cmocka-1.0.0.tar.xz
+    - tar -xvf cmocka-1.0.0.tar.xz
+    - cd cmocka-1.0.0
     - mkdir build
     - cd build
     - cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug ..
     - make
     - sudo make install
     - cd ../..
-    - rm -rf cmocka-0.4.1
+    - rm -rf cmocka-1.0.0
     - ./bootstrap.sh
 script: ./configure && make && make check
diff --git a/CHANGELOG b/CHANGELOG
index 815f6e55..519660db 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,16 +1,26 @@
+0.4.7
+=====
+
+- GNU Readline
+- Message Carbons (xep-0280)
+- Message Delivery Receipts (xep-0184)
+- MUC Mediated Invitation support
+
 0.4.6
 =====
 
-- 16 colour support
+- 16 colour support (/theme colours)
 - UI preferences included in themes
-- Word wrapping (/wrap)
-- Show hide time (/time)
-- Show or hide and customise roster panel (/roster)
+- /wrap - Word wrapping
+- /time - Show/hide time in main window, and configure precision
+- /roster - Show/hide and customise roster panel
+- /roster and /occupants panel size settings (% of screen width)
+- /account default - Set default account for /connect
 - /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
-- eval_password property on accounts for retrieving password from keyring/keychain
+- /presence - Show/hide contact presence in titlebar 
+- /resource - Override resource during chat, resource display settings
+- Improved chat session handling <http://xmpp.org/rfcs/rfc6121.html#message-chat>
+- Lower CPU usage with dynamic input blocking timeout
+- Keychain/keyring integration using account eval_password property
+- Disable term window title by default
+- Fixed remote code execution bug on OSX when desktop notifications configured to show message text
diff --git a/LICENSE.txt b/LICENSE.txt
index 609ce80c..e1990985 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,5 +1,5 @@
 Profanity
-Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
 
 Profanity is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
diff --git a/Makefile.am b/Makefile.am
index d9fa9729..372b60f3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -13,7 +13,9 @@ core_sources = \
 	src/xmpp/roster.c src/xmpp/roster.h \
 	src/xmpp/bookmark.c src/xmpp/bookmark.h \
 	src/xmpp/form.c src/xmpp/form.h \
-	src/server_events.c src/server_events.h \
+	src/event/server_events.c src/event/server_events.h \
+	src/event/client_events.c src/event/client_events.h \
+	src/event/ui_events.c src/event/ui_events.h \
 	src/ui/ui.h src/ui/window.c src/ui/window.h src/ui/core.c \
 	src/ui/titlebar.c src/ui/statusbar.c src/ui/inputwin.c \
 	src/ui/titlebar.h src/ui/statusbar.h src/ui/inputwin.h \
@@ -27,7 +29,6 @@ core_sources = \
 	src/tools/parser.h \
 	src/tools/p_sha1.h src/tools/p_sha1.c \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
-	src/tools/history.c src/tools/history.h \
 	src/tools/tinyurl.c src/tools/tinyurl.h \
 	src/config/accounts.c src/config/accounts.h \
 	src/config/account.c src/config/account.h \
@@ -44,13 +45,13 @@ tests_sources = \
 	src/roster_list.c src/roster_list.h \
 	src/xmpp/xmpp.h src/xmpp/form.c \
 	src/ui/ui.h \
+	src/otr/otr.h \
 	src/command/command.h src/command/command.c \
 	src/command/commands.h src/command/commands.c \
 	src/tools/parser.c \
 	src/tools/parser.h \
 	src/tools/p_sha1.h src/tools/p_sha1.c \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
-	src/tools/history.c src/tools/history.h \
 	src/tools/tinyurl.c src/tools/tinyurl.h \
 	src/config/accounts.h \
 	src/config/account.c src/config/account.h \
@@ -61,9 +62,10 @@ tests_sources = \
 	src/ui/buffer.c \
 	src/ui/titlebar.c src/ui/statusbar.c src/ui/inputwin.c \
 	src/ui/titlebar.h src/ui/statusbar.h src/ui/inputwin.h \
-	src/server_events.c src/server_events.h \
+	src/event/server_events.c src/event/server_events.h \
+	src/event/client_events.c src/event/client_events.h \
+	src/event/ui_events.c src/event/ui_events.h \
 	tests/xmpp/stub_xmpp.c \
-	tests/otr/stub_otr.c \
 	tests/ui/stub_ui.c \
 	tests/log/stub_log.c \
 	tests/config/stub_accounts.c \
@@ -78,12 +80,10 @@ tests_sources = \
 	tests/test_cmd_roster.c tests/test_cmd_roster.h \
 	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 \
-	tests/test_history.c tests/test_history.h \
 	tests/test_jid.c tests/test_jid.h \
 	tests/test_muc.c tests/test_muc.h \
 	tests/test_parser.c tests/test_parser.h \
@@ -104,6 +104,9 @@ otr3_sources = \
 otr4_sources = \
 	src/otr/otrlib.h src/otr/otrlibv4.c src/otr/otr.h src/otr/otr.c
 
+otr_test_sources = \
+	tests/otr/stub_otr.c
+
 themes_sources = themes/*
 
 script_sources = bootstrap.sh configure-debug install-all.sh
@@ -111,6 +114,7 @@ script_sources = bootstrap.sh configure-debug install-all.sh
 man_sources = docs/profanity.1
 
 if BUILD_OTR
+tests_sources += $(otr_test_sources)
 if BUILD_OTR3
 core_sources += $(otr3_sources)
 endif
diff --git a/configure.ac b/configure.ac
index 7369f112..1e2c6173 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 
-AC_INIT([profanity], [0.4.6], [boothj5web@gmail.com])
+AC_INIT([profanity], [0.4.7], [boothj5web@gmail.com])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([src/main.c])
 AC_CONFIG_HEADERS([src/config.h])
@@ -34,6 +34,8 @@ AC_DEFINE_UNQUOTED([PACKAGE_STATUS], ["$PACKAGE_STATUS"], [Status of this build]
 
 AS_IF([test "x$PLATFORM" = xcygwin],
     [AC_DEFINE([PLATFORM_CYGWIN], [1], [Cygwin])])
+AS_IF([test "x$PLATFORM" = xosx],
+    [AC_DEFINE([PLATFORM_OSX], [1], [OSx])])
 
 ### Options
 AC_ARG_ENABLE([notifications],
@@ -134,10 +136,23 @@ PKG_CHECK_MODULES([glib], [glib-2.0 >= 2.26], [],
 PKG_CHECK_MODULES([curl], [libcurl], [],
     [AC_MSG_ERROR([libcurl is required for profanity])])
 
+AS_IF([test "x$PLATFORM" != xosx],
+    [AC_CHECK_LIB([readline], [main], [],
+        [AC_MSG_ERROR([libreadline is required for profanity])])],
+    [AC_CHECK_FILE([/usr/local/opt/readline/lib],
+        [LIBS="-lreadline $LIBS"
+            AM_CPPFLAGS="-I/usr/local/opt/readline/include $AM_CPPFLAGS"
+            AM_LDFLAGS="-L/usr/local/opt/readline/lib $AM_LDFLAGS"
+            AC_SUBST(AM_LDFLAGS)],
+        [AC_MSG_ERROR([libreadline is required for profanity])])])
+
+AC_CHECK_LIB([uuid], [uuid_generate], [],
+    [AC_MSG_ERROR([libuuid is required for profanity])])
+
 AS_IF([test "x$PLATFORM" = xosx], [LIBS="-lcurl $LIBS"])
 
 ### Check for desktop notification support
-### Linux requires libnotify
+### Linux/FreeBSD require libnotify
 ### Windows uses native OS calls
 ### OSX requires terminal-notifier
 
@@ -150,7 +165,7 @@ AS_IF([test "x$PLATFORM" = xosx],
                     [AC_MSG_ERROR([terminal-notifier not found, required for desktop notifications.])],
                     [AC_MSG_NOTICE([Desktop notifications not supported.])])],
                 [AC_DEFINE([HAVE_OSXNOTIFY], [1], [terminal notifier])])])],
-    [test "x$PLATFORM" = xnix],
+    [test "x$PLATFORM" = xnix -o "x$PLATFORM" = xfreebsd],
         [AS_IF([test "x$enable_notifications" != xno],
             [PKG_CHECK_MODULES([libnotify], [libnotify],
                 [AC_DEFINE([HAVE_LIBNOTIFY], [1], [libnotify module])],
diff --git a/docs/profanity.1 b/docs/profanity.1
index a65281fb..63cd0677 100644
--- a/docs/profanity.1
+++ b/docs/profanity.1
@@ -1,9 +1,9 @@
-.TH Profanity 1 "March 2014" "Profanity XMPP client"
+.TH Profanity 1 "February 2015" "Profanity XMPP client"
 .SH NAME
 Profanity \- a simple console based XMPP chat client.
 .SH SYNOPSIS
 .B profanity
-[\-vhd] [\-l level]
+[\-vhd] [\-l level] [\-a account]
 .SH DESCRIPTION
 .B Profanity
 is a simple lightweight console based XMPP chat client. Its emphasis is 
@@ -15,19 +15,21 @@ at:
 .SH OPTIONS
 .TP
 .BI "\-v, \-\-version"
-Show version information.
+Show version and build information.
 .TP
 .BI "\-h, \-\-help"
 Show help on command line arguments.
 .TP
-.BI "\-a, \-\-account"
-Auto connect to an account on startup.
+.BI "\-a, \-\-account "ACCOUNT
+Auto connect to an account on startup,
+.I ACCOUNT
+must be an existing account.
 .TP
 .BI "\-d, \-\-disable\-tls"
 Disable TLS for servers that either don't support it, or claim to but do not
 complete the handshake.
 .TP
-.BI "\-l, \-\-log="LEVEL
+.BI "\-l, \-\-log "LEVEL
 Set the logging level,
 .I LEVEL
 may be set to DEBUG, INFO (the default), WARN or ERROR.
@@ -45,10 +47,10 @@ is stored in
 , details on configuring Profanity can be found at <http://www.profanity.im/configuration.html>.
 .PP
 .SH BUGS
-Bugs can either be reported by sending a mail directly to:
+Bugs can either be reported by raising an issue at the Github issue tracker:
 .br
 .PP
-<boothj5web@gmail.com>
+<https://github.com/boothj5/profanity/issues>
 .br
 .PP
 or to the mailing list at:
@@ -57,12 +59,14 @@ or to the mailing list at:
 <https://groups.google.com/forum/#!forum/profanitydev>
 .br
 .PP
-or with a Github account by logging issues on the issue tracker at:
+or by sending a mail directly to:
+.br
+.PP
+<boothj5web@gmail.com>
 .br
 .PP
-<https://github.com/boothj5/profanity>
 .SH LICENSE
-Copyright (C) 2012 \- 2014 James Booth <boothj5web@gmail.com>.
+Copyright (C) 2012 \- 2015 James Booth <boothj5web@gmail.com>.
 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
 This is free software; you are free to change and redistribute it.
 There is NO WARRANTY, to the extent permitted by law.
diff --git a/install-all.sh b/install-all.sh
index cc32d8fd..4ffc5999 100755
--- a/install-all.sh
+++ b/install-all.sh
@@ -24,7 +24,7 @@ debian_prepare()
     echo
     echo Profanity installer... installing dependencies
     echo
-    sudo apt-get -y install git automake autoconf libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr5-dev libtool
+    sudo apt-get -y install git automake autoconf libssl-dev libexpat1-dev libncursesw5-dev libglib2.0-dev libnotify-dev libcurl3-dev libxss-dev libotr5-dev libreadline-dev libtool uuid-dev
 
 }
 
@@ -36,7 +36,7 @@ fedora_prepare()
 
     ARCH=`arch`
 
-    sudo yum -y install gcc git autoconf automake openssl-devel.$ARCH expat-devel.$ARCH ncurses-devel.$ARCH  glib2-devel.$ARCH libnotify-devel.$ARCH libcurl-devel.$ARCH libXScrnSaver-devel.$ARCH libotr3-devel.$ARCH libtool
+    sudo yum -y install gcc git autoconf automake openssl-devel.$ARCH expat-devel.$ARCH ncurses-devel.$ARCH  glib2-devel.$ARCH libnotify-devel.$ARCH libcurl-devel.$ARCH libXScrnSaver-devel.$ARCH libotr3-devel.$ARCH readline-devel.$ARCH libtool uuid-devel.$ARCH
 }
 
 opensuse_prepare()
@@ -44,7 +44,7 @@ opensuse_prepare()
     echo
     echo Profanity installer...installing dependencies
     echo
-    sudo zypper -n in gcc git automake make autoconf libopenssl-devel expat libexpat-devel ncurses-devel glib2-devel libnotify-devel libcurl-devel libXScrnSaver-devel libotr-devel libtool
+    sudo zypper -n in gcc git automake make autoconf libopenssl-devel expat libexpat-devel ncurses-devel glib2-devel libnotify-devel libcurl-devel libXScrnSaver-devel libotr-devel readline-devel libtool libuuid-devel
 }
 
 cygwin_prepare()
@@ -60,9 +60,9 @@ cygwin_prepare()
     mv apt-cyg /usr/local/bin/
 
     if [ -n "$CYG_MIRROR" ]; then
-        apt-cyg -m $CYG_MIRROR install git make gcc-core m4 automake autoconf pkg-config openssl-devel libexpat-devel zlib-devel libncursesw-devel libglib2.0-devel libcurl-devel libidn-devel libssh2-devel libkrb5-devel openldap-devel libgcrypt-devel libtool
+        apt-cyg -m $CYG_MIRROR install git make gcc-core m4 automake autoconf pkg-config openssl-devel libexpat-devel zlib-devel libncursesw-devel libglib2.0-devel libcurl-devel libidn-devel libssh2-devel libkrb5-devel openldap-devel libgcrypt-devel libreadline-devel libtool libuuid-devel
     else
-        apt-cyg install git make gcc-core m4 automake autoconf pkg-config openssl-devel libexpat-devel zlib-devel libncursesw-devel libglib2.0-devel libcurl-devel libidn-devel libssh2-devel libkrb5-devel openldap-devel libgcrypt-devel libtool
+        apt-cyg install git make gcc-core m4 automake autoconf pkg-config openssl-devel libexpat-devel zlib-devel libncursesw-devel libglib2.0-devel libcurl-devel libidn-devel libssh2-devel libkrb5-devel openldap-devel libgcrypt-devel libreadline-devel libtool libuuid-devel
 
     fi
 }
diff --git a/src/chat_session.c b/src/chat_session.c
index fbe06f76..2758d603 100644
--- a/src/chat_session.c
+++ b/src/chat_session.c
@@ -1,7 +1,7 @@
 /*
  * chat_session.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -64,7 +64,7 @@ _chat_session_new(const char * const barejid, const char * const resource,
 static void
 _chat_session_free(ChatSession *session)
 {
-    if (session != NULL) {
+    if (session) {
         free(session->barejid);
         free(session->resource);
         free(session);
@@ -81,7 +81,7 @@ chat_sessions_init(void)
 void
 chat_sessions_clear(void)
 {
-    if (sessions != NULL)
+    if (sessions)
         g_hash_table_remove_all(sessions);
 }
 
@@ -141,7 +141,7 @@ chat_session_recipient_active(const char * const barejid, const char * const res
         // 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
+        // session exists with different resource and no override, replace
         } else if (!session->resource_override) {
             _chat_session_new(barejid, resource, FALSE, send_states);
         }
diff --git a/src/chat_session.h b/src/chat_session.h
index 585a523b..f51a8341 100644
--- a/src/chat_session.h
+++ b/src/chat_session.h
@@ -1,7 +1,7 @@
 /*
  * chat_session.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/chat_state.c b/src/chat_state.c
index 99a83f43..8ddb6f1e 100644
--- a/src/chat_state.c
+++ b/src/chat_state.c
@@ -1,7 +1,7 @@
 /*
  * chat_state.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/chat_state.h b/src/chat_state.h
index ba394a56..7f0d8832 100644
--- a/src/chat_state.h
+++ b/src/chat_state.h
@@ -1,7 +1,7 @@
 /*
  * chat_state.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/command/command.c b/src/command/command.c
index 1441ef46..1871ea6c 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -1,7 +1,7 @@
 /*
  * command.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -36,6 +36,7 @@
 #include <errno.h>
 #include <limits.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 
 #include <glib.h>
@@ -70,8 +71,6 @@
 typedef char*(*autocompleter)(char*, int*);
 
 static gboolean _cmd_execute(const char * const command, const char * const inp);
-static gboolean _cmd_execute_default(const char * inp);
-static gboolean _cmd_execute_alias(const char * const inp, gboolean *ran);
 
 static char * _cmd_complete_parameters(const char * const input);
 
@@ -101,6 +100,8 @@ static char * _role_autocomplete(const char * const input);
 static char * _resource_autocomplete(const char * const input);
 static char * _titlebar_autocomplete(const char * const input);
 static char * _inpblock_autocomplete(const char * const input);
+static char * _time_autocomplete(const char * const input);
+static char * _receipts_autocomplete(const char * const input);
 
 GHashTable *commands = NULL;
 
@@ -111,18 +112,19 @@ static struct cmd_t command_defs[] =
 {
     { "/help",
         cmd_help, parse_args, 0, 1, NULL,
-        { "/help [area|command]", "Get help on using Profanity.",
+        { "/help [area|command]", "Help on using Profanity.",
         { "/help [area|command]",
-          "-------------------------",
-          "Use with no arguments to get a help summary.",
-          "Supply an area to see help for commands related to specific features.",
-          "Supply a command (without the leading slash) to see help for that command.",
+          "--------------------",
+          "Help on using Profanity.",
+          "",
+          "area    : Summary help for commands in a certain area of functionality.",
+          "command : Full help for a specific command, for example '/help connect'.",
           "",
-          "Example : /help commands",
-          "Example : /help presence",
-          "Example : /help who",
+          "Use with no arguments to see a list of areas.",
           "",
-          "For more detailed help, see the user guide at http://www.profanity.im/userguide.html.",
+          "Example: /help commands",
+          "Example: /help presence",
+          "Example: /help who",
           NULL } } },
 
     { "/about",
@@ -135,15 +137,17 @@ static struct cmd_t command_defs[] =
 
     { "/connect",
         cmd_connect, parse_args, 0, 5, NULL,
-        { "/connect [account] [server value] [port value]", "Login to a chat service.",
+        { "/connect [account] [server value] [port value]", "Account login.",
         { "/connect [account] [server value] [port value]",
           "----------------------------------------------",
-          "Connect to an XMPP service using the specified account.",
-          "Use the server property to specify a server if required.",
-          "Change the default port (5222, or 5223 for SSL) with the port property.",
-          "An account is automatically created if one does not exist.",
-          "If no account is specified, then the default account is used."
-          "See the /account command for more details.",
+          "Login to a chat service.",
+          "",
+          "account      : The local account you wish to connect with, or a JID if connecting for the first time.",
+          "server value : Supply a server if it is different to the domain part of your JID.",
+          "port value   : The port to use if different to the default (5222, or 5223 for SSL).",
+          "",
+          "If no account is specified, the default is used if one is configured.",
+          "A local account is created with the JID as it's name if it doesn't already exist.",
           "",
           "Example: /connect",
           "Example: /connect myuser@gmail.com",
@@ -162,48 +166,57 @@ static struct cmd_t command_defs[] =
 
     { "/msg",
         cmd_msg, parse_args_with_freetext, 1, 2, NULL,
-        { "/msg contact|nick [message]", "Start chat with user.",
+        { "/msg contact|nick [message]", "Start chat with a user.",
         { "/msg contact|nick [message]",
           "---------------------------",
-          "Open a chat window for the contact and send the message if one is supplied.",
-          "When in a chat room, supply a nickname to start private chat with a room member.",
+          "Send a one to one chat message, or a private message to a chat room occupant.",
+          "",
+          "contact : The contact's JID, or nickname if one has been set in your roster.",
+          "nick    : A chat room occupant, to whom you wish to send a private message.",
+          "message : The message to send",
+          "",
+          "If the message is omitted, a new chat window will be opened without sending a message.",
           "Use quotes if the nickname includes spaces.",
           "",
-          "Example : /msg myfriend@server.com Hey, here's a message!",
-          "Example : /msg otherfriend@server.com",
-          "Example : /msg Bob Here is a private message",
-          "Example : /msg \"My Friend\" Hi, how are you?",
+          "Example: /msg myfriend@server.com Hey, here's a message!",
+          "Example: /msg otherfriend@server.com",
+          "Example: /msg Bob Here is a private message",
+          "Example: /msg \"My Friend\" Hi, how are you?",
           NULL } } },
 
     { "/roster",
         cmd_roster, parse_args_with_freetext, 0, 3, NULL,
-        { "/roster [online|show|hide|by|size|add|remove|nick|clearnick] [offline|resource] [percent] [group|presence|none] [jid] [nickname]", "Manage your roster.",
-        { "/roster [online|show|hide|by|size|add|remove|nick|clearnick] [offline|resource] [percent] [group|presence|none] [jid] [nickname]",
-          "-------------------------------------------------------------------------------------------------------------------------",
-          "View, add to, and remove from your roster.",
+        { "/roster [command] [args..]", "Manage your roster.",
+        { "/roster [command] [args..]",
+          "--------------------------",
+          "Manage your roster, and roster display settings.",
+          "",
+          "command - online|show|hide|by|size|add|remove|nick|clearnick",
+          "",
+          "online         : Show all online contacts in your roster.",
+          "show           : Show the roster panel.",
+          "show offline   : Show offline contacts in the roster panel.",
+          "show resource  : Show contact's connected resources in the roster panel.",
+          "hide           : Hide the roster panel.",
+          "hide offline   : Hide offline contacts in the roster panel.",
+          "hide resource  : Hide contact's connected resources in the roster panel.",
+          "by group       : Group contacts in the roster panel by roster group.",
+          "by presence    : Group contacts in the roster panel by presence.",
+          "by none        : No grouping in the roster panel.",
+          "size           : Percentage of the screen taken up by the roster (1-99).",
+          "add jid [nick] : Add a new item to the roster.",
+          "remove jid     : Removes an item from the roster.",
+          "nick jid nick  : Change a contacts nickname.",
+          "clearnick jid  : Removes the current nickname.",
+          "",
           "Passing no arguments lists all contacts in your roster.",
-          "online        - Show all online contacts in your roster.",
-          "show          - Show the roster panel in the console window.",
-          "hide          - Hide the roster panel.",
-          "show offline  - Show offline contacts in the roster panel.",
-          "hide offline  - Hide offline contacts in the roster panel.",
-          "show resource - Show contact's connected resources in the roster panel.",
-          "hide resource - Hide contact's connected resources in the roster panel.",
-          "by group      - Group contacts in the roster panel by roster group.",
-          "by presence   - Group contacts in the roster panel by presence.",
-          "by none       - No grouping in the roster panel.",
-          "size          - Percentage of the screen taken up by the roster (1-99).",
-          "add           - Add a new item, jid is required, nickname is optional.",
-          "remove        - Removes a contact, jid is required.",
-          "nick          - Changes a contacts nickname, both jid and nickname are required,",
-          "clearnick     - Removes the current nickname, jid is required.",
-          "",
-          "Example : /roster (show your roster)",
-          "Example : /roster add someone@contacts.org (add the contact)",
-          "Example : /roster add someone@contacts.org Buddy (add the contact with nickname 'Buddy')",
-          "Example : /roster remove someone@contacts.org (remove the contact)",
-          "Example : /roster nick myfriend@chat.org My Friend",
-          "Example : /roster clearnick kai@server.com (clears nickname)",
+          "",
+          "Example: /roster (show your roster)",
+          "Example: /roster add someone@contacts.org (add the contact)",
+          "Example: /roster add someone@contacts.org Buddy (add the contact with nickname 'Buddy')",
+          "Example: /roster remove someone@contacts.org (remove the contact)",
+          "Example: /roster nick myfriend@chat.org My Friend",
+          "Example: /roster clearnick kai@server.com (clears nickname)",
           NULL } } },
 
     { "/group",
@@ -212,31 +225,35 @@ static struct cmd_t command_defs[] =
         { "/group [show|add|remove] [group] [contact]",
           "------------------------------------------",
           "View, add to, and remove from roster groups.",
+          "",
+          "show group           : List all roster items a group.",
+          "add group contact    : Added a contact to a group.",
+          "remove group contact : Remove a contact from a group.",
+          "",
           "Passing no argument will list all roster groups.",
-          "The 'show' command takes 'group' as an argument, and lists all roster items in that group.",
-          "The 'add' command takes 'group' and 'contact' arguments, and adds the contact to the group.",
-          "The 'remove' command takes 'group' and 'contact' arguments and removes the contact from the group,",
           "",
-          "Example : /group",
-          "Example : /group show friends",
-          "Example : /group add friends newfriend@server.org",
-          "Example : /group add family Brother (using contacts nickname)",
-          "Example : /group remove colleagues boss@work.com",
+          "Example: /group",
+          "Example: /group show friends",
+          "Example: /group add friends newfriend@server.org",
+          "Example: /group add family Brother (using contacts nickname)",
+          "Example: /group remove colleagues boss@work.com",
           NULL } } },
 
     { "/info",
         cmd_info, parse_args, 0, 1, NULL,
-        { "/info [contact|nick]", "Show basic information about a contact, room, or room member.",
+        { "/info [contact|nick]", "Show information about a contact, room, or room member.",
         { "/info [contact|nick]",
           "--------------------",
-          "Show basic information about a contact, room, or room member.",
-          "If in the console, a contact must be specified.",
-          "If in a chat window the parameter is not required, the current recipient will be used.",
-          "If in a chat room, providing no arguments will display information about the room.",
-          "If in a chat room, supplying a nick will show information about the occupant.",
+          "Show information about a contact, room, or room member.",
           "",
-          "Example : /info mybuddy@chat.server.org",
-          "Example : /info kai",
+          "contact : The contact you wish to view information about.",
+          "nick    : When in a chat room, the occupant you wish to view information about.",
+          "",
+          "Passing no argument in a chat window will use the current recipient.",
+          "Passing no argument in a chat room will display information about the room.",
+          "",
+          "Example: /info mybuddy@chat.server.org",
+          "Example: /info kai",
           NULL } } },
 
     { "/caps",
@@ -244,14 +261,16 @@ static struct cmd_t command_defs[] =
         { "/caps [fulljid|nick]", "Find out a contacts client software capabilities.",
         { "/caps [fulljid|nick]",
           "--------------------",
-          "Find out a contact, or room members client software capabilities.",
-          "If in the console window or a regular chat window, a full JID is required.",
-          "If in a chat room, the nickname is required.",
-          "If in private chat, no parameter is required.",
+          "Find out a contacts, or room members client software capabilities.",
+          "",
+          "fulljid : If in the console or a chat window, the full JID for which you wish to see capabilities.",
+          "nick    : If in a chat room, nickname for which you wish to see capabilities.",
+          "",
+          "If in private chat initiated from a chat room, no parameter is required.",
           "",
-          "Example : /caps mybuddy@chat.server.org/laptop (contact's laptop resource)",
-          "Example : /caps mybuddy@chat.server.org/phone (contact's phone resource)",
-          "Example : /caps bruce (room member)",
+          "Example: /caps mybuddy@chat.server.org/laptop (contact's laptop resource)",
+          "Example: /caps mybuddy@chat.server.org/phone (contact's phone resource)",
+          "Example: /caps bruce (room member)",
           NULL } } },
 
     { "/software",
@@ -259,15 +278,17 @@ static struct cmd_t command_defs[] =
         { "/software [fulljid|nick]", "Find out software version information about a contacts resource.",
         { "/software [fulljid|nick]",
           "------------------------",
-          "Find out a contact, or room members software version information, if such requests are supported.",
-          "If in the console window or a regular chat window, a full JID is required.",
-          "If in a chat room, the nickname is required.",
-          "If in private chat, no parameter is required.",
+          "Find out a contact, or room members software version information.",
+          "",
+          "fulljid : If in the console or a chat window, the full JID for which you wish to see software information.",
+          "nick    : If in a chat room, nickname for which you wish to see software information.",
+          "",
+          "If in private chat initiated from a chat room, no parameter is required.",
           "If the contact's software does not support software version requests, nothing will be displayed.",
           "",
-          "Example : /software mybuddy@chat.server.org/laptop (contact's laptop resource)",
-          "Example : /software mybuddy@chat.server.org/phone (contact's phone resource)",
-          "Example : /software bruce (room member)",
+          "Example: /software mybuddy@chat.server.org/laptop (contact's laptop resource)",
+          "Example: /software mybuddy@chat.server.org/phone (contact's phone resource)",
+          "Example: /software bruce (room member)",
           NULL } } },
 
     { "/status",
@@ -276,39 +297,49 @@ static struct cmd_t command_defs[] =
         { "/status [contact|nick]",
           "----------------------",
           "Find out a contact, or room members presence information.",
+          "",
+          "contact : The contact who's presence you which to see.",
+          "nick    : If in a chat room, the occupant who's presence you wish to see.",
+          "",
           "If in a chat window the parameter is not required, the current recipient will be used.",
           "",
-          "Example : /status buddy@server.com",
-          "Example : /status jon",
+          "Example: /status buddy@server.com",
+          "Example: /status jon",
           NULL } } },
 
     { "/resource",
         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 contact's resource, display settings.",
         { "/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.",
+          "Override chat session resource, and manage resource display settings.",
+          "",
+          "set resource   : Set the resource to which messages will be sent.",
+          "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 when showing an incoming message.",
           NULL } } },
 
     { "/join",
-        cmd_join, parse_args, 1, 5, NULL,
-        { "/join room[@server] [nick value] [password value]", "Join a chat room.",
-        { "/join room[@server] [nick value] [password value]",
-          "-------------------------------------------------",
+        cmd_join, parse_args, 0, 5, NULL,
+        { "/join [room] [nick value] [password value]", "Join a chat room.",
+        { "/join [room] [nick value] [password value]",
+          "-----------------------------------------",
           "Join a chat room at the conference server.",
-          "If nick is specified you will join with this nickname.",
-          "Otherwise the account preference 'muc.nick' will be used which by default is the localpart of your JID (before the @).",
-          "If no server is supplied, the account preference 'muc.service' is used, which is 'conference.<domain-part>' by default.",
+          "",
+          "room           : Bare room JID (the chat server is determined by the 'muc.service' account property) or full room jid."
+          "nick value     : Nickname to use in the room",
+          "password value : Password if the room requires it.",
+          "",
+          "If no room is supplied, a generated name will be used with the format private-chat-[UUID].",
+          "If no nickname is specified the account preference 'muc.nick' will be used which by default is the localpart of your JID.",
           "If the room doesn't exist, and the server allows it, a new one will be created.",
           "",
-          "Example : /join jdev@conference.jabber.org",
-          "Example : /join jdev@conference.jabber.org nick mynick",
-          "Example : /join private@conference.jabber.org nick mynick password mypassword",
-          "Example : /join jdev (as user@jabber.org will join jdev@conference.jabber.org)",
+          "Example: /join",
+          "Example: /join jdev@conference.jabber.org",
+          "Example: /join jdev@conference.jabber.org nick mynick",
+          "Example: /join private@conference.jabber.org nick mynick password mypassword",
+          "Example: /join jdev (as user@jabber.org will join jdev@conference.jabber.org)",
           NULL } } },
 
     { "/leave",
@@ -324,8 +355,10 @@ static struct cmd_t command_defs[] =
         { "/invite contact [message]", "Invite contact to chat room.",
         { "/invite contact [message]",
           "-------------------------",
-          "Send a direct invite to the specified contact to the current chat room.",
-          "If a message is supplied it will be sent as the reason for the invite.",
+          "Send a direct invite to the current chat room.",
+          "",
+          "contact : The contact you wish to invite",
+          "message : An optional message to send with the invite.",
           NULL } } },
 
     { "/invites",
@@ -333,9 +366,7 @@ static struct cmd_t command_defs[] =
         { "/invites", "Show outstanding chat room invites.",
         { "/invites",
           "--------",
-          "Show all rooms that you have been invited to, and have not yet been accepted or declind.",
-          "Use \"/join <room>\" to accept a room invitation.",
-          "Use \"/decline <room>\" to decline a room invitation.",
+          "Show all rooms that you have been invited to, and not accepted or declined.",
           NULL } } },
 
     { "/decline",
@@ -343,7 +374,9 @@ static struct cmd_t command_defs[] =
         { "/decline room", "Decline a chat room invite.",
         { "/decline room",
           "-------------",
-          "Decline invitation to a chat room, the room will no longer be in the list of outstanding invites.",
+          "Decline a chat room invitation.",
+          "",
+          "room : The room for the invite you wish to decline.",
           NULL } } },
 
     { "/room",
@@ -351,9 +384,11 @@ static struct cmd_t command_defs[] =
         { "/room accept|destroy|config", "Room configuration.",
         { "/room accept|destroy|config",
           "---------------------------",
-          "accept  - Accept default room configuration.",
-          "destroy - Reject default room configuration.",
-          "config  - Edit room configuration.",
+          "Chat room configuration.",
+          "",
+          "accept  : Accept default room configuration.",
+          "destroy : Reject default room configuration.",
+          "config  : Edit room configuration.",
           NULL } } },
 
     { "/kick",
@@ -361,8 +396,10 @@ static struct cmd_t command_defs[] =
         { "/kick nick [reason]", "Kick occupants from chat rooms.",
         { "/kick nick [reason]",
           "-------------------",
-          "nick   - Nickname of the occupant to kick from the room.",
-          "reason - Optional reason for kicking the occupant.",
+          "Kick occupants from chat rooms.",
+          "",
+          "nick   : Nickname of the occupant to kick from the room.",
+          "reason : Optional reason for kicking the occupant.",
           NULL } } },
 
     { "/ban",
@@ -370,8 +407,10 @@ static struct cmd_t command_defs[] =
         { "/ban jid [reason]", "Ban users from chat rooms.",
         { "/ban jid [reason]",
           "-----------------",
-          "jid    - Bare JID of the user to ban from the room.",
-          "reason - Optional reason for banning the user.",
+          "Ban users from chat rooms.",
+          "",
+          "jid    : Bare JID of the user to ban from the room.",
+          "reason : Optional reason for banning the user.",
           NULL } } },
 
     { "/subject",
@@ -379,8 +418,10 @@ static struct cmd_t command_defs[] =
         { "/subject set|clear [subject]", "Set or clear room subject.",
         { "/subject set|clear [subject]",
           "----------------------------",
-          "set subject  - Set the room subject.",
-          "clear        - Clear the room subject.",
+          "Set or clear room subject.",
+          "",
+          "set subject  : Set the room subject.",
+          "clear        : Clear the room subject.",
           NULL } } },
 
     { "/affiliation",
@@ -388,8 +429,11 @@ static struct cmd_t command_defs[] =
         { "/affiliation set|list [affiliation] [jid] [reason]", "Manage room affiliations.",
         { "/affiliation set|list [affiliation] [jid] [reason]",
           "--------------------------------------------------",
-          "set affiliation jid [reason]- Set the affiliation of user with jid, with an optional reason.",
-          "list [affiliation]          - List all users with the specified affiliation, or all if none specified.",
+          "Manage room affiliations.",
+          "",
+          "set affiliation jid [reason]: Set the affiliation of user with jid, with an optional reason.",
+          "list [affiliation]          : List all users with the specified affiliation, or all if none specified.",
+          "",
           "The affiliation may be one of owner, admin, member, outcast or none.",
           NULL } } },
 
@@ -398,20 +442,28 @@ static struct cmd_t command_defs[] =
         { "/role set|list [role] [nick] [reason]", "Manage room roles.",
         { "/role set|list [role] [nick] [reason]",
           "-------------------------------------",
-          "set role nick [reason] - Set the role of occupant with nick, with an optional reason.",
-          "list [role]            - List all occupants with the specified role, or all if none specified.",
+          "Manage room roles.",
+          "",
+          "set role nick [reason] : Set the role of occupant with nick, with an optional reason.",
+          "list [role]            : List all occupants with the specified role, or all if none specified.",
+          "",
           "The role may be one of moderator, participant, visitor or none.",
           NULL } } },
 
     { "/occupants",
-        cmd_occupants, parse_args, 1, 2, cons_occupants_setting,
-        { "/occupants show|hide|default|size [show|hide] [percent]", "Show or hide room occupants.",
-        { "/occupants show|hide|default|size [show|hide] [percent]",
-          "-------------------------------------------------------",
-          "show    - Show the occupants panel in chat rooms.",
-          "hide    - Hide the occupants panel in chat rooms.",
-          "default - Whether occupants are shown by default in new rooms, 'show' or 'hide'",
-          "size    - Percentage of the screen taken by the occupants list in rooms (1-99).",
+        cmd_occupants, parse_args, 1, 3, cons_occupants_setting,
+        { "/occupants show|hide|default|size [jid|show|hide|percent] [jid]", "Show or hide room occupants.",
+        { "/occupants show|hide|default|size [jid|show|hide|percent] [jid]",
+          "---------------------------------------------------------------",
+          "Show or hide room occupants, and occupants panel display settings.",
+          "",
+          "show                    : Show the occupants panel in current room.",
+          "hide                    : Hide the occupants panel in current room.",
+          "show jid                : Show jid in the occupants panel in current room.",
+          "hide jid                : Hide jid in the occupants panel in current room.",
+          "default show|hide       : Whether occupants are shown by default in new rooms.",
+          "default show|hide jid   : Whether occupants jids are shown by default in new rooms.",
+          "size percent            : Percentage of the screen taken by the occupants list in rooms (1-99).",
           NULL } } },
 
     { "/form",
@@ -419,10 +471,12 @@ static struct cmd_t command_defs[] =
         { "/form show|submit|cancel|help [tag]", "Form handling.",
         { "/form show|submit|cancel|help [tag]",
           "-----------------------------------",
-          "show             - Show the current form.",
-          "submit           - Submit the current form.",
-          "cancel           - Cancel changes to the current form.",
-          "help [tag]       - Display help for form, or a specific field.",
+          "Form configuration.",
+          "",
+          "show             : Show the current form.",
+          "submit           : Submit the current form.",
+          "cancel           : Cancel changes to the current form.",
+          "help [tag]       : Display help for form, or a specific field.",
           NULL } } },
 
     { "/rooms",
@@ -431,45 +485,51 @@ static struct cmd_t command_defs[] =
         { "/rooms [conference-service]",
           "---------------------------",
           "List the chat rooms available at the specified conference service",
-          "If no argument is supplied, the account preference 'muc.service' is used, which is 'conference.<domain-part>' by default.",
           "",
-          "Example : /rooms conference.jabber.org",
-          "Example : /rooms (if logged in as me@server.org, is equivalent to /rooms conference.server.org)",
+          "conference-service : The conference service to query.",
+          "",
+          "If no argument is supplied, the account preference 'muc.service' is used, 'conference.<domain-part>' by default.",
+          "",
+          "Example: /rooms conference.jabber.org",
+          "Example: /rooms (if logged in as me@server.org, is equivalent to /rooms conference.server.org)",
           NULL } } },
 
     { "/bookmark",
         cmd_bookmark, parse_args, 0, 8, NULL,
-        { "/bookmark [list|add|update|remove|join] [room@server] [nick value] [password value] [autojoin on|off]", "Manage bookmarks.",
-        { "/bookmark [list|add|update|remove|join] [room@server] [nick value] [password value] [autojoin on|off]",
-          "---------------------------------------------------------------------------------------------------",
-          "Manage bookmarks.",
-          "list: List all bookmarks.",
-          "add: Add a bookmark for room@server with the following optional properties:",
-          "  nick: Nickname used in the chat room",
-          "  password: Password for private rooms, note this may be stored in plaintext on your server",
-          "  autojoin: Whether to join the room automatically on login \"on\" or \"off\".",
-          "update: Update any of the above properties associated with the bookmark.",
-          "remove: Remove the bookmark for room@server.",
-          "join: Join room@server using the properties associated with the bookmark.",
-          "When in a chat room, the /bookmark command with no arguments will bookmark the current room with the current settings, and set autojoin to \"on\".",
+        { "/bookmark [command] [args..]", "Manage bookmarks.",
+        { "/bookmark [command] [args..]",
+          "----------------------------",
+          "Manage bookmarks and join bookmarked rooms.",
+          "",
+          "command : list|add|update|remove|join",
+          "",
+          "list                              : List all bookmarks.",
+          "add room@server [prop value..]    : Add a bookmark for room@server with the following optional properties:",
+          "  nick value                      : Nickname used in the chat room",
+          "  password value                  : Password if required, may be stored in plaintext on your server",
+          "  autojoin on|off                 : Whether to join the room automatically on login.",
+          "update room@server [prop value..] : Update any of the above properties associated with the bookmark.",
+          "remove room@server                : Remove the bookmark for room@server.",
+          "join room@server                  : Join room using the properties associated with the bookmark.",
+          "",
+          "In a chat room, /bookmark with no arguments will bookmark the current room, setting autojoin to \"on\".",
           NULL } } },
 
     { "/disco",
         cmd_disco, parse_args, 1, 2, NULL,
-        { "/disco command entity", "Service discovery.",
-        { "/disco command entity",
+        { "/disco info|items entity", "Service discovery.",
+        { "/disco info|items entity",
           "---------------------",
           "Find out information about an entities supported services.",
-          "Command may be one of:",
-          "info: List protocols and features supported by an entity.",
-          "items: List items associated with an entity.",
           "",
-          "The entity must be a Jabber ID.",
+          "info   : List protocols and features supported by an entity.",
+          "items  : List items associated with an entity.",
+          "entity : Jabber ID.",
           "",
-          "Example : /disco info myserver.org",
-          "Example : /disco items myserver.org",
-          "Example : /disco items conference.jabber.org",
-          "Example : /disco info myfriend@server.com/laptop",
+          "Example: /disco info myserver.org",
+          "Example: /disco items myserver.org",
+          "Example: /disco items conference.jabber.org",
+          "Example: /disco info myfriend@server.com/laptop",
           NULL } } },
 
     { "/nick",
@@ -478,47 +538,52 @@ static struct cmd_t command_defs[] =
         { "/nick nickname",
           "--------------",
           "Change the name by which other members of a chat room see you.",
-          "This command is only valid when called within a chat room window.",
           "",
-          "Example : /nick kai hansen",
-          "Example : /nick bob",
+          "nickname : The new nickname.",
+          "",
+          "Example: /nick kai hansen",
+          "Example: /nick bob",
           NULL } } },
 
     { "/win",
         cmd_win, parse_args, 1, 1, NULL,
         { "/win num", "View a window.",
         { "/win num",
-          "------------------",
+          "--------",
           "Show the contents of a specific window in the main window area.",
+          "",
+          "num - Window number to display.",
           NULL } } },
 
     { "/wins",
         cmd_wins, parse_args, 0, 3, NULL,
-        { "/wins [tidy|prune|swap] [source] [target]", "List or tidy active windows.",
-        { "/wins [tidy|prune|swap] [source] [target]",
-          "-----------------------------------------",
-          "Passing no argument will list all currently active windows and information about their usage.",
-          "tidy               : Shuffle windows so there are no gaps.",
+        { "/wins [tidy|prune|swap] [source target]", "List or tidy active windows.",
+        { "/wins [tidy|prune|swap] [source target]",
+          "---------------------------------------",
+          "Show a list of windows, or tidy or swap.",
+          "",
+          "tidy               : Move windows so there are no gaps.",
           "prune              : Close all windows with no unread messages, and then tidy as above.",
           "swap source target : Swap windows, target may be an empty position.",
+          "",
+          "Passing no argument will list all currently active windows and information about their usage.",
           NULL } } },
 
     { "/sub",
         cmd_sub, parse_args, 1, 2, NULL,
-        { "/sub command [jid]", "Manage subscriptions.",
-        { "/sub command [jid]",
-          "------------------",
-          "command : One of the following,",
-          "request  : Send a subscription request to the user to be informed of their",
-          "         : presence.",
-          "allow    : Approve a contact's subscription reqeust to see your presence.",
-          "deny     : Remove subscription for a contact, or deny a request",
-          "show     : Show subscriprion status for a contact.",
-          "sent     : Show all sent subscription requests pending a response.",
-          "received : Show all received subscription requests awaiting your response.",
-          "",
-          "The optional 'jid' parameter only applys to 'request', 'allow', 'deny' and 'show'",
-          "If it is omitted the contact of the current window is used.",
+        { "/sub request|allow|deny|show|sent|received [jid]", "Manage subscriptions.",
+        { "/sub request|allow|deny|show|sent|received [jid]",
+          "------------------------------------------------",
+          "Manage subscriptions to contact presence.",
+          "",
+          "request [jid] : Send a subscription request to the user.",
+          "allow [jid]   : Approve a contact's subscription request.",
+          "deny [jid]    : Remove subscription for a contact, or deny a request",
+          "show [jid]    : Show subscription status for a contact.",
+          "sent          : Show all sent subscription requests pending a response.",
+          "received      : Show all received subscription requests awaiting your response.",
+          "",
+          "If jid is omitted, the contact of the current window is used.",
           "",
           "Example: /sub request myfriend@jabber.org",
           "Example: /sub allow myfriend@jabber.org",
@@ -531,9 +596,11 @@ static struct cmd_t command_defs[] =
         { "/tiny url", "Send url as tinyurl in current chat.",
         { "/tiny url",
           "---------",
-          "Send the url as a tiny url.",
+          "Send url as tinyurl in current chat.",
+          "",
+          "url : The url to make tiny.",
           "",
-          "Example : /tiny http://www.profanity.im",
+          "Example: /tiny http://www.profanity.im",
           NULL } } },
 
     { "/who",
@@ -541,31 +608,30 @@ static struct cmd_t command_defs[] =
         { "/who [status|role|affiliation] [group]", "Show contacts/room occupants with chosen status, role or affiliation",
         { "/who [status|role|affiliation] [group]",
           "--------------------------------------",
-          "Normal usage:",
-          "Status may be one of - online, offline, away, dnd, xa, chat, available, unavailable, or any where:",
-          "online      : Contacts that are online, chat, away, xa, dnd",
-          "available   : Contacts that are available for chat - online, chat.",
-          "unavailable : Contacts that are not available for chat - offline, away, xa, dnd.",
-          "any         : Contacts with any status (same as calling with no argument).",
+          "Show contacts/room occupants with chosen status, role or affiliation",
           "",
-          "The group argument filters the list by that group.",
-          "",
-          "In a chat room, a role or affiliation may also be supplied instead of status.",
-          "Roles: moderator, participant, visitor",
-          "Affiliations: owner, admin, member",
+          "status : online|offline|away|dnd|xa|chat|available|unavailable|any",
+          "  online      : Contacts that are online, chat, away, xa, dnd",
+          "  available   : Contacts that are available for chat - online, chat.",
+          "  unavailable : Contacts that are not available for chat - offline, away, xa, dnd.",
+          "  any         : Contacts with any status (same as calling with no argument).",
+          "role        : moderator|participant|visitor",
+          "affiliation : owner|admin|member",
+          "group       : Filter the results by the specified group.",
           NULL } } },
 
     { "/close",
         cmd_close, parse_args, 0, 1, NULL,
-        { "/close [win|read|all]", "Close windows.",
-        { "/close [win|read|all]",
+        { "/close [num|read|all]", "Close windows.",
+        { "/close [num|read|all]",
           "---------------------",
+          "Close the current window, or a number of windows.",
+          "",
+          "num  : Close the specified window.",
+          "all  : Close all windows.",
+          "read : Close all windows that have no new messages.",
+          "",
           "Passing no argument will close the current window.",
-          "2,3,4,5,6,7,8,9 or 0 : Close the specified window.",
-          "all                  : Close all currently open windows.",
-          "read                 : Close all windows that have no new messages.",
-          "The console window cannot be closed.",
-          "If in a chat room, you will leave the room.",
           NULL } } },
 
     { "/clear",
@@ -588,8 +654,8 @@ static struct cmd_t command_defs[] =
         cmd_privileges, parse_args, 1, 1, &cons_privileges_setting,
         { "/privileges on|off", "Show occupant privileges in chat rooms.",
         { "/privileges on|off",
-          "---------------------------",
-          "If enabled the room roster will be broken down my role, and role information will be showin in the room.",
+          "------------------",
+          "If enabled the room occupants panel will be grouped by role, and role information will be shown in the room.",
           NULL } } },
 
     { "/beep",
@@ -607,7 +673,7 @@ static struct cmd_t command_defs[] =
         { "/presence on|off", "Show the contacts presence in the titlebar.",
         { "/presence on|off",
           "----------------",
-          "Switch display of the contacts presence on or off.",
+          "Switch display of the contacts presence in the titlebar on or off.",
           NULL } } },
 
     { "/wrap",
@@ -615,71 +681,61 @@ static struct cmd_t command_defs[] =
         { "/wrap on|off", "Word wrapping.",
         { "/wrap on|off",
           "------------",
-          "Enable or disable word wrapping.",
+          "Enable or disable word wrapping in the main window.",
           NULL } } },
 
     { "/time",
-        cmd_time, parse_args, 1, 1, &cons_time_setting,
-        { "/time <format>", "Time display.",
-        { "/time <format>",
-          "---------------------",
-          "Configure the time format for the main window.",
+        cmd_time, parse_args, 1, 3, &cons_time_setting,
+        { "/time main|statusbar set|off [format]", "Time display.",
+        { "/time main|statusbar set|off [format]",
+          "---------------------------------",
+          "Configure time display preferences.",
+          "",
+          "main set <format>      : Change strftime format to <format> in main window.",
+          "main off               : Do not show time in main window.",
+          "statusbar set <format> : Change strftime format to <format> in statusbar.",
+          "statusbar off          : Do not show time in status bar.",
           NULL } } },
 
     { "/inpblock",
         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]", "Configure input blocking.",
         { "/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.",
+          "",
+          "timeout millis : Time to wait (1-1000) in milliseconds before reading input from the terminal buffer, default: 1000.",
+          "dynamic on|off : Start with 0 millis and dynamically increase up to timeout when no activity, default: on.",
           NULL } } },
 
     { "/notify",
         cmd_notify, parse_args, 2, 3, &cons_notify_setting,
-        { "/notify [type value]|[type setting value]", "Control various desktop noficiations.",
+        { "/notify [type value]|[type setting value]", "Control various desktop notifications.",
         { "/notify [type value]|[type setting value]",
           "-----------------------------------------",
-          "Settings for various desktop notifications where type is one of:",
-          "message         : Notificaitons for regular messages.",
-          "                : on|off",
-          "message current : Whether messages in the current window trigger notifications.",
-          "                : on|off",
-          "message text    : Show message text in message notifications.",
-          "                : on|off",
-          "room            : Notificaitons for chat room messages.",
-          "                : on|off|mention",
-          "room current    : Whether chat room messages in the current window trigger notifications.",
-          "                : on|off",
-          "room text       : Show message test in chat room message notifications.",
-          "                : on|off",
-          "remind          : Notification reminders of unread messages.",
-          "                : where value is the reminder period in seconds,",
-          "                : use 0 to disable.",
-          "typing          : Notifications when contacts are typing.",
-          "                : on|off",
-          "typing current  : Whether typing notifications are triggerd for the current window.",
-          "                : on|off",
-          "invite          : Notifications for chat room invites.",
-          "                : on|off",
-          "sub             : Notifications for subscription requests.",
-          "                : on|off",
-          "",
-          "Example : /notify message on        (enable message notifications)",
-          "Example : /notify message text on   (show message text in notifications)",
-          "Example : /notify room mention      (enable chat room notifications only on mention)",
-          "Example : /notify room current off  (disable room message notifications when window visible)",
-          "Example : /notify room text off     (do not show message text in chat room notifications)",
-          "Example : /notify remind 10         (remind every 10 seconds)",
-          "Example : /notify remind 0          (switch off reminders)",
-          "Example : /notify typing on         (enable typing notifications)",
-          "Example : /notify invite on         (enable chat room invite notifications)",
+          "Settings for various kinds of desktop notifications.",
+          "",
+          "message on|off         : Notifications for regular messages.",
+          "message current on|off : Whether messages in the current window trigger notifications.",
+          "message text on|off    : Show message text in message notifications.",
+          "room on|off|mention    : Notifications for chat room messages.",
+          "room current on|off    : Whether chat room messages in the current window trigger notifications.",
+          "room text on|off       : Show message text in chat room message notifications.",
+          "remind seconds         : Notification reminder period for unread messages, use 0 to disable.",
+          "typing on|off          : Notifications when contacts are typing.",
+          "typing current of|off  : Whether typing notifications are triggered for the current window.",
+          "invite on|off          : Notifications for chat room invites.",
+          "sub on|off             : Notifications for subscription requests.",
+          "",
+          "Example: /notify message on (enable message notifications)",
+          "Example: /notify message text on (show message text in notifications)",
+          "Example: /notify room mention (enable chat room notifications only on mention)",
+          "Example: /notify room current off (disable room message notifications when window visible)",
+          "Example: /notify room text off (do not show message text in chat room notifications)",
+          "Example: /notify remind 10 (remind every 10 seconds)",
+          "Example: /notify remind 0 (switch off reminders)",
+          "Example: /notify typing on (enable typing notifications)",
+          "Example: /notify invite on (enable chat room invite notifications)",
           NULL } } },
 
     { "/flash",
@@ -687,8 +743,7 @@ static struct cmd_t command_defs[] =
         { "/flash on|off", "Terminal flash on new messages.",
         { "/flash on|off",
           "-------------",
-          "Make the terminal flash when incoming messages are received.",
-          "The flash will only occur if you are not in the chat window associated with the user sending the message.",
+          "Make the terminal flash when incoming messages are received in another window.",
           "If the terminal doesn't support flashing, it may attempt to beep.",
           NULL } } },
 
@@ -717,7 +772,7 @@ static struct cmd_t command_defs[] =
           "The setting can be overridden by the -a (--account) command line option.",
           "",
           "Example: /autoconnect set jc@stuntteam.org (autoconnect with the specified account).",
-          "Example: /autoconnect off                  (disable autoconnect).",
+          "Example: /autoconnect off (disable autoconnect).",
           NULL } } },
 
     { "/vercheck",
@@ -725,18 +780,18 @@ static struct cmd_t command_defs[] =
         { "/vercheck [on|off]", "Check for a new release.",
         { "/vercheck [on|off]",
           "------------------",
-          "Without a parameter will check for a new release.",
-          "Switching on or off will enable/disable a version check when Profanity starts, and each time the /about command is run.",
+          "Enable/disable a version check when Profanity starts, and each time the /about command is run.",
           NULL  } } },
 
     { "/titlebar",
         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.",
+          "",
+          "show    : Show current logged in user, and unread messages in the title.",
+          "goodbye : Show a message in the title when exiting profanity.",
           NULL  } } },
 
     { "/mouse",
@@ -744,12 +799,7 @@ static struct cmd_t command_defs[] =
         { "/mouse on|off", "Use profanity mouse handling.",
         { "/mouse on|off",
           "-------------",
-          "If set to 'on', profanity will handle mouse actions, which enables scrolling the main window with the mouse wheel.",
-          "To select text, use the shift key while selcting an area.",
-          "If set to 'off', profanity leaves mouse handling to the terminal implementation.",
-          "This feature is experimental, certain mouse click events may occasionally freeze",
-          "Profanity until a key is pressed or another mouse event is received",
-          "The default is 'off'.",
+          "This feature is experimental, default is 'off'.",
           NULL } } },
 
     { "/alias",
@@ -758,12 +808,17 @@ static struct cmd_t command_defs[] =
         { "/alias add|remove|list [name value]",
           "-----------------------------------",
           "Add, remove or show command aliases.",
-          "The alias will be available as a command, the leading '/' will be added if not supplied.",
-          "Example : /alias add friends /who online friends",
-          "Example : /alias add /q /quit",
-          "Example : /alias a /away \"I'm in a meeting.\"",
-          "Example : /alias remove q",
-          "Example : /alias list",
+          "",
+          "add name value : Add a new command alias.",
+          "remove name    : Remove a command alias.",
+          "list           : List all aliases.",
+          "",
+          "Example: /alias add friends /who online friends",
+          "Example: /alias add /q /quit",
+          "Example: /alias a /away \"I'm in a meeting.\"",
+          "Example: /alias remove q",
+          "Example: /alias list",
+          "",
           "The above aliases will be available as /friends and /a",
           NULL } } },
 
@@ -792,29 +847,30 @@ static struct cmd_t command_defs[] =
         { "/states on|off", "Send chat states during a chat session.",
         { "/states on|off",
           "--------------",
-          "Sending of chat state notifications during chat sessions.",
-          "Such as whether you have become inactive, or have closed the chat window.",
+          "Send chat state notifications during chat sessions.",
           NULL } } },
 
     { "/otr",
         cmd_otr, parse_args, 1, 3, NULL,
-        { "/otr gen|myfp|theirfp|start|end|trust|untrust|log|warn|libver|policy|secret|question|answer", "Off The Record encryption commands.",
-        { "/otr gen|myfp|theirfp|start|end|trust|untrust|log|warn|libver|policy|secret|question|answer",
-          "-------------------------------------------------------------------------------------------",
-          "gen - Generate your private key.",
-          "myfp - Show your fingerprint.",
-          "theirfp - Show contacts fingerprint.",
-          "start [contact] - Start an OTR session with the contact, or the current recipient if in a chat window and no argument supplied.",
-          "end - End the current OTR session,",
-          "trust - Indicate that you have verified the contact's fingerprint.",
-          "untrust - Indicate the the contact's fingerprint is not verified,",
-          "log - How to log OTR messages, options are 'on', 'off' and 'redact', with redaction being the default.",
-          "warn - Show when unencrypted messaging is being used in the title bar, options are 'on' and 'off' with 'on' being the default.",
-          "libver - Show which version of the libotr library is being used.",
-          "policy - manual, opportunistic or always.",
-          "secret [secret]- Verify a contacts identity using a shared secret.",
-          "question [question] [answer] - Verify a contacts identity using a question and expected anwser, if the question has spaces, surround with double quotes.",
-          "answer [answer] - Respond to a question answer verification request with your answer.",
+        { "/otr command [args..]", "Off The Record encryption commands.",
+        { "/otr command [args..]",
+          "---------------------",
+          "Off The Record encryption commands.",
+          "",
+          "gen                                : Generate your private key.",
+          "myfp                               : Show your fingerprint.",
+          "theirfp                            : Show contacts fingerprint.",
+          "start [contact]                    : Start an OTR session with contact, or current recipient if omitted.",
+          "end                                : End the current OTR session,",
+          "trust                              : Indicate that you have verified the contact's fingerprint.",
+          "untrust                            : Indicate the the contact's fingerprint is not verified,",
+          "log on|off|redact                  : OTR message logging, default: redact.",
+          "warn on|off                        : Show in the titlebar when unencrypted messaging is being used.",
+          "libver                             : Show which version of the libotr library is being used.",
+          "policy manual|opportunistic|always : Set the global OTR policy.",
+          "secret [secret]                    : Verify a contacts identity using a shared secret.",
+          "question [question] [answer]       : Verify a contacts identity using a question and expected answer.",
+          "answer [answer]                    : Respond to a question answer verification request with your answer.",
           NULL } } },
 
     { "/outtype",
@@ -822,8 +878,7 @@ static struct cmd_t command_defs[] =
         { "/outtype on|off", "Send typing notification to recipient.",
         { "/outtype on|off",
           "---------------",
-          "Send an indication that you are typing to the chat recipient.",
-          "Chat states (/states) will be enabled if this setting is set.",
+          "Send typing notifications, chat states (/states) will be enabled if this setting is set.",
           NULL } } },
 
     { "/gone",
@@ -831,8 +886,7 @@ static struct cmd_t command_defs[] =
         { "/gone minutes", "Send 'gone' state to recipient after a period.",
         { "/gone minutes",
           "-------------",
-          "Send a 'gone' state to the recipient after the specified number of minutes."
-          "This indicates to the recipient's client that you have left the conversation.",
+          "Send a 'gone' state to the recipient after the specified number of minutes.",
           "A value of 0 will disable sending this chat state.",
           "Chat states (/states) will be enabled if this setting is set.",
           NULL } } },
@@ -848,15 +902,36 @@ static struct cmd_t command_defs[] =
 
     { "/log",
         cmd_log, parse_args, 1, 2, &cons_log_setting,
-        { "/log [property] [value]", "Manage system logging settings.",
-        { "/log [property] [value]",
-          "-----------------------",
-          "where   : Show the current log file location.",
-          "Property may be one of:",
-          "rotate  : Rotate log, accepts 'on' or 'off', defaults to 'on'.",
-          "maxsize : With rotate enabled, specifies the max log size, defaults to 1048580 (1MB).",
-          "shared  : Share logs between all instances, accepts 'on' or 'off', defaults to 'on'.",
-          NULL } } },
+        { "/log where|rotate|maxsize|shared [value]", "Manage system logging settings.",
+        { "/log where|rotate|maxsize|shared [value]",
+          "----------------------------------------",
+          "Manage profanity logging settings.",
+          "",
+          "where         : Show the current log file location.",
+          "rotate on|off : Rotate log, default on.",
+          "maxsize bytes : With rotate enabled, specifies the max log size, defaults to 1048580 (1MB).",
+          "shared on|off : Share logs between all instances, default: on.",
+          NULL } } },
+
+    { "/carbons",
+      cmd_carbons, parse_args, 1, 1, &cons_carbons_setting,
+      { "/carbons on|off", "Message carbons.",
+      { "/carbons on|off",
+        "---------------",
+        "Enable or disable message carbons.",
+        "The message carbons feature ensures that both sides of all conversations are shared with all the user's clients that implement this protocol.",
+        NULL  } } },
+
+    { "/receipts",
+      cmd_receipts, parse_args, 2, 2, &cons_receipts_setting,
+      { "/receipts send|request on|off", "Message delivery receipts.",
+      { "/receipts send|request on|off",
+        "-----------------------------",
+        "Enable or disable message delivery receipts. The interface will indicate when a message has been received.",
+        "",
+        "send on|off    : Enable or disable sending of delivery receipts.",
+        "request on|off : Enable or disable sending of delivery receipt requests.",
+        NULL  } } },
 
     { "/reconnect",
         cmd_reconnect, parse_args, 1, 1, &cons_reconnect_setting,
@@ -879,26 +954,25 @@ static struct cmd_t command_defs[] =
     { "/ping",
         cmd_ping, parse_args, 0, 1, NULL,
         { "/ping [target]", "Send ping IQ request.",
-        { "/ping [rarget]",
+        { "/ping [target]",
           "--------------",
-          "Sends an IQ ping stanza to the specificed target.",
-          "If no target is supplied, your chat server will be used.",
+          "Sends an IQ ping stanza to the specified target.",
+          "If no target is supplied, your chat server will be pinged.",
           NULL } } },
 
     { "/autoaway",
         cmd_autoaway, parse_args_with_freetext, 2, 2, &cons_autoaway_setting,
-        { "/autoaway setting value", "Set auto idle/away properties.",
-        { "/autoaway setting value",
-          "-----------------------",
-          "'setting' may be one of 'mode', 'time', 'message' or 'check', with the following values:",
-          "",
-          "mode    : idle - Sends idle time, whilst your status remains online.",
-          "          away - Sends an away presence.",
-          "          off - Disabled (default).",
-          "time    : Number of minutes before the presence change is sent, the default is 15.",
-          "message : Optional message to send with the presence change.",
-          "        : off - Disable message (default).",
-          "check   : on|off, when enabled, checks for activity and sends online presence, default is 'on'.",
+        { "/autoaway mode|time|message|check value", "Set auto idle/away properties.",
+        { "/autoaway mode|time|message|check value",
+          "---------------------------------------",
+          "Manage autoway properties.",
+          "",
+          "mode idle        : Sends idle time, status remains online.",
+          "mode away        : Sends an away presence.",
+          "mode off         : Disabled (default).",
+          "time minutes     : Number of minutes before the presence change is sent, default: 15.",
+          "message text|off : Optional message to send with the presence change, default: off (disabled).",
+          "check on|off     : When enabled, checks for activity and sends online presence, default: on.",
           "",
           "Example: /autoaway mode idle",
           "Example: /autoaway time 30",
@@ -911,9 +985,11 @@ static struct cmd_t command_defs[] =
         { "/priority value", "Set priority for the current account.",
         { "/priority value",
           "---------------",
-          "Set priority for the current account, presence will be sent when calling this command.",
-          "See the /account command for more specific priority settings per presence status.",
-          "value : Number between -128 and 127. Default value is 0.",
+          "Set priority for the current account.",
+          "",
+          "value : Number between -128 and 127, default: 0.",
+          "",
+          "See the /account command for specific priority settings per presence status.",
           NULL } } },
 
     { "/account",
@@ -922,6 +998,7 @@ static struct cmd_t command_defs[] =
         { "/account [command] [account] [property] [value]",
           "-----------------------------------------------",
           "Commands for creating and managing accounts.",
+          "",
           "list                         : List all accounts.",
           "show account                 : Show information about an account.",
           "enable account               : Enable the account, it will be used for autocomplete.",
@@ -933,43 +1010,38 @@ static struct cmd_t command_defs[] =
           "set account property value   : Set 'property' of 'account' to 'value'.",
           "clear account property value : Clear 'property' of 'account'.",
           "",
-          "When connected, the /account command can be called with no arguments, to show current account settings.",
-          "",
-          "The set command may use one of the following for 'property'.",
-          "jid              : The Jabber ID of the account, the account name will be used if this property is not set.",
-          "server           : The chat server, if different to the domainpart of the JID.",
-          "port             : The port used for connecting if not the default (5222, or 5223 for SSL).",
-          "status           : The presence status to use on login, use 'last' to use whatever your last status was.",
-          "online|chat|away",
-          "|xa|dnd          : Priority for the specified presence.",
-          "resource         : The resource to be used.",
-          "password         : Password for the account, note this is currently stored in plaintext if set.",
-          "eval_password    : Shell command evaluated to retrieve password for the account.  Can be used to retrieve password from keyring.",
-          "muc              : The default MUC chat service to use.",
-          "nick             : The default nickname to use when joining chat rooms.",
-          "otr              : Override global OTR policy for this account: manual, opportunistic or always.",
-          "",
-          "The clear command works for password, port and server",
-          "",
-          "Example : /account add work",
-          "        : /account set work jid me@chatty",
-          "        : /account set work server talk.chat.com",
-          "        : /account set work port 5111",
-          "        : /account set work resource desktop",
-          "        : /account set work muc chatservice.mycompany.com",
-          "        : /account set work nick dennis",
-          "        : /account set work status dnd",
-          "        : /account set work dnd -1",
-          "        : /account set work online 10",
-          "        : /account rename work gtalk",
+          "Account properties.",
+          "",
+          "jid                     : The Jabber ID of the account, account name will be used if not set.",
+          "server                  : The chat server, if different to the domainpart of the JID.",
+          "port                    : The port used for connecting if not the default (5222, or 5223 for SSL).",
+          "status                  : The presence status to use on login, use 'last' to use your last status before logging out.",
+          "online|chat|away|xa|dnd : Priority for the specified presence.",
+          "resource                : The resource to be used.",
+          "password                : Password for the account, note this is currently stored in plaintext if set.",
+          "eval_password           : Shell command evaluated to retrieve password for the account. Can be used to retrieve password from keyring.",
+          "muc                     : The default MUC chat service to use.",
+          "nick                    : The default nickname to use when joining chat rooms.",
+          "otr                     : Override global OTR policy for this account: manual, opportunistic or always.",
+          "",
+          "Example: /account add me",
+          "Example: /account set me jid me@chatty",
+          "Example: /account set me server talk.chat.com",
+          "Example: /account set me port 5111",
+          "Example: /account set me muc chatservice.mycompany.com",
+          "Example: /account set me nick dennis",
+          "Example: /account set me status dnd",
+          "Example: /account set me dnd -1",
+          "Example: /account rename me gtalk",
           NULL  } } },
 
     { "/prefs",
         cmd_prefs, parse_args, 0, 1, NULL,
-        { "/prefs [area]", "Show configuration.",
-        { "/prefs [area]",
-          "-------------",
-          "Area is one of:",
+        { "/prefs [ui|desktop|chat|log|conn|presence]", "Show configuration.",
+        { "/prefs [ui|desktop|chat|log|conn|presence]",
+          "------------------------------------------",
+          "Show preferences for different areas of functionality.",
+          "",
           "ui       : User interface preferences.",
           "desktop  : Desktop notification preferences.",
           "chat     : Chat state preferences.",
@@ -977,22 +1049,22 @@ static struct cmd_t command_defs[] =
           "conn     : Connection handling preferences.",
           "presence : Chat presence preferences.",
           "",
-          "No argument shows all categories.",
+          "No argument shows all preferences.",
           NULL } } },
 
     { "/theme",
         cmd_theme, parse_args, 1, 2, &cons_theme_setting,
-        { "/theme list|set|colours [theme-name]", "Change colour theme.",
-        { "/theme list|set|colours [theme-name]",
-          "------------------------------------",
-          "Change the colour settings used.",
+        { "/theme list|load|colours [theme-name]", "Change colour theme.",
+        { "/theme list|load|colours [theme-name]",
+          "-------------------------------------",
+          "Load a theme, includes colours and UI options.",
           "",
-          "list           : List all available themes.",
-          "set theme-name : Load the named theme.\"default\" will reset to the default colours.",
-          "colours        : Show the colour values as rendered by the terminal.",
+          "list            : List all available themes.",
+          "load theme-name : Load the named theme. 'default' will reset to the default theme.",
+          "colours         : Show the colour values as rendered by the terminal.",
           "",
-          "Example : /theme list",
-          "Example : /theme set mycooltheme",
+          "Example: /theme list",
+          "Example: /theme load mycooltheme",
           NULL } } },
 
 
@@ -1001,12 +1073,23 @@ static struct cmd_t command_defs[] =
         { "/statuses console|chat|muc setting", "Set preferences for presence change messages.",
         { "/statuses console|chat|muc setting",
           "----------------------------------",
-          "Configure how presence changes are displayed in various windows.",
-          "Settings:",
-          "  all - Show all presence changes.",
-          "  online - Show only online/offline changes.",
-          "  none - Don't show any presence changes.",
+          "Configure which presence changes are displayed in various windows.",
+          "",
+          "console : Configure what is displayed in the console window.",
+          "chat    : Configure what is displayed in chat windows.",
+          "muc     : Configure what is displayed in chat room windows.",
+          "",
+          "Available options are:",
+          "",
+          "all    : Show all presence changes.",
+          "online : Show only online/offline changes.",
+          "none   : Don't show any presence changes.",
+          "",
           "The default is 'all' for all windows.",
+          "",
+          "Example: /statuses console none",
+          "Example: /statuses chat online",
+          "Example: /statuses muc all",
           NULL } } },
 
     { "/xmlconsole",
@@ -1019,57 +1102,52 @@ static struct cmd_t command_defs[] =
 
     { "/away",
         cmd_away, parse_args_with_freetext, 0, 1, NULL,
-        { "/away [msg]", "Set status to away.",
-        { "/away [msg]",
-          "-----------",
+        { "/away [message]", "Set status to away.",
+        { "/away [message]",
+          "---------------",
           "Set your status to 'away' with the optional message.",
-          "Your current status can be found in the top right of the screen.",
           "",
-          "Example : /away Gone for lunch",
+          "Example: /away Gone for lunch",
           NULL } } },
 
     { "/chat",
         cmd_chat, parse_args_with_freetext, 0, 1, NULL,
-        { "/chat [msg]", "Set status to chat (available for chat).",
-        { "/chat [msg]",
-          "-----------",
+        { "/chat [message]", "Set status to chat (available for chat).",
+        { "/chat [message]",
+          "---------------",
           "Set your status to 'chat', meaning 'available for chat', with the optional message.",
-          "Your current status can be found in the top right of the screen.",
           "",
-          "Example : /chat Please talk to me!",
+          "Example: /chat Please talk to me!",
           NULL } } },
 
     { "/dnd",
         cmd_dnd, parse_args_with_freetext, 0, 1, NULL,
-        { "/dnd [msg]", "Set status to dnd (do not disturb).",
-        { "/dnd [msg]",
-          "----------",
+        { "/dnd [message]", "Set status to dnd (do not disturb).",
+        { "/dnd [message]",
+          "--------------",
           "Set your status to 'dnd', meaning 'do not disturb', with the optional message.",
-          "Your current status can be found in the top right of the screen.",
           "",
-          "Example : /dnd I'm in the zone",
+          "Example: /dnd I'm in the zone",
           NULL } } },
 
     { "/online",
         cmd_online, parse_args_with_freetext, 0, 1, NULL,
-        { "/online [msg]", "Set status to online.",
-        { "/online [msg]",
-          "-------------",
+        { "/online [message]", "Set status to online.",
+        { "/online [message]",
+          "-----------------",
           "Set your status to 'online' with the optional message.",
-          "Your current status can be found in the top right of the screen.",
           "",
-          "Example : /online Up the Irons!",
+          "Example: /online Up the Irons!",
           NULL } } },
 
     { "/xa",
         cmd_xa, parse_args_with_freetext, 0, 1, NULL,
-        { "/xa [msg]", "Set status to xa (extended away).",
-        { "/xa [msg]",
-          "---------",
+        { "/xa [message]", "Set status to xa (extended away).",
+        { "/xa [message]",
+          "-------------",
           "Set your status to 'xa', meaning 'extended away', with the optional message.",
-          "Your current status can be found in the top right of the screen.",
           "",
-          "Example : /xa This meeting is going to be a long one",
+          "Example: /xa This meeting is going to be a long one",
           NULL } } },
 };
 
@@ -1121,8 +1199,12 @@ static Autocomplete form_ac;
 static Autocomplete form_field_multi_ac;
 static Autocomplete occupants_ac;
 static Autocomplete occupants_default_ac;
+static Autocomplete occupants_show_ac;
+static Autocomplete time_ac;
+static Autocomplete time_statusbar_ac;
 static Autocomplete resource_ac;
 static Autocomplete inpblock_ac;
+static Autocomplete receipts_ac;
 
 /*
  * Initialise command autocompleter and history
@@ -1140,7 +1222,7 @@ cmd_init(void)
     autocomplete_add(help_ac, "basic");
     autocomplete_add(help_ac, "chatting");
     autocomplete_add(help_ac, "groupchat");
-    autocomplete_add(help_ac, "presence");
+    autocomplete_add(help_ac, "presences");
     autocomplete_add(help_ac, "contacts");
     autocomplete_add(help_ac, "service");
     autocomplete_add(help_ac, "settings");
@@ -1163,7 +1245,7 @@ cmd_init(void)
     // load aliases
     GList *aliases = prefs_get_aliases();
     GList *curr = aliases;
-    while (curr != NULL) {
+    while (curr) {
         ProfAlias *alias = curr->data;
         GString *ac_alias = g_string_new("/");
         g_string_append(ac_alias, alias->name);
@@ -1243,8 +1325,8 @@ cmd_init(void)
     autocomplete_add(autoconnect_ac, "off");
 
     theme_ac = autocomplete_new();
+    autocomplete_add(theme_ac, "load");
     autocomplete_add(theme_ac, "list");
-    autocomplete_add(theme_ac, "set");
     autocomplete_add(theme_ac, "colours");
 
     disco_ac = autocomplete_new();
@@ -1460,6 +1542,20 @@ cmd_init(void)
     autocomplete_add(occupants_default_ac, "show");
     autocomplete_add(occupants_default_ac, "hide");
 
+    occupants_show_ac = autocomplete_new();
+    autocomplete_add(occupants_show_ac, "jid");
+
+    time_ac = autocomplete_new();
+    autocomplete_add(time_ac, "minutes");
+    autocomplete_add(time_ac, "seconds");
+    autocomplete_add(time_ac, "off");
+    autocomplete_add(time_ac, "statusbar");
+
+    time_statusbar_ac = autocomplete_new();
+    autocomplete_add(time_statusbar_ac, "minutes");
+    autocomplete_add(time_statusbar_ac, "seconds");
+    autocomplete_add(time_statusbar_ac, "off");
+
     resource_ac = autocomplete_new();
     autocomplete_add(resource_ac, "set");
     autocomplete_add(resource_ac, "off");
@@ -1469,6 +1565,10 @@ cmd_init(void)
     inpblock_ac = autocomplete_new();
     autocomplete_add(inpblock_ac, "timeout");
     autocomplete_add(inpblock_ac, "dynamic");
+
+    receipts_ac = autocomplete_new();
+    autocomplete_add(receipts_ac, "send");
+    autocomplete_add(receipts_ac, "request");
 }
 
 void
@@ -1522,8 +1622,12 @@ cmd_uninit(void)
     autocomplete_free(form_field_multi_ac);
     autocomplete_free(occupants_ac);
     autocomplete_free(occupants_default_ac);
+    autocomplete_free(occupants_show_ac);
+    autocomplete_free(time_ac);
+    autocomplete_free(time_statusbar_ac);
     autocomplete_free(resource_ac);
     autocomplete_free(inpblock_ac);
+    autocomplete_free(receipts_ac);
 }
 
 gboolean
@@ -1539,7 +1643,7 @@ cmd_exists(char *cmd)
 void
 cmd_autocomplete_add(char *value)
 {
-    if (commands_ac != NULL) {
+    if (commands_ac) {
         autocomplete_add(commands_ac, value);
     }
 }
@@ -1547,41 +1651,45 @@ cmd_autocomplete_add(char *value)
 void
 cmd_autocomplete_add_form_fields(DataForm *form)
 {
-    if (form) {
-        GSList *fields = autocomplete_create_list(form->tag_ac);
-        GSList *curr_field = fields;
-        while (curr_field) {
-            GString *field_str = g_string_new("/");
-            g_string_append(field_str, curr_field->data);
-            cmd_autocomplete_add(field_str->str);
-            g_string_free(field_str, TRUE);
-            curr_field = g_slist_next(curr_field);
-        }
-        g_slist_free_full(fields, free);
+    if (form == NULL) {
+        return;
     }
+
+    GSList *fields = autocomplete_create_list(form->tag_ac);
+    GSList *curr_field = fields;
+    while (curr_field) {
+        GString *field_str = g_string_new("/");
+        g_string_append(field_str, curr_field->data);
+        cmd_autocomplete_add(field_str->str);
+        g_string_free(field_str, TRUE);
+        curr_field = g_slist_next(curr_field);
+    }
+    g_slist_free_full(fields, free);
 }
 
 void
 cmd_autocomplete_remove_form_fields(DataForm *form)
 {
-    if (form) {
-        GSList *fields = autocomplete_create_list(form->tag_ac);
-        GSList *curr_field = fields;
-        while (curr_field) {
-            GString *field_str = g_string_new("/");
-            g_string_append(field_str, curr_field->data);
-            cmd_autocomplete_remove(field_str->str);
-            g_string_free(field_str, TRUE);
-            curr_field = g_slist_next(curr_field);
-        }
-        g_slist_free_full(fields, free);
+    if (form == NULL) {
+        return;
     }
+
+    GSList *fields = autocomplete_create_list(form->tag_ac);
+    GSList *curr_field = fields;
+    while (curr_field) {
+        GString *field_str = g_string_new("/");
+        g_string_append(field_str, curr_field->data);
+        cmd_autocomplete_remove(field_str->str);
+        g_string_free(field_str, TRUE);
+        curr_field = g_slist_next(curr_field);
+    }
+    g_slist_free_full(fields, free);
 }
 
 void
 cmd_autocomplete_remove(char *value)
 {
-    if (commands_ac != NULL) {
+    if (commands_ac) {
         autocomplete_remove(commands_ac, value);
     }
 }
@@ -1589,7 +1697,7 @@ cmd_autocomplete_remove(char *value)
 void
 cmd_alias_add(char *value)
 {
-    if (aliases_ac != NULL) {
+    if (aliases_ac) {
         autocomplete_add(aliases_ac, value);
     }
 }
@@ -1597,7 +1705,7 @@ cmd_alias_add(char *value)
 void
 cmd_alias_remove(char *value)
 {
-    if (aliases_ac != NULL) {
+    if (aliases_ac) {
         autocomplete_remove(aliases_ac, value);
     }
 }
@@ -1610,7 +1718,7 @@ cmd_autocomplete(const char * const input)
     if ((strncmp(input, "/", 1) == 0) && (!str_contains(input, strlen(input), ' '))) {
         char *found = NULL;
         found = autocomplete_complete(commands_ac, input, TRUE);
-        if (found != NULL) {
+        if (found) {
             return found;
         }
 
@@ -1650,8 +1758,8 @@ cmd_reset_autocomplete()
     autocomplete_reset(autoaway_mode_ac);
     autocomplete_reset(autoconnect_ac);
     autocomplete_reset(theme_ac);
-    if (theme_load_ac != NULL) {
-        autocomplete_reset(theme_load_ac);
+    if (theme_load_ac) {
+        autocomplete_free(theme_load_ac);
         theme_load_ac = NULL;
     }
     autocomplete_reset(account_ac);
@@ -1686,8 +1794,12 @@ cmd_reset_autocomplete()
     autocomplete_reset(form_field_multi_ac);
     autocomplete_reset(occupants_ac);
     autocomplete_reset(occupants_default_ac);
+    autocomplete_reset(occupants_show_ac);
+    autocomplete_reset(time_ac);
+    autocomplete_reset(time_statusbar_ac);
     autocomplete_reset(resource_ac);
     autocomplete_reset(inpblock_ac);
+    autocomplete_reset(receipts_ac);
 
     if (ui_current_win_type() == WIN_CHAT) {
         ProfChatWin *chatwin = wins_get_current_chat();
@@ -1724,11 +1836,6 @@ cmd_process_input(char *inp)
     gboolean result = FALSE;
     g_strstrip(inp);
 
-    // add line to history if something typed
-    if (strlen(inp) > 0) {
-        ui_inp_history_append(inp);
-    }
-
     // just carry on if no input
     if (strlen(inp) == 0) {
         result = TRUE;
@@ -1742,7 +1849,7 @@ cmd_process_input(char *inp)
 
     // call a default handler if input didn't start with '/'
     } else {
-        result = _cmd_execute_default(inp);
+        result = cmd_execute_default(inp);
     }
 
     return result;
@@ -1782,7 +1889,7 @@ _cmd_execute(const char * const command, const char * const inp)
     Command *cmd = g_hash_table_lookup(commands, command);
     gboolean result = FALSE;
 
-    if (cmd != NULL) {
+    if (cmd) {
         gchar **args = cmd->parser(inp, cmd->min_args, cmd->max_args, &result);
         if (result == FALSE) {
             ui_invalid_command_usage(cmd->help.usage, cmd->setting_func);
@@ -1794,145 +1901,15 @@ _cmd_execute(const char * const command, const char * const inp)
         }
     } else {
         gboolean ran_alias = FALSE;
-        gboolean alias_result = _cmd_execute_alias(inp, &ran_alias);
+        gboolean alias_result = cmd_execute_alias(inp, &ran_alias);
         if (!ran_alias) {
-            return _cmd_execute_default(inp);
+            return cmd_execute_default(inp);
         } else {
             return alias_result;
         }
     }
 }
 
-static gboolean
-_cmd_execute_alias(const char * const inp, gboolean *ran)
-{
-    if (inp[0] != '/') {
-        ran = FALSE;
-        return TRUE;
-    } else {
-        char *alias = strdup(inp+1);
-        char *value = prefs_get_alias(alias);
-        free(alias);
-        if (value != NULL) {
-            *ran = TRUE;
-            return cmd_process_input(value);
-        } else {
-            *ran = FALSE;
-            return TRUE;
-        }
-    }
-}
-
-static gboolean
-_cmd_execute_default(const char * inp)
-{
-    jabber_conn_status_t status = jabber_get_connection_status();
-
-    // handle escaped commands - treat as normal message
-    if (g_str_has_prefix(inp, "//")) {
-        inp++;
-
-    // handle unknown commands
-    } else if ((inp[0] == '/') && (!g_str_has_prefix(inp, "/me "))) {
-        cons_show("Unknown command: %s", inp);
-        cons_alert();
-        return TRUE;
-    }
-
-    win_type_t win_type = ui_current_win_type();
-    switch (win_type)
-    {
-        case WIN_MUC:
-            if (status != JABBER_CONNECTED) {
-                ui_current_print_line("You are not currently connected.");
-            } else {
-                ProfMucWin *mucwin = wins_get_current_muc();
-                message_send_groupchat(mucwin->roomjid, inp);
-            }
-            break;
-
-        case WIN_CHAT:
-            if (status != JABBER_CONNECTED) {
-                ui_current_print_line("You are not currently connected.");
-            } else {
-                ProfWin *current = wins_get_current();
-                ProfChatWin *chatwin = (ProfChatWin*)current;
-                assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-#ifdef HAVE_LIBOTR
-                prof_otrpolicy_t policy = otr_get_policy(chatwin->barejid);
-                if (policy == PROF_OTRPOLICY_ALWAYS && !otr_is_secure(chatwin->barejid)) {
-                    cons_show_error("Failed to send message. Please check OTR policy");
-                    return TRUE;
-                }
-                if (otr_is_secure(chatwin->barejid)) {
-                    char *encrypted = otr_encrypt_message(chatwin->barejid, inp);
-                    if (encrypted != NULL) {
-                        message_send_chat(chatwin->barejid, encrypted);
-                        otr_free_message(encrypted);
-                        if (prefs_get_boolean(PREF_CHLOG)) {
-                            const char *jid = jabber_get_fulljid();
-                            Jid *jidp = jid_create(jid);
-                            char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
-                            if (strcmp(pref_otr_log, "on") == 0) {
-                                chat_log_chat(jidp->barejid, chatwin->barejid, inp, PROF_OUT_LOG, NULL);
-                            } else if (strcmp(pref_otr_log, "redact") == 0) {
-                                chat_log_chat(jidp->barejid, chatwin->barejid, "[redacted]", PROF_OUT_LOG, NULL);
-                            }
-                            prefs_free_string(pref_otr_log);
-                            jid_destroy(jidp);
-                        }
-
-                        ui_outgoing_chat_msg("me", chatwin->barejid, inp);
-                    } else {
-                        cons_show_error("Failed to send message.");
-                    }
-                } else {
-                    message_send_chat(chatwin->barejid, inp);
-                    if (prefs_get_boolean(PREF_CHLOG)) {
-                        const char *jid = jabber_get_fulljid();
-                        Jid *jidp = jid_create(jid);
-                        chat_log_chat(jidp->barejid, chatwin->barejid, inp, PROF_OUT_LOG, NULL);
-                        jid_destroy(jidp);
-                    }
-
-                    ui_outgoing_chat_msg("me", chatwin->barejid, inp);
-                }
-#else
-                message_send_chat(chatwin->barejid, inp);
-                if (prefs_get_boolean(PREF_CHLOG)) {
-                    const char *jid = jabber_get_fulljid();
-                    Jid *jidp = jid_create(jid);
-                    chat_log_chat(jidp->barejid, chatwin->barejid, inp, PROF_OUT_LOG, NULL);
-                    jid_destroy(jidp);
-                }
-
-                ui_outgoing_chat_msg("me", chatwin->barejid, inp);
-#endif
-            }
-            break;
-
-        case WIN_PRIVATE:
-            if (status != JABBER_CONNECTED) {
-                ui_current_print_line("You are not currently connected.");
-            } else {
-                ProfPrivateWin *privatewin = wins_get_current_private();
-                message_send_private(privatewin->fulljid, inp);
-                ui_outgoing_private_msg("me", privatewin->fulljid, inp);
-            }
-            break;
-
-        case WIN_CONSOLE:
-        case WIN_XML:
-            cons_show("Unknown command: %s", inp);
-            break;
-
-        default:
-            break;
-    }
-
-    return TRUE;
-}
-
 static char *
 _cmd_complete_parameters(const char * const input)
 {
@@ -1942,7 +1919,7 @@ _cmd_complete_parameters(const char * const input)
     // autocomplete boolean settings
     gchar *boolean_choices[] = { "/beep", "/intype", "/states", "/outtype",
         "/flash", "/splash", "/chlog", "/grlog", "/mouse", "/history",
-        "/vercheck", "/privileges", "/presence", "/wrap" };
+        "/vercheck", "/privileges", "/presence", "/wrap", "/carbons" };
 
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
         result = autocomplete_param_with_func(input, boolean_choices[i], prefs_autocomplete_boolean_choice);
@@ -1958,23 +1935,31 @@ _cmd_complete_parameters(const char * const input)
         if (nick_ac) {
             gchar *nick_choices[] = { "/msg", "/info", "/caps", "/status", "/software" } ;
 
+            // Remove quote character before and after names when doing autocomplete
+            char *unquoted = strip_arg_quotes(input);
             for (i = 0; i < ARRAY_SIZE(nick_choices); i++) {
-                result = autocomplete_param_with_ac(input, nick_choices[i], nick_ac, TRUE);
+                result = autocomplete_param_with_ac(unquoted, nick_choices[i], nick_ac, TRUE);
                 if (result) {
+                    free(unquoted);
                     return result;
                 }
             }
+            free(unquoted);
         }
 
     // otherwise autocomplete using roster
     } else {
         gchar *contact_choices[] = { "/msg", "/info", "/status" };
+        // Remove quote character before and after names when doing autocomplete
+        char *unquoted = strip_arg_quotes(input);
         for (i = 0; i < ARRAY_SIZE(contact_choices); i++) {
-            result = autocomplete_param_with_func(input, contact_choices[i], roster_contact_autocomplete);
+            result = autocomplete_param_with_func(unquoted, contact_choices[i], roster_contact_autocomplete);
             if (result) {
+                free(unquoted);
                 return result;
             }
         }
+        free(unquoted);
 
         gchar *resource_choices[] = { "/caps", "/software", "/ping" };
         for (i = 0; i < ARRAY_SIZE(resource_choices); i++) {
@@ -1998,8 +1983,8 @@ _cmd_complete_parameters(const char * const input)
         }
     }
 
-    gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins", "/subject", "/room", };
-    Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, subject_ac, room_ac, };
+    gchar *cmds[] = { "/help", "/prefs", "/disco", "/close", "/wins", "/subject", "/room" };
+    Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, subject_ac, room_ac };
 
     for (i = 0; i < ARRAY_SIZE(cmds); i++) {
         result = autocomplete_param_with_ac(input, cmds[i], completers[i], TRUE);
@@ -2034,6 +2019,8 @@ _cmd_complete_parameters(const char * const input)
     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);
+    g_hash_table_insert(ac_funcs, "/time",          _time_autocomplete);
+    g_hash_table_insert(ac_funcs, "/receipts",      _receipts_autocomplete);
 
     int len = strlen(input);
     char parsed[len+1];
@@ -2049,7 +2036,7 @@ _cmd_complete_parameters(const char * const input)
     parsed[i] = '\0';
 
     char * (*ac_func)(const char * const) = g_hash_table_lookup(ac_funcs, parsed);
-    if (ac_func != NULL) {
+    if (ac_func) {
         result = ac_func(input);
         if (result) {
             g_hash_table_destroy(ac_funcs);
@@ -2073,15 +2060,15 @@ _sub_autocomplete(const char * const input)
 {
     char *result = NULL;
     result = autocomplete_param_with_func(input, "/sub allow", presence_sub_request_find);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/sub deny", presence_sub_request_find);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/sub", sub_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2096,7 +2083,7 @@ _who_autocomplete(const char * const input)
 
     if (win_type == WIN_MUC) {
         result = autocomplete_param_with_ac(input, "/who", who_room_ac, TRUE);
-        if (result != NULL) {
+        if (result) {
             return result;
         }
     } else {
@@ -2107,13 +2094,13 @@ _who_autocomplete(const char * const input)
 
         for (i = 0; i < ARRAY_SIZE(group_commands); i++) {
             result = autocomplete_param_with_func(input, group_commands[i], roster_group_autocomplete);
-            if (result != NULL) {
+            if (result) {
                 return result;
             }
         }
 
         result = autocomplete_param_with_ac(input, "/who", who_roster_ac, TRUE);
-        if (result != NULL) {
+        if (result) {
             return result;
         }
     }
@@ -2126,31 +2113,31 @@ _roster_autocomplete(const char * const input)
 {
     char *result = NULL;
     result = autocomplete_param_with_func(input, "/roster nick", roster_barejid_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/roster clearnick", roster_barejid_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/roster remove", roster_barejid_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/roster show", roster_option_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/roster hide", roster_option_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/roster by", roster_by_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/roster", roster_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2162,28 +2149,28 @@ _group_autocomplete(const char * const input)
 {
     char *result = NULL;
     result = autocomplete_param_with_func(input, "/group show", roster_group_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_no_with_func(input, "/group add", 4, roster_contact_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_no_with_func(input, "/group remove", 4, roster_contact_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/group add", roster_group_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/group remove", roster_group_autocomplete);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/group", group_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2244,7 +2231,7 @@ _bookmark_autocomplete(const char * const input)
             found = autocomplete_param_with_ac(input, beginning->str, bookmark_property_ac, TRUE);
         }
         g_string_free(beginning, TRUE);
-        if (found != NULL) {
+        if (found) {
             g_strfreev(args);
             return found;
         }
@@ -2253,15 +2240,15 @@ _bookmark_autocomplete(const char * const input)
     g_strfreev(args);
 
     found = autocomplete_param_with_func(input, "/bookmark remove", bookmark_find);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
     found = autocomplete_param_with_func(input, "/bookmark join", bookmark_find);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
     found = autocomplete_param_with_func(input, "/bookmark update", bookmark_find);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2276,42 +2263,42 @@ _notify_autocomplete(const char * const input)
     char *result = NULL;
 
     result = autocomplete_param_with_func(input, "/notify room current", prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_func(input, "/notify message current", prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_func(input, "/notify typing current", prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_func(input, "/notify room text", prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_func(input, "/notify message text", prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/notify room", notify_room_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/notify message", notify_message_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/notify typing", notify_typing_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2319,13 +2306,13 @@ _notify_autocomplete(const char * const input)
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
         result = autocomplete_param_with_func(input, boolean_choices[i],
             prefs_autocomplete_boolean_choice);
-        if (result != NULL) {
+        if (result) {
             return result;
         }
     }
 
     result = autocomplete_param_with_ac(input, "/notify", notify_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2338,16 +2325,16 @@ _autoaway_autocomplete(const char * const input)
     char *result = NULL;
 
     result = autocomplete_param_with_ac(input, "/autoaway mode", autoaway_mode_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/autoaway check",
         prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/autoaway", autoaway_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2361,16 +2348,16 @@ _log_autocomplete(const char * const input)
 
     result = autocomplete_param_with_func(input, "/log rotate",
         prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_func(input, "/log shared",
         prefs_autocomplete_boolean_choice);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
     result = autocomplete_param_with_ac(input, "/log", log_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2383,12 +2370,12 @@ _autoconnect_autocomplete(const char * const input)
     char *result = NULL;
 
     result = autocomplete_param_with_func(input, "/autoconnect set", accounts_find_enabled);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/autoconnect", autoconnect_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2401,12 +2388,12 @@ _otr_autocomplete(const char * const input)
     char *found = NULL;
 
     found = autocomplete_param_with_func(input, "/otr start", roster_contact_autocomplete);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/otr log", otr_log_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2421,7 +2408,7 @@ _otr_autocomplete(const char * const input)
 
         found = autocomplete_param_with_func(input, beginning->str, roster_contact_autocomplete);
         g_string_free(beginning, TRUE);
-        if (found != NULL) {
+        if (found) {
             g_strfreev(args);
             return found;
         }
@@ -2430,18 +2417,18 @@ _otr_autocomplete(const char * const input)
     g_strfreev(args);
 
     found = autocomplete_param_with_ac(input, "/otr policy", otr_policy_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_func(input, "/otr warn",
         prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/otr", otr_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2452,24 +2439,25 @@ static char *
 _theme_autocomplete(const char * const input)
 {
     char *result = NULL;
-    if ((strncmp(input, "/theme set ", 11) == 0) && (strlen(input) > 11)) {
+    if ((strncmp(input, "/theme load ", 12) == 0) && (strlen(input) > 12)) {
         if (theme_load_ac == NULL) {
             theme_load_ac = autocomplete_new();
             GSList *themes = theme_list();
-            while (themes != NULL) {
-                autocomplete_add(theme_load_ac, themes->data);
-                themes = g_slist_next(themes);
+            GSList *curr = themes;
+            while (curr) {
+                autocomplete_add(theme_load_ac, curr->data);
+                curr = g_slist_next(curr);
             }
-            g_slist_free(themes);
+            g_slist_free_full(themes, g_free);
             autocomplete_add(theme_load_ac, "default");
         }
-        result = autocomplete_param_with_ac(input, "/theme set", theme_load_ac, TRUE);
-        if (result != NULL) {
+        result = autocomplete_param_with_ac(input, "/theme load", theme_load_ac, TRUE);
+        if (result) {
             return result;
         }
     }
     result = autocomplete_param_with_ac(input, "/theme", theme_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2488,24 +2476,24 @@ _resource_autocomplete(const char * const input)
         if (contact) {
             Autocomplete ac = p_contact_resource_ac(contact);
             found = autocomplete_param_with_ac(input, "/resource set", ac, FALSE);
-            if (found != NULL) {
+            if (found) {
                 return found;
             }
         }
     }
 
     found = autocomplete_param_with_func(input, "/resource title", prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_func(input, "/resource message", prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/resource", resource_ac, FALSE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2518,17 +2506,17 @@ _titlebar_autocomplete(const char * const input)
     char *found = NULL;
 
     found = autocomplete_param_with_func(input, "/titlebar show", prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_func(input, "/titlebar goodbye", prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/titlebar", titlebar_ac, FALSE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2541,12 +2529,12 @@ _inpblock_autocomplete(const char * const input)
     char *found = NULL;
 
     found = autocomplete_param_with_func(input, "/inpblock dynamic", prefs_autocomplete_boolean_choice);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/inpblock", inpblock_ac, FALSE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2567,13 +2555,13 @@ _form_autocomplete(const char * const input)
     DataForm *form = confwin->form;
     if (form) {
         found = autocomplete_param_with_ac(input, "/form help", form->tag_ac, TRUE);
-        if (found != NULL) {
+        if (found) {
             return found;
         }
     }
 
     found = autocomplete_param_with_ac(input, "/form", form_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2656,13 +2644,51 @@ _occupants_autocomplete(const char * const input)
 {
     char *found = NULL;
 
+    found = autocomplete_param_with_ac(input, "/occupants default show", occupants_show_ac, TRUE);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/occupants default hide", occupants_show_ac, TRUE);
+    if (found) {
+        return found;
+    }
+
     found = autocomplete_param_with_ac(input, "/occupants default", occupants_default_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/occupants show", occupants_show_ac, TRUE);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/occupants hide", occupants_show_ac, TRUE);
+    if (found) {
         return found;
     }
 
     found = autocomplete_param_with_ac(input, "/occupants", occupants_ac, TRUE);
-    if (found != NULL) {
+    if (found) {
+        return found;
+    }
+
+    return NULL;
+}
+
+static char *
+_time_autocomplete(const char * const input)
+{
+    char *found = NULL;
+
+    found = autocomplete_param_with_ac(input, "/time statusbar", time_statusbar_ac, TRUE);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/time", time_ac, TRUE);
+    if (found) {
         return found;
     }
 
@@ -2678,9 +2704,9 @@ _kick_autocomplete(const char * const input)
         ProfMucWin *mucwin = wins_get_current_muc();
         Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
 
-        if (nick_ac != NULL) {
+        if (nick_ac) {
             result = autocomplete_param_with_ac(input, "/kick", nick_ac, TRUE);
-            if (result != NULL) {
+            if (result) {
                 return result;
             }
         }
@@ -2698,9 +2724,9 @@ _ban_autocomplete(const char * const input)
         ProfMucWin *mucwin = wins_get_current_muc();
         Autocomplete jid_ac = muc_roster_jid_ac(mucwin->roomjid);
 
-        if (jid_ac != NULL) {
+        if (jid_ac) {
             result = autocomplete_param_with_ac(input, "/ban", jid_ac, TRUE);
-            if (result != NULL) {
+            if (result) {
                 return result;
             }
         }
@@ -2729,7 +2755,7 @@ _affiliation_autocomplete(const char * const input)
 
             result = autocomplete_param_with_ac(input, beginning->str, jid_ac, TRUE);
             g_string_free(beginning, TRUE);
-            if (result != NULL) {
+            if (result) {
                 g_strfreev(args);
                 return result;
             }
@@ -2739,17 +2765,17 @@ _affiliation_autocomplete(const char * const input)
     }
 
     result = autocomplete_param_with_ac(input, "/affiliation set", affiliation_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/affiliation list", affiliation_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/affiliation", privilege_cmd_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2776,7 +2802,7 @@ _role_autocomplete(const char * const input)
 
             result = autocomplete_param_with_ac(input, beginning->str, nick_ac, TRUE);
             g_string_free(beginning, TRUE);
-            if (result != NULL) {
+            if (result) {
                 g_strfreev(args);
                 return result;
             }
@@ -2786,17 +2812,17 @@ _role_autocomplete(const char * const input)
     }
 
     result = autocomplete_param_with_ac(input, "/role set", role_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/role list", role_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/role", privilege_cmd_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2809,22 +2835,45 @@ _statuses_autocomplete(const char * const input)
     char *result = NULL;
 
     result = autocomplete_param_with_ac(input, "/statuses console", statuses_setting_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/statuses chat", statuses_setting_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/statuses muc", statuses_setting_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/statuses", statuses_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
+        return result;
+    }
+
+    return NULL;
+}
+
+static char *
+_receipts_autocomplete(const char * const input)
+{
+    char *result = NULL;
+
+    result = autocomplete_param_with_func(input, "/receipts send", prefs_autocomplete_boolean_choice);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_func(input, "/receipts request", prefs_autocomplete_boolean_choice);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_ac(input, "/receipts", receipts_ac, TRUE);
+    if (result) {
         return result;
     }
 
@@ -2837,12 +2886,12 @@ _alias_autocomplete(const char * const input)
     char *result = NULL;
 
     result = autocomplete_param_with_ac(input, "/alias remove", aliases_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
     result = autocomplete_param_with_ac(input, "/alias", alias_ac, TRUE);
-    if (result != NULL) {
+    if (result) {
         return result;
     }
 
@@ -2860,7 +2909,7 @@ _connect_autocomplete(const char * const input)
     if ((strncmp(input, "/connect", 8) == 0) && (result == TRUE)) {
         GString *beginning = g_string_new("/connect ");
         g_string_append(beginning, args[0]);
-        if (args[1] != NULL && args[2] != NULL) {
+        if (args[1] && args[2]) {
             g_string_append(beginning, " ");
             g_string_append(beginning, args[1]);
             g_string_append(beginning, " ");
@@ -2868,7 +2917,7 @@ _connect_autocomplete(const char * const input)
         }
         found = autocomplete_param_with_ac(input, beginning->str, connect_property_ac, TRUE);
         g_string_free(beginning, TRUE);
-        if (found != NULL) {
+        if (found) {
             g_strfreev(args);
             return found;
         }
@@ -2877,7 +2926,7 @@ _connect_autocomplete(const char * const input)
     g_strfreev(args);
 
     found = autocomplete_param_with_func(input, "/connect", accounts_find_enabled);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2891,7 +2940,7 @@ _join_autocomplete(const char * const input)
     gboolean result = FALSE;
 
     found = autocomplete_param_with_func(input, "/join", bookmark_find);
-    if (found != NULL) {
+    if (found) {
         return found;
     }
 
@@ -2900,7 +2949,7 @@ _join_autocomplete(const char * const input)
     if ((strncmp(input, "/join", 5) == 0) && (result == TRUE)) {
         GString *beginning = g_string_new("/join ");
         g_string_append(beginning, args[0]);
-        if (args[1] != NULL && args[2] != NULL) {
+        if (args[1] && args[2]) {
             g_string_append(beginning, " ");
             g_string_append(beginning, args[1]);
             g_string_append(beginning, " ");
@@ -2908,7 +2957,7 @@ _join_autocomplete(const char * const input)
         }
         found = autocomplete_param_with_ac(input, beginning->str, join_property_ac, TRUE);
         g_string_free(beginning, TRUE);
-        if (found != NULL) {
+        if (found) {
             g_strfreev(args);
             return found;
         }
@@ -2935,14 +2984,14 @@ _account_autocomplete(const char * const input)
             g_string_append(beginning, args[2]);
             found = autocomplete_param_with_ac(input, beginning->str, otr_policy_ac, TRUE);
             g_string_free(beginning, TRUE);
-            if (found != NULL) {
+            if (found) {
                 g_strfreev(args);
                 return found;
             }
         } else {
             found = autocomplete_param_with_ac(input, beginning->str, account_set_ac, TRUE);
             g_string_free(beginning, TRUE);
-            if (found != NULL) {
+            if (found) {
                 g_strfreev(args);
                 return found;
             }
@@ -2954,7 +3003,7 @@ _account_autocomplete(const char * const input)
         g_string_append(beginning, args[1]);
         found = autocomplete_param_with_ac(input, beginning->str, account_clear_ac, TRUE);
         g_string_free(beginning, TRUE);
-        if (found != NULL) {
+        if (found) {
             g_strfreev(args);
             return found;
         }
@@ -2974,7 +3023,7 @@ _account_autocomplete(const char * const input)
 
     for (i = 0; i < ARRAY_SIZE(account_choice); i++) {
         found = autocomplete_param_with_func(input, account_choice[i], accounts_find_all);
-        if (found != NULL) {
+        if (found) {
             return found;
         }
     }
@@ -2982,3 +3031,55 @@ _account_autocomplete(const char * const input)
     found = autocomplete_param_with_ac(input, "/account", account_ac, TRUE);
     return found;
 }
+
+static int
+_cmp_command(Command *cmd1, Command *cmd2)
+{
+    return g_strcmp0(cmd1->cmd, cmd2->cmd);
+}
+
+void
+command_docgen(void)
+{
+    GList *cmds = NULL;
+    unsigned int i;
+    for (i = 0; i < ARRAY_SIZE(command_defs); i++) {
+        Command *pcmd = command_defs+i;
+        cmds = g_list_insert_sorted(cmds, pcmd, (GCompareFunc)_cmp_command);
+    }
+
+    FILE *toc_fragment = fopen("toc_fragment.html", "w");
+    FILE *main_fragment = fopen("main_fragment.html", "w");
+
+    fputs("<ul><li><ul><li>\n", toc_fragment);
+    fputs("<hr>\n", main_fragment);
+
+    GList *curr = cmds;
+    while (curr) {
+        Command *pcmd = curr->data;
+
+        fprintf(toc_fragment, "<a href=\"#%s\">%s</a>,\n", &pcmd->cmd[1], pcmd->cmd);
+
+        fprintf(main_fragment, "<a name=\"%s\"></a>\n", &pcmd->cmd[1]);
+        fprintf(main_fragment, "<h4>%s</h4>\n", pcmd->cmd);
+        fputs("<p>Usage:</p>\n", main_fragment);
+        fprintf(main_fragment, "<p><pre><code>%s</code></pre></p>\n", pcmd->help.usage);
+
+        fputs("<p>Details:</p>\n", main_fragment);
+        fputs("<p><pre><code>", main_fragment);
+        int i = 2;
+        while (pcmd->help.long_help[i]) {
+            fprintf(main_fragment, "%s\n", pcmd->help.long_help[i++]);
+        }
+        fputs("</code></pre></p>\n<a href=\"#top\"><h5>back to top</h5></a><br><hr>\n", main_fragment);
+        fputs("\n", main_fragment);
+
+        curr = g_list_next(curr);
+    }
+
+    fputs("</ul></ul>\n", toc_fragment);
+
+    fclose(toc_fragment);
+    fclose(main_fragment);
+    g_list_free(cmds);
+}
diff --git a/src/command/command.h b/src/command/command.h
index ffbccfa4..b500404b 100644
--- a/src/command/command.h
+++ b/src/command/command.h
@@ -1,7 +1,7 @@
 /*
  * command.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -66,4 +66,6 @@ void cmd_history_append(char *inp);
 char *cmd_history_previous(char *inp);
 char *cmd_history_next(char *inp);
 
+void command_docgen(void);
+
 #endif
diff --git a/src/command/commands.c b/src/command/commands.c
index 02910f99..c5350519 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -1,7 +1,7 @@
 /*
  * commands.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -38,10 +38,8 @@
 #include <stdlib.h>
 #include <errno.h>
 #include <assert.h>
+#include <uuid/uuid.h>
 #include <glib.h>
-#ifdef HAVE_LIBOTR
-#include <libotr/proto.h>
-#endif
 
 #include "chat_session.h"
 #include "command/commands.h"
@@ -67,12 +65,13 @@
 #include "xmpp/bookmark.h"
 #include "ui/ui.h"
 #include "ui/windows.h"
+#include "event/client_events.h"
+#include "event/ui_events.h"
 
 static void _update_presence(const resource_presence_t presence,
     const char * const show, gchar **args);
 static gboolean _cmd_set_boolean_preference(gchar *arg, struct cmd_help_t help,
     const char * const display, preference_t pref);
-static int _strtoi(char *str, int *saveptr, int min, int max);
 static void _cmd_show_filtered_help(char *heading, gchar *cmd_filter[], int filter_size);
 static gint _compare_commands(Command *a, Command *b);
 static void _who_room(gchar **args, struct cmd_help_t help);
@@ -81,115 +80,178 @@ static void _who_roster(gchar **args, struct cmd_help_t help);
 extern GHashTable *commands;
 
 gboolean
-cmd_connect(gchar **args, struct cmd_help_t help)
+cmd_execute_default(const char * inp)
 {
-    gboolean result = FALSE;
+    // handle escaped commands - treat as normal message
+    if (g_str_has_prefix(inp, "//")) {
+        inp++;
 
-    jabber_conn_status_t conn_status = jabber_get_connection_status();
+    // handle unknown commands
+    } else if ((inp[0] == '/') && (!g_str_has_prefix(inp, "/me "))) {
+        cons_show("Unknown command: %s", inp);
+        cons_alert();
+        return TRUE;
+    }
+
+    // handle non commands in non chat windows
+    ProfWin *current = wins_get_current();
+    if (current->type != WIN_CHAT && current->type != WIN_MUC && current->type != WIN_PRIVATE) {
+        cons_show("Unknown command: %s", inp);
+        return TRUE;
+    }
+
+    jabber_conn_status_t status = jabber_get_connection_status();
+    if (status != JABBER_CONNECTED) {
+        ui_current_print_line("You are not currently connected.");
+        return TRUE;
+    }
+
+    switch (current->type) {
+    case WIN_CHAT:
+    {
+        ProfChatWin *chatwin = wins_get_current_chat();
+        cl_ev_send_msg(chatwin, inp);
+        break;
+    }
+    case WIN_PRIVATE:
+    {
+        ProfPrivateWin *privatewin = wins_get_current_private();
+        cl_ev_send_priv_msg(privatewin, inp);
+        break;
+    }
+    case WIN_MUC:
+    {
+        ProfMucWin *mucwin = wins_get_current_muc();
+        cl_ev_send_muc_msg(mucwin, inp);
+        break;
+    }
+    default:
+        break;
+    }
+
+    return TRUE;
+}
+
+gboolean
+cmd_execute_alias(const char * const inp, gboolean *ran)
+{
+    if (inp[0] != '/') {
+        ran = FALSE;
+        return TRUE;
+    }
+
+    char *alias = strdup(inp+1);
+    char *value = prefs_get_alias(alias);
+    free(alias);
+    if (value) {
+        *ran = TRUE;
+        return cmd_process_input(value);
+    }
 
+    *ran = FALSE;
+    return TRUE;
+}
+
+gboolean
+cmd_connect(gchar **args, struct cmd_help_t help)
+{
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
     if ((conn_status != JABBER_DISCONNECTED) && (conn_status != JABBER_STARTED)) {
         cons_show("You are either connected already, or a login is in process.");
-        result = TRUE;
-    } else {
-        gchar *opt_keys[] = { "server", "port", NULL };
-        gboolean parsed;
+        return TRUE;
+    }
 
-        GHashTable *options = parse_options(&args[args[0] ? 1 : 0], opt_keys, &parsed);
-        if (!parsed) {
-            cons_show("Usage: %s", help.usage);
+    gchar *opt_keys[] = { "server", "port", NULL };
+    gboolean parsed;
+
+    GHashTable *options = parse_options(&args[args[0] ? 1 : 0], opt_keys, &parsed);
+    if (!parsed) {
+        cons_show("Usage: %s", help.usage);
+        cons_show("");
+        return TRUE;
+    }
+
+    char *altdomain = g_hash_table_lookup(options, "server");
+
+    int port = 0;
+    if (g_hash_table_contains(options, "port")) {
+        char *port_str = g_hash_table_lookup(options, "port");
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(port_str, &port, 1, 65535, &err_msg);
+        if (!res) {
+            cons_show(err_msg);
             cons_show("");
+            free(err_msg);
+            port = 0;
             return TRUE;
         }
+    }
 
-        char *altdomain = g_hash_table_lookup(options, "server");
-
-        int port = 0;
-        if (g_hash_table_contains(options, "port")) {
-            char *port_str = g_hash_table_lookup(options, "port");
-            if (_strtoi(port_str, &port, 1, 65535) != 0) {
-                port = 0;
-                cons_show("");
-                return TRUE;
-            }
+    char *user = args[0];
+    char *def = prefs_get_string(PREF_DEFAULT_ACCOUNT);
+    if (!user) {
+        if (def) {
+            user = def;
+            cons_show("Using default account %s.", user);
+        } else {
+            cons_show("No default account.");
+            g_free(def);
+            return TRUE;
         }
+    }
 
-        char *user = args[0];
-        char *def = prefs_get_string(PREF_DEFAULT_ACCOUNT);
-        if(!user){
-            if(def){
-                user = def;
-                cons_show("Using default account %s.", user);
+    char *lower = g_utf8_strdown(user, -1);
+    char *jid;
+    g_free(def);
+
+    // connect with account
+    ProfAccount *account = accounts_get_account(lower);
+    if (account) {
+        // use password if set
+        if (account->password) {
+            conn_status = cl_ev_connect_account(account);
+
+        // use eval_password if set
+        } else if (account->eval_password) {
+            gboolean res = account_eval_password(account);
+            if (res) {
+                conn_status = cl_ev_connect_account(account);
+                free(account->password);
+                account->password = NULL;
             } else {
-                cons_show("No default account.");
-                g_free(def);
+                cons_show("Error evaluating password, see logs for details.");
+                g_free(lower);
                 return TRUE;
             }
-        }
 
-        char *lower = g_utf8_strdown(user, -1);
-        char *jid;
-        g_free(def);
-        def = NULL;
-
-        ProfAccount *account = accounts_get_account(lower);
-        if (account != NULL) {
-            jid = account_create_full_jid(account);
-            if(account->eval_password){
-                // Evaluate as shell command to retrieve password
-                GString *cmd = g_string_append(g_string_new(account->eval_password), " 2>/dev/null");
-                FILE *stream = popen(cmd->str, "r");
-                g_string_free(cmd, TRUE);
-                if(stream){
-                    // Limit to READ_BUF_SIZE bytes to prevent overflows in the case of a poorly chosen command
-                    account->password = g_malloc(READ_BUF_SIZE);
-                    if(!account->password){
-                        log_error("Failed to allocate enough memory to read eval_password output");
-                        cons_show("Error evaluating password, see logs for details.");
-                        return TRUE;
-                    }
-                    account->password = fgets(account->password, READ_BUF_SIZE, stream);
-                    pclose(stream);
-                    if(!account->password){
-                        log_error("No result from eval_password.");
-                        cons_show("Error evaluating password, see logs for details.");
-                        return TRUE;
-                    }
-                    g_strstrip(account->password);
-                } else {
-                    log_error("popen failed when running eval_password.");
-                    cons_show("Error evaluating password, see logs for details.");
-                    return TRUE;
-                }
-            } else if (!account->password) {
-                account->password = ui_ask_password();
-            }
-            cons_show("Connecting with account %s as %s", account->name, jid);
-            if(g_hash_table_contains(options, "port") || g_hash_table_contains(options, "server"))
-                cons_show("Ignoring extra connect options. Please set them with /account set");
-            conn_status = jabber_connect_with_account(account);
-            account_free(account);
+        // no account password setting, prompt
         } else {
-            char *passwd = ui_ask_password();
-            jid = strdup(lower);
-            cons_show("Connecting as %s", jid);
-            conn_status = jabber_connect_with_details(jid, passwd, altdomain, port);
-            free(passwd);
-        }
-        g_free(lower);
-
-        if (conn_status == JABBER_DISCONNECTED) {
-            cons_show_error("Connection attempt for %s failed.", jid);
-            log_info("Connection attempt for %s failed", jid);
+            account->password = ui_ask_password();
+            conn_status = cl_ev_connect_account(account);
+            free(account->password);
+            account->password = NULL;
         }
 
-        options_destroy(options);
+        jid = account_create_full_jid(account);
 
-        free(jid);
+    // connect with JID
+    } else {
+        jid = strdup(lower);
+        char *passwd = ui_ask_password();
+        conn_status = cl_ev_connect_jid(jid, passwd, altdomain, port);
+        free(passwd);
+    }
 
-        result = TRUE;
+    if (conn_status == JABBER_DISCONNECTED) {
+        cons_show_error("Connection attempt for %s failed.", jid);
+        log_info("Connection attempt for %s failed", jid);
     }
 
-    return result;
+    options_destroy(options);
+    g_free(lower);
+    free(jid);
+
+    return TRUE;
 }
 
 gboolean
@@ -342,7 +404,7 @@ cmd_account(gchar **args, struct cmd_help_t help)
                     } else {
                         accounts_set_jid(account_name, jid->barejid);
                         cons_show("Updated jid for account %s: %s", account_name, jid->barejid);
-                        if (jid->resourcepart != NULL) {
+                        if (jid->resourcepart) {
                             accounts_set_resource(account_name, jid->resourcepart);
                             cons_show("Updated resource for account %s: %s", account_name, jid->resourcepart);
                         }
@@ -355,8 +417,12 @@ cmd_account(gchar **args, struct cmd_help_t help)
                     cons_show("");
                 } else if (strcmp(property, "port") == 0) {
                     int port;
-                    if (_strtoi(value, &port, 1, 65535) != 0) {
+                    char *err_msg = NULL;
+                    gboolean res = strtoi_range(value, &port, 1, 65535, &err_msg);
+                    if (!res) {
+                        cons_show(err_msg);
                         cons_show("");
+                        free(err_msg);
                         return TRUE;
                     } else {
                         accounts_set_port(account_name, port);
@@ -368,7 +434,7 @@ cmd_account(gchar **args, struct cmd_help_t help)
                     cons_show("Updated resource for account %s: %s", account_name, value);
                     cons_show("");
                 } else if (strcmp(property, "password") == 0) {
-                    if(accounts_get_account(account_name)->eval_password != NULL) {
+                    if(accounts_get_account(account_name)->eval_password) {
                         cons_show("Cannot set password when eval_password is set.");
                     } else {
                         accounts_set_password(account_name, value);
@@ -376,7 +442,7 @@ cmd_account(gchar **args, struct cmd_help_t help)
                         cons_show("");
                     }
                 } else if (strcmp(property, "eval_password") == 0) {
-                    if(accounts_get_account(account_name)->password != NULL) {
+                    if(accounts_get_account(account_name)->password) {
                         cons_show("Cannot set eval_password when password is set.");
                     } else {
                         accounts_set_eval_password(account_name, value);
@@ -410,42 +476,46 @@ cmd_account(gchar **args, struct cmd_help_t help)
                     }
                     cons_show("");
                 } else if (valid_resource_presence_string(property)) {
-                        int intval;
-
-                        if (_strtoi(value, &intval, -128, 127) == 0) {
-                            resource_presence_t presence_type = resource_presence_from_string(property);
-                            switch (presence_type)
-                            {
-                                case (RESOURCE_ONLINE):
-                                    accounts_set_priority_online(account_name, intval);
-                                    break;
-                                case (RESOURCE_CHAT):
-                                    accounts_set_priority_chat(account_name, intval);
-                                    break;
-                                case (RESOURCE_AWAY):
-                                    accounts_set_priority_away(account_name, intval);
-                                    break;
-                                case (RESOURCE_XA):
-                                    accounts_set_priority_xa(account_name, intval);
-                                    break;
-                                case (RESOURCE_DND):
-                                    accounts_set_priority_dnd(account_name, intval);
-                                    break;
-                            }
+                    int intval;
+                    char *err_msg = NULL;
+                    gboolean res = strtoi_range(value, &intval, -128, 127, &err_msg);
+                    if (res) {
+                        resource_presence_t presence_type = resource_presence_from_string(property);
+                        switch (presence_type)
+                        {
+                            case (RESOURCE_ONLINE):
+                                accounts_set_priority_online(account_name, intval);
+                                break;
+                            case (RESOURCE_CHAT):
+                                accounts_set_priority_chat(account_name, intval);
+                                break;
+                            case (RESOURCE_AWAY):
+                                accounts_set_priority_away(account_name, intval);
+                                break;
+                            case (RESOURCE_XA):
+                                accounts_set_priority_xa(account_name, intval);
+                                break;
+                            case (RESOURCE_DND):
+                                accounts_set_priority_dnd(account_name, intval);
+                                break;
+                        }
 
-                            jabber_conn_status_t conn_status = jabber_get_connection_status();
-                            if (conn_status == JABBER_CONNECTED) {
-                                char *connected_account = jabber_get_account_name();
-                                resource_presence_t last_presence = accounts_get_last_presence(connected_account);
+                        jabber_conn_status_t conn_status = jabber_get_connection_status();
+                        if (conn_status == JABBER_CONNECTED) {
+                            char *connected_account = jabber_get_account_name();
+                            resource_presence_t last_presence = accounts_get_last_presence(connected_account);
 
-                                if (presence_type == last_presence) {
-                                    char *message = jabber_get_presence_message();
-                                    presence_update(last_presence, message, 0);
-                                }
+                            if (presence_type == last_presence) {
+                                char *message = jabber_get_presence_message();
+                                cl_ev_presence_send(last_presence, message, 0);
                             }
-                            cons_show("Updated %s priority for account %s: %s", property, account_name, value);
-                            cons_show("");
                         }
+                        cons_show("Updated %s priority for account %s: %s", property, account_name, value);
+                        cons_show("");
+                    } else {
+                        cons_show(err_msg);
+                        free(err_msg);
+                    }
                 } else {
                     cons_show("Invalid property: %s", property);
                     cons_show("");
@@ -652,10 +722,14 @@ gboolean
 cmd_win(gchar **args, struct cmd_help_t help)
 {
     int num = atoi(args[0]);
-    gboolean switched = ui_switch_win(num);
-    if (switched == FALSE) {
+
+    ProfWin *window = wins_get_by_num(num);
+    if (!window) {
         cons_show("Window %d does not exist.", num);
+    } else {
+        ui_ev_focus_win(window);
     }
+
     return TRUE;
 }
 
@@ -681,7 +755,7 @@ cmd_help(gchar **args, struct cmd_help_t help)
         }
 
         GList *curr = ordered_commands;
-        while (curr != NULL) {
+        while (curr) {
             Command *cmd = curr->data;
             cons_show("%-12s: %s", cmd->cmd, cmd->help.short_help);
             curr = g_list_next(curr);
@@ -711,7 +785,7 @@ cmd_help(gchar **args, struct cmd_help_t help)
             "/rooms", "/tiny", "/who", "/nick", "/privileges", "/info", "/occupants" };
         _cmd_show_filtered_help("Groupchat commands", filter, ARRAY_SIZE(filter));
 
-    } else if (strcmp(args[0], "presence") == 0) {
+    } else if (strcmp(args[0], "presences") == 0) {
         gchar *filter[] = { "/autoaway", "/away", "/chat", "/dnd",
             "/online", "/priority", "/account", "/status", "/statuses", "/who",
             "/xa" };
@@ -727,7 +801,7 @@ cmd_help(gchar **args, struct cmd_help_t help)
 
     } else if (strcmp(args[0], "settings") == 0) {
         gchar *filter[] = { "/account", "/autoaway", "/autoping", "/autoconnect", "/beep",
-            "/chlog", "/flash", "/gone", "/grlog", "/history", "/intype",
+            "/carbons", "/chlog", "/flash", "/gone", "/grlog", "/history", "/intype",
             "/log", "/mouse", "/notify", "/outtype", "/prefs", "/priority",
             "/reconnect", "/roster", "/splash", "/states", "/statuses", "/theme",
             "/titlebar", "/vercheck", "/privileges", "/occupants", "/presence", "/wrap" };
@@ -743,12 +817,12 @@ cmd_help(gchar **args, struct cmd_help_t help)
         const gchar **help_text = NULL;
         Command *command = g_hash_table_lookup(commands, cmd_with_slash);
 
-        if (command != NULL) {
+        if (command) {
             help_text = command->help.long_help;
         }
 
         cons_show("");
-        if (help_text != NULL) {
+        if (help_text) {
             ProfWin *console = wins_get_console();
             ui_show_lines(console, help_text);
         } else {
@@ -819,7 +893,7 @@ cmd_theme(gchar **args, struct cmd_help_t help)
         g_slist_free_full(themes, g_free);
 
     // load a theme
-    } else if (g_strcmp0(args[0], "set") == 0) {
+    } else if (g_strcmp0(args[0], "load") == 0) {
         if (args[1] == NULL) {
             cons_show("Usage: %s", help.usage);
         } else if (theme_load(args[1])) {
@@ -854,13 +928,13 @@ cmd_theme(gchar **args, struct cmd_help_t help)
 static void
 _who_room(gchar **args, struct cmd_help_t help)
 {
-    if ((g_strv_length(args) == 2) && (args[1] != NULL)) {
+    if ((g_strv_length(args) == 2) && args[1]) {
         cons_show("Argument group is not applicable to chat rooms.");
         return;
     }
 
     // bad arg
-    if (args[0] != NULL &&
+    if (args[0] &&
             (g_strcmp0(args[0], "online") != 0) &&
             (g_strcmp0(args[0], "available") != 0) &&
             (g_strcmp0(args[0], "unavailable") != 0) &&
@@ -904,7 +978,7 @@ _who_room(gchar **args, struct cmd_help_t help)
         } else if (strcmp("available", presence) == 0) {
             GList *filtered = NULL;
 
-            while (occupants != NULL) {
+            while (occupants) {
                 Occupant *occupant = occupants->data;
                 if (muc_occupant_available(occupant)) {
                     filtered = g_list_append(filtered, occupant);
@@ -918,7 +992,7 @@ _who_room(gchar **args, struct cmd_help_t help)
         } else if (strcmp("unavailable", presence) == 0) {
             GList *filtered = NULL;
 
-            while (occupants != NULL) {
+            while (occupants) {
                 Occupant *occupant = occupants->data;
                 if (!muc_occupant_available(occupant)) {
                     filtered = g_list_append(filtered, occupant);
@@ -932,7 +1006,7 @@ _who_room(gchar **args, struct cmd_help_t help)
         } else {
             GList *filtered = NULL;
 
-            while (occupants != NULL) {
+            while (occupants) {
                 Occupant *occupant = occupants->data;
                 const char *presence_str = string_from_resource_presence(occupant->presence);
                 if (strcmp(presence_str, presence) == 0) {
@@ -986,7 +1060,7 @@ _who_roster(gchar **args, struct cmd_help_t help)
     char *presence = args[0];
 
     // bad arg
-    if ((presence != NULL)
+    if (presence
             && (strcmp(presence, "online") != 0)
             && (strcmp(presence, "available") != 0)
             && (strcmp(presence, "unavailable") != 0)
@@ -1001,13 +1075,13 @@ _who_roster(gchar **args, struct cmd_help_t help)
     }
 
     char *group = NULL;
-    if ((g_strv_length(args) == 2) && (args[1] != NULL)) {
+    if ((g_strv_length(args) == 2) && args[1]) {
         group = args[1];
     }
 
     cons_show("");
     GSList *list = NULL;
-    if (group != NULL) {
+    if (group) {
         list = roster_get_group(group);
         if (list == NULL) {
             cons_show("No such group: %s.", group);
@@ -1023,7 +1097,7 @@ _who_roster(gchar **args, struct cmd_help_t help)
 
     // no arg, show all contacts
     if ((presence == NULL) || (g_strcmp0(presence, "any") == 0)) {
-        if (group != NULL) {
+        if (group) {
             if (list == NULL) {
                 cons_show("No contacts in group %s.", group);
             } else {
@@ -1043,15 +1117,16 @@ _who_roster(gchar **args, struct cmd_help_t help)
     } else if (strcmp("available", presence) == 0) {
         GSList *filtered = NULL;
 
-        while (list != NULL) {
-            PContact contact = list->data;
+        GSList *curr = list;
+        while (curr) {
+            PContact contact = curr->data;
             if (p_contact_is_available(contact)) {
                 filtered = g_slist_append(filtered, contact);
             }
-            list = g_slist_next(list);
+            curr = g_slist_next(curr);
         }
 
-        if (group != NULL) {
+        if (group) {
             if (filtered == NULL) {
                 cons_show("No contacts in group %s are %s.", group, presence);
             } else {
@@ -1066,20 +1141,22 @@ _who_roster(gchar **args, struct cmd_help_t help)
                 cons_show_contacts(filtered);
             }
         }
+        g_slist_free(filtered);
 
     // unavailable
     } else if (strcmp("unavailable", presence) == 0) {
         GSList *filtered = NULL;
 
-        while (list != NULL) {
-            PContact contact = list->data;
+        GSList *curr = list;
+        while (curr) {
+            PContact contact = curr->data;
             if (!p_contact_is_available(contact)) {
                 filtered = g_slist_append(filtered, contact);
             }
-            list = g_slist_next(list);
+            curr = g_slist_next(curr);
         }
 
-        if (group != NULL) {
+        if (group) {
             if (filtered == NULL) {
                 cons_show("No contacts in group %s are %s.", group, presence);
             } else {
@@ -1094,20 +1171,22 @@ _who_roster(gchar **args, struct cmd_help_t help)
                 cons_show_contacts(filtered);
             }
         }
+        g_slist_free(filtered);
 
     // online, available resources
     } else if (strcmp("online", presence) == 0) {
         GSList *filtered = NULL;
 
-        while (list != NULL) {
-            PContact contact = list->data;
+        GSList *curr = list;
+        while (curr) {
+            PContact contact = curr->data;
             if (p_contact_has_available_resource(contact)) {
                 filtered = g_slist_append(filtered, contact);
             }
-            list = g_slist_next(list);
+            curr = g_slist_next(curr);
         }
 
-        if (group != NULL) {
+        if (group) {
             if (filtered == NULL) {
                 cons_show("No contacts in group %s are %s.", group, presence);
             } else {
@@ -1122,20 +1201,22 @@ _who_roster(gchar **args, struct cmd_help_t help)
                 cons_show_contacts(filtered);
             }
         }
+        g_slist_free(filtered);
 
     // offline, no available resources
     } else if (strcmp("offline", presence) == 0) {
         GSList *filtered = NULL;
 
-        while (list != NULL) {
-            PContact contact = list->data;
+        GSList *curr = list;
+        while (curr) {
+            PContact contact = curr->data;
             if (!p_contact_has_available_resource(contact)) {
                 filtered = g_slist_append(filtered, contact);
             }
-            list = g_slist_next(list);
+            curr = g_slist_next(curr);
         }
 
-        if (group != NULL) {
+        if (group) {
             if (filtered == NULL) {
                 cons_show("No contacts in group %s are %s.", group, presence);
             } else {
@@ -1150,20 +1231,22 @@ _who_roster(gchar **args, struct cmd_help_t help)
                 cons_show_contacts(filtered);
             }
         }
+        g_slist_free(filtered);
 
     // show specific status
     } else {
         GSList *filtered = NULL;
 
-        while (list != NULL) {
-            PContact contact = list->data;
+        GSList *curr = list;
+        while (curr) {
+            PContact contact = curr->data;
             if (strcmp(p_contact_presence(contact), presence) == 0) {
                 filtered = g_slist_append(filtered, contact);
             }
-            list = g_slist_next(list);
+            curr = g_slist_next(curr);
         }
 
-        if (group != NULL) {
+        if (group) {
             if (filtered == NULL) {
                 cons_show("No contacts in group %s are %s.", group, presence);
             } else {
@@ -1178,6 +1261,7 @@ _who_roster(gchar **args, struct cmd_help_t help)
                 cons_show_contacts(filtered);
             }
         }
+        g_slist_free(filtered);
     }
 
     g_slist_free(list);
@@ -1218,6 +1302,7 @@ cmd_msg(gchar **args, struct cmd_help_t help)
         return TRUE;
     }
 
+    // send private message when in MUC room
     if (win_type == WIN_MUC) {
         ProfMucWin *mucwin = wins_get_current_muc();
         if (muc_roster_contains_nick(mucwin->roomjid, usr)) {
@@ -1225,11 +1310,14 @@ cmd_msg(gchar **args, struct cmd_help_t help)
             g_string_append(full_jid, "/");
             g_string_append(full_jid, usr);
 
-            if (msg != NULL) {
-                message_send_private(full_jid->str, msg);
-                ui_outgoing_private_msg("me", full_jid->str, msg);
-            } else {
-                ui_new_private_win(full_jid->str);
+            ProfPrivateWin *privwin = wins_get_private(full_jid->str);
+            if (!privwin) {
+                privwin = ui_ev_new_private_win(full_jid->str);
+            }
+            ui_ev_focus_win((ProfWin*)privwin);
+
+            if (msg) {
+                cl_ev_send_priv_msg(privwin, msg);
             }
 
             g_string_free(full_jid, TRUE);
@@ -1240,85 +1328,30 @@ cmd_msg(gchar **args, struct cmd_help_t help)
 
         return TRUE;
 
+    // send chat message
     } else {
-        // get barejid
         char *barejid = roster_barejid_from_name(usr);
         if (barejid == NULL) {
             barejid = usr;
         }
 
-        if (msg != NULL) {
-#ifdef HAVE_LIBOTR
-            if (otr_is_secure(barejid)) {
-                char *encrypted = otr_encrypt_message(barejid, msg);
-                if (encrypted != NULL) {
-                    message_send_chat(barejid, encrypted);
-                    otr_free_message(encrypted);
-                    ui_outgoing_chat_msg("me", barejid, msg);
-
-                    if (((win_type == WIN_CHAT) || (win_type == WIN_CONSOLE)) && prefs_get_boolean(PREF_CHLOG)) {
-                        const char *jid = jabber_get_fulljid();
-                        Jid *jidp = jid_create(jid);
-                        char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
-                        if (strcmp(pref_otr_log, "on") == 0) {
-                            chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL);
-                        } else if (strcmp(pref_otr_log, "redact") == 0) {
-                            chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_OUT_LOG, NULL);
-                        }
-                        prefs_free_string(pref_otr_log);
-                        jid_destroy(jidp);
-                    }
-                } else {
-                    cons_show_error("Failed to encrypt and send message,");
-                }
-            } else {
-                prof_otrpolicy_t policy = otr_get_policy(barejid);
-
-                if (policy == PROF_OTRPOLICY_ALWAYS) {
-                    cons_show_error("Failed to send message. Please check OTR policy");
-                    return TRUE;
-                } else if (policy == PROF_OTRPOLICY_OPPORTUNISTIC) {
-                    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);
-                    message_send_chat(barejid, otr_message->str);
-
-                    g_string_free(otr_message, TRUE);
-                } else {
-                    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)) {
-                    const char *jid = jabber_get_fulljid();
-                    Jid *jidp = jid_create(jid);
-                    chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL);
-                    jid_destroy(jidp);
-                }
-            }
-            return TRUE;
-#else
-            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)) {
-                const char *jid = jabber_get_fulljid();
-                Jid *jidp = jid_create(jid);
-                chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL);
-                jid_destroy(jidp);
-            }
-            return TRUE;
-#endif
+        ProfChatWin *chatwin = wins_get_chat(barejid);
+        if (!chatwin) {
+            chatwin = ui_ev_new_chat_win(barejid);
+        }
+        ui_ev_focus_win((ProfWin*)chatwin);
 
-        } else { // msg == NULL
-            ui_new_chat_win(barejid);
+        if (msg) {
+            cl_ev_send_msg(chatwin, msg);
+        } else {
 #ifdef HAVE_LIBOTR
             if (otr_is_secure(barejid)) {
                 ui_gone_secure(barejid, otr_is_trusted(barejid));
             }
 #endif
-            return TRUE;
         }
+
+        return TRUE;
     }
 }
 
@@ -1336,9 +1369,9 @@ cmd_group(gchar **args, struct cmd_help_t help)
     if (args[0] == NULL) {
         GSList *groups = roster_get_groups();
         GSList *curr = groups;
-        if (curr != NULL) {
+        if (curr) {
             cons_show("Groups:");
-            while (curr != NULL) {
+            while (curr) {
                 cons_show("  %s", curr->data);
                 curr = g_slist_next(curr);
             }
@@ -1455,11 +1488,14 @@ cmd_roster(gchar **args, struct cmd_help_t help)
 
     // set roster size
     } else if (g_strcmp0(args[0], "size") == 0) {
-        int intval = 0;
         if (!args[1]) {
             cons_show("Usage: %s", help.usage);
             return TRUE;
-        } else if (_strtoi(args[1], &intval, 1, 99) == 0) {
+        }
+        int intval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(args[1], &intval, 1, 99, &err_msg);
+        if (res) {
             prefs_set_roster_size(intval);
             cons_show("Roster screen size set to: %d%%", intval);
             if (prefs_get_boolean(PREF_ROSTER)) {
@@ -1467,6 +1503,8 @@ cmd_roster(gchar **args, struct cmd_help_t help)
             }
             return TRUE;
         } else {
+            cons_show(err_msg);
+            free(err_msg);
             return TRUE;
         }
 
@@ -1701,35 +1739,35 @@ cmd_status(gchar **args, struct cmd_help_t help)
     switch (win_type)
     {
         case WIN_MUC:
-            if (usr != NULL) {
+            if (usr) {
                 ProfMucWin *mucwin = wins_get_current_muc();
                 ProfWin *window = (ProfWin*) mucwin;
                 Occupant *occupant = muc_roster_item(mucwin->roomjid, usr);
                 if (occupant) {
                     win_show_occupant(window, occupant);
                 } else {
-                    win_save_vprint(window, '-', NULL, 0, 0, "", "No such participant \"%s\" in room.", usr);
+                    win_vprint(window, '-', NULL, 0, 0, "", "No such participant \"%s\" in room.", usr);
                 }
             } else {
                 ui_current_print_line("You must specify a nickname.");
             }
             break;
         case WIN_CHAT:
-            if (usr != NULL) {
+            if (usr) {
                 ui_current_print_line("No parameter required when in chat.");
             } else {
                 ProfChatWin *chatwin = wins_get_current_chat();
                 ProfWin *window = (ProfWin*) chatwin;
                 PContact pcontact = roster_get_contact(chatwin->barejid);
-                if (pcontact != NULL) {
+                if (pcontact) {
                     win_show_contact(window, pcontact);
                 } else {
-                    win_save_println(window, "Error getting contact info.");
+                    win_println(window, "Error getting contact info.");
                 }
             }
             break;
         case WIN_PRIVATE:
-            if (usr != NULL) {
+            if (usr) {
                 ui_current_print_line("No parameter required when in chat.");
             } else {
                 ProfPrivateWin *privatewin = wins_get_current_private();
@@ -1739,13 +1777,13 @@ cmd_status(gchar **args, struct cmd_help_t help)
                 if (occupant) {
                     win_show_occupant(window, occupant);
                 } else {
-                    win_save_println(window, "Error getting contact info.");
+                    win_println(window, "Error getting contact info.");
                 }
                 jid_destroy(jid);
             }
             break;
         case WIN_CONSOLE:
-            if (usr != NULL) {
+            if (usr) {
                 char *usr_jid = roster_barejid_from_name(usr);
                 if (usr_jid == NULL) {
                     usr_jid = usr;
@@ -1790,7 +1828,7 @@ cmd_info(gchar **args, struct cmd_help_t help)
                 }
             } else {
                 ProfMucWin *mucwin = wins_get_current_muc();
-                iq_room_info_request(mucwin->roomjid);
+                iq_room_info_request(mucwin->roomjid, TRUE);
                 ui_show_room_info(mucwin);
                 return TRUE;
             }
@@ -1802,10 +1840,10 @@ cmd_info(gchar **args, struct cmd_help_t help)
                 ProfChatWin *chatwin = wins_get_current_chat();
                 ProfWin *window = (ProfWin*) chatwin;
                 PContact pcontact = roster_get_contact(chatwin->barejid);
-                if (pcontact != NULL) {
+                if (pcontact) {
                     win_show_info(window, pcontact);
                 } else {
-                    win_save_println(window, "Error getting contact info.");
+                    win_println(window, "Error getting contact info.");
                 }
             }
             break;
@@ -1820,7 +1858,7 @@ cmd_info(gchar **args, struct cmd_help_t help)
                 if (occupant) {
                     win_show_occupant_info(window, jid->barejid, occupant);
                 } else {
-                    win_save_println(window, "Error getting contact info.");
+                    win_println(window, "Error getting contact info.");
                 }
                 jid_destroy(jid);
             }
@@ -1832,7 +1870,7 @@ cmd_info(gchar **args, struct cmd_help_t help)
                     usr_jid = usr;
                 }
                 pcontact = roster_get_contact(usr_jid);
-                if (pcontact != NULL) {
+                if (pcontact) {
                     cons_show_info(pcontact);
                 } else {
                     cons_show("No such contact \"%s\" in roster.", usr);
@@ -1864,7 +1902,7 @@ cmd_caps(gchar **args, struct cmd_help_t help)
     switch (win_type)
     {
         case WIN_MUC:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 ProfMucWin *mucwin = wins_get_current_muc();
                 occupant = muc_roster_item(mucwin->roomjid, args[0]);
                 if (occupant) {
@@ -1880,7 +1918,7 @@ cmd_caps(gchar **args, struct cmd_help_t help)
             break;
         case WIN_CHAT:
         case WIN_CONSOLE:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 Jid *jid = jid_create(args[0]);
 
                 if (jid->fulljid == NULL) {
@@ -1904,7 +1942,7 @@ cmd_caps(gchar **args, struct cmd_help_t help)
             }
             break;
         case WIN_PRIVATE:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 cons_show("No parameter needed to /caps when in private chat.");
             } else {
                 ProfPrivateWin *privatewin = wins_get_current_private();
@@ -1939,7 +1977,7 @@ cmd_software(gchar **args, struct cmd_help_t help)
     switch (win_type)
     {
         case WIN_MUC:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 ProfMucWin *mucwin = wins_get_current_muc();
                 occupant = muc_roster_item(mucwin->roomjid, args[0]);
                 if (occupant) {
@@ -1955,7 +1993,7 @@ cmd_software(gchar **args, struct cmd_help_t help)
             break;
         case WIN_CHAT:
         case WIN_CONSOLE:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 Jid *jid = jid_create(args[0]);
 
                 if (jid == NULL || jid->fulljid == NULL) {
@@ -1969,7 +2007,7 @@ cmd_software(gchar **args, struct cmd_help_t help)
             }
             break;
         case WIN_PRIVATE:
-            if (args[0] != NULL) {
+            if (args[0]) {
                 cons_show("No parameter needed to /software when in private chat.");
             } else {
                 ProfPrivateWin *privatewin = wins_get_current_private();
@@ -1993,8 +2031,24 @@ cmd_join(gchar **args, struct cmd_help_t help)
     }
 
     if (args[0] == NULL) {
-        cons_show("Usage: %s", help.usage);
-        cons_show("");
+        uuid_t uuid;
+        uuid_generate(uuid);
+        char *uuid_str = malloc(sizeof(char) * 37);
+        uuid_unparse_lower(uuid, uuid_str);
+
+        char *account_name = jabber_get_account_name();
+        ProfAccount *account = accounts_get_account(account_name);
+
+        GString *room_str = g_string_new("");
+        g_string_append_printf(room_str, "private-chat-%s@%s", uuid_str, account->muc_service);
+
+        presence_join_room(room_str->str, account->muc_nick, NULL);
+        muc_join(room_str->str, account->muc_nick, NULL, FALSE);
+
+        g_string_free(room_str, TRUE);
+        free(uuid_str);
+        account_free(account);
+
         return TRUE;
     }
 
@@ -2013,7 +2067,7 @@ cmd_join(gchar **args, struct cmd_help_t help)
     ProfAccount *account = accounts_get_account(account_name);
 
     // full room jid supplied (room@server)
-    if (room_arg->localpart != NULL) {
+    if (room_arg->localpart) {
         room = args[0];
 
     // server not supplied (room), use account preference
@@ -2041,10 +2095,15 @@ cmd_join(gchar **args, struct cmd_help_t help)
     options_destroy(options);
 
     // In the case that a nick wasn't provided by the optional args...
-    if (nick == NULL) {
+    if (!nick) {
         nick = account->muc_nick;
     }
 
+    // When no password, check for invite with password
+    if (!passwd) {
+        passwd = muc_invite_password(room);
+    }
+
     if (!muc_active(room)) {
         presence_join_room(room, nick, passwd);
         muc_join(room, nick, passwd, FALSE);
@@ -2083,7 +2142,7 @@ cmd_invite(gchar **args, struct cmd_help_t help)
 
     ProfMucWin *mucwin = wins_get_current_muc();
     message_send_invite(mucwin->roomjid, usr_jid, reason);
-    if (reason != NULL) {
+    if (reason) {
         cons_show("Room invite sent, contact: %s, room: %s, reason: \"%s\".",
             contact, mucwin->roomjid, reason);
     } else {
@@ -2368,7 +2427,7 @@ cmd_form(gchar **args, struct cmd_help_t help)
 
     if (g_strcmp0(args[0], "help") == 0) {
         char *tag = args[1];
-        if (tag != NULL) {
+        if (tag) {
             ui_show_form_field_help(confwin, tag);
         } else {
             ui_show_form_help(confwin);
@@ -2376,7 +2435,7 @@ cmd_form(gchar **args, struct cmd_help_t help)
             const gchar **help_text = NULL;
             Command *command = g_hash_table_lookup(commands, "/form");
 
-            if (command != NULL) {
+            if (command) {
                 help_text = command->help.long_help;
             }
 
@@ -2400,12 +2459,11 @@ cmd_form(gchar **args, struct cmd_help_t help)
             cmd_autocomplete_remove_form_fields(confwin->form);
         }
         wins_close_current();
-        ProfWin *current = (ProfWin*)wins_get_muc(confwin->roomjid);
-        if (current == NULL) {
-            current = wins_get_console();
+        ProfWin *new_current = (ProfWin*)wins_get_muc(confwin->roomjid);
+        if (!new_current) {
+            new_current = wins_get_console();
         }
-        int num = wins_get_num(current);
-        ui_switch_win(num);
+        ui_ev_focus_win(new_current);
     }
 
     return TRUE;
@@ -2435,7 +2493,7 @@ cmd_kick(gchar **args, struct cmd_help_t help)
             char *reason = args[1];
             iq_room_kick_occupant(mucwin->roomjid, nick, reason);
         } else {
-            win_save_vprint((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Occupant does not exist: %s", nick);
+            win_vprint((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Occupant does not exist: %s", nick);
         }
     } else {
         cons_show("Usage: %s", help.usage);
@@ -2494,10 +2552,10 @@ cmd_subject(gchar **args, struct cmd_help_t help)
     if (args[0] == NULL) {
         char *subject = muc_subject(mucwin->roomjid);
         if (subject) {
-            win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room subject: ");
-            win_save_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
+            win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room subject: ");
+            win_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
         } else {
-            win_save_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room has no subject");
+            win_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room has no subject");
         }
         return TRUE;
     }
@@ -2543,7 +2601,7 @@ cmd_affiliation(gchar **args, struct cmd_help_t help)
     }
 
     char *affiliation = args[1];
-    if ((affiliation != NULL) &&
+    if (affiliation &&
             (g_strcmp0(affiliation, "owner") != 0) &&
             (g_strcmp0(affiliation, "admin") != 0) &&
             (g_strcmp0(affiliation, "member") != 0) &&
@@ -2562,7 +2620,7 @@ cmd_affiliation(gchar **args, struct cmd_help_t help)
             iq_room_affiliation_list(mucwin->roomjid, "member");
             iq_room_affiliation_list(mucwin->roomjid, "outcast");
         } else if (g_strcmp0(affiliation, "none") == 0) {
-            win_save_print((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Cannot list users with no affiliation.");
+            win_print((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Cannot list users with no affiliation.");
         } else {
             iq_room_affiliation_list(mucwin->roomjid, affiliation);
         }
@@ -2613,7 +2671,7 @@ cmd_role(gchar **args, struct cmd_help_t help)
     }
 
     char *role = args[1];
-    if ((role != NULL ) &&
+    if (role &&
             (g_strcmp0(role, "visitor") != 0) &&
             (g_strcmp0(role, "participant") != 0) &&
             (g_strcmp0(role, "moderator") != 0) &&
@@ -2630,7 +2688,7 @@ cmd_role(gchar **args, struct cmd_help_t help)
             iq_room_role_list(mucwin->roomjid, "participant");
             iq_room_role_list(mucwin->roomjid, "visitor");
         } else if (g_strcmp0(role, "none") == 0) {
-            win_save_print((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Cannot list users with no role.");
+            win_print((ProfWin*) mucwin, '!', NULL, 0, 0, "", "Cannot list users with no role.");
         } else {
             iq_room_role_list(mucwin->roomjid, role);
         }
@@ -2693,12 +2751,12 @@ cmd_room(gchar **args, struct cmd_help_t help)
     if (g_strcmp0(args[0], "accept") == 0) {
         gboolean requires_config = muc_requires_config(mucwin->roomjid);
         if (!requires_config) {
-            win_save_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Current room does not require configuration.");
+            win_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Current room does not require configuration.");
             return TRUE;
         } else {
             iq_confirm_instant_room(mucwin->roomjid);
             muc_set_requires_config(mucwin->roomjid, FALSE);
-            win_save_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room unlocked.");
+            win_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room unlocked.");
             return TRUE;
         }
     }
@@ -2711,9 +2769,8 @@ cmd_room(gchar **args, struct cmd_help_t help)
     if (g_strcmp0(args[0], "config") == 0) {
         ProfMucConfWin *confwin = wins_get_muc_conf(mucwin->roomjid);
 
-        if (confwin != NULL) {
-            num = wins_get_num(window);
-            ui_switch_win(num);
+        if (confwin) {
+            ui_ev_focus_win((ProfWin*)confwin);
         } else {
             iq_request_room_config_form(mucwin->roomjid);
         }
@@ -2734,26 +2791,44 @@ cmd_occupants(gchar **args, struct cmd_help_t help)
     }
 
     if (g_strcmp0(args[0], "size") == 0) {
-        int intval = 0;
         if (!args[1]) {
             cons_show("Usage: %s", help.usage);
             return TRUE;
-        } else if (_strtoi(args[1], &intval, 1, 99) == 0) {
-            prefs_set_occupants_size(intval);
-            cons_show("Occupants screen size set to: %d%%", intval);
-            wins_resize_all();
-            return TRUE;
+        } else {
+            int intval = 0;
+            char *err_msg = NULL;
+            gboolean res = strtoi_range(args[1], &intval, 1, 99, &err_msg);
+            if (res) {
+                prefs_set_occupants_size(intval);
+                cons_show("Occupants screen size set to: %d%%", intval);
+                wins_resize_all();
+                return TRUE;
+            } else {
+                cons_show(err_msg);
+                free(err_msg);
+                return TRUE;
+            }
         }
     }
 
     if (g_strcmp0(args[0], "default") == 0) {
         if (g_strcmp0(args[1], "show") == 0) {
-            cons_show("Occupant list enabled.");
-            prefs_set_boolean(PREF_OCCUPANTS, TRUE);
+            if (g_strcmp0(args[2], "jid") == 0) {
+                cons_show("Occupant jids enabled.");
+                prefs_set_boolean(PREF_OCCUPANTS_JID, TRUE);
+            } else {
+                cons_show("Occupant list enabled.");
+                prefs_set_boolean(PREF_OCCUPANTS, TRUE);
+            }
             return TRUE;
         } else if (g_strcmp0(args[1], "hide") == 0) {
-            cons_show("Occupant list disabled.");
-            prefs_set_boolean(PREF_OCCUPANTS, FALSE);
+            if (g_strcmp0(args[2], "jid") == 0) {
+                cons_show("Occupant jids disabled.");
+                prefs_set_boolean(PREF_OCCUPANTS_JID, FALSE);
+            } else {
+                cons_show("Occupant list disabled.");
+                prefs_set_boolean(PREF_OCCUPANTS, FALSE);
+            }
             return TRUE;
         } else {
             cons_show("Usage: %s", help.usage);
@@ -2763,16 +2838,26 @@ cmd_occupants(gchar **args, struct cmd_help_t help)
 
     win_type_t win_type = ui_current_win_type();
     if (win_type != WIN_MUC) {
-        cons_show("Cannot show/hide occupant list when not in chat room.");
+        cons_show("Cannot apply setting when not in chat room.");
         return TRUE;
     }
 
     ProfMucWin *mucwin = wins_get_current_muc();
 
     if (g_strcmp0(args[0], "show") == 0) {
-        ui_room_show_occupants(mucwin->roomjid);
+        if (g_strcmp0(args[1], "jid") == 0) {
+            mucwin->showjid = TRUE;
+            ui_room_update_occupants(mucwin->roomjid);
+        } else {
+            ui_room_show_occupants(mucwin->roomjid);
+        }
     } else if (g_strcmp0(args[0], "hide") == 0) {
-        ui_room_hide_occupants(mucwin->roomjid);
+        if (g_strcmp0(args[1], "jid") == 0) {
+            mucwin->showjid = FALSE;
+            ui_room_update_occupants(mucwin->roomjid);
+        } else {
+            ui_room_hide_occupants(mucwin->roomjid);
+        }
     } else {
         cons_show("Usage: %s", help.usage);
     }
@@ -2876,7 +2961,7 @@ cmd_bookmark(gchar **args, struct cmd_help_t help)
             char *password = g_hash_table_lookup(options, "password");
             char *autojoin = g_hash_table_lookup(options, "autojoin");
 
-            if (autojoin != NULL) {
+            if (autojoin) {
                 if ((strcmp(autojoin, "on") != 0) && (strcmp(autojoin, "off") != 0)) {
                     cons_show("Usage: %s", help.usage);
                     cons_show("");
@@ -2924,7 +3009,7 @@ cmd_disco(gchar **args, struct cmd_help_t help)
     }
 
     GString *jid = g_string_new("");
-    if (args[1] != NULL) {
+    if (args[1]) {
         jid = g_string_append(jid, args[1]);
     } else {
         Jid *jidp = jid_create(jabber_get_fulljid());
@@ -3040,82 +3125,48 @@ gboolean
 cmd_tiny(gchar **args, struct cmd_help_t help)
 {
     char *url = args[0];
-    win_type_t win_type = ui_current_win_type();
-
-    if (!tinyurl_valid(url)) {
-        GString *error = g_string_new("/tiny, badly formed URL: ");
-        g_string_append(error, url);
-        cons_show_error(error->str);
-        if (win_type != WIN_CONSOLE) {
-            ui_current_error_line(error->str);
-        }
-        g_string_free(error, TRUE);
-    } else if (win_type != WIN_CONSOLE) {
-        char *tiny = tinyurl_get(url);
+    ProfWin *current = wins_get_current();
 
-        if (tiny != NULL) {
-            if (win_type == WIN_CHAT) {
-                ProfChatWin *chatwin = wins_get_current_chat();
-#ifdef HAVE_LIBOTR
-                if (otr_is_secure(chatwin->barejid)) {
-                    char *encrypted = otr_encrypt_message(chatwin->barejid, tiny);
-                    if (encrypted != NULL) {
-                        message_send_chat(chatwin->barejid, encrypted);
-                        otr_free_message(encrypted);
-                        if (prefs_get_boolean(PREF_CHLOG)) {
-                            const char *jid = jabber_get_fulljid();
-                            Jid *jidp = jid_create(jid);
-                            char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
-                            if (strcmp(pref_otr_log, "on") == 0) {
-                                chat_log_chat(jidp->barejid, chatwin->barejid, tiny, PROF_OUT_LOG, NULL);
-                            } else if (strcmp(pref_otr_log, "redact") == 0) {
-                                chat_log_chat(jidp->barejid, chatwin->barejid, "[redacted]", PROF_OUT_LOG, NULL);
-                            }
-                            prefs_free_string(pref_otr_log);
-                            jid_destroy(jidp);
-                        }
+    if (current->type != WIN_CHAT && current->type != WIN_MUC && current->type != WIN_PRIVATE) {
+        cons_show("/tiny can only be used in chat windows");
+        return TRUE;
+    }
 
-                        ui_outgoing_chat_msg("me", chatwin->barejid, tiny);
-                    } else {
-                        cons_show_error("Failed to send message.");
-                    }
-                } else {
-                    message_send_chat(chatwin->barejid, tiny);
-                    if (prefs_get_boolean(PREF_CHLOG)) {
-                        const char *jid = jabber_get_fulljid();
-                        Jid *jidp = jid_create(jid);
-                        chat_log_chat(jidp->barejid, chatwin->barejid, tiny, PROF_OUT_LOG, NULL);
-                        jid_destroy(jidp);
-                    }
+    if (!tinyurl_valid(url)) {
+        win_vprint(current, '-', NULL, 0, THEME_ERROR, "", "/tiny, badly formed URL: %s", url);
+        return TRUE;
+    }
 
-                    ui_outgoing_chat_msg("me", chatwin->barejid, tiny);
-                }
-#else
-                message_send_chat(chatwin->barejid, tiny);
-                if (prefs_get_boolean(PREF_CHLOG)) {
-                    const char *jid = jabber_get_fulljid();
-                    Jid *jidp = jid_create(jid);
-                    chat_log_chat(jidp->barejid, chatwin->barejid, tiny, PROF_OUT_LOG, NULL);
-                    jid_destroy(jidp);
-                }
+    char *tiny = tinyurl_get(url);
+    if (!tiny) {
+        win_print(current, '-', NULL, 0, THEME_ERROR, "", "Couldn't create tinyurl.");
+        return TRUE;
+    }
 
-                ui_outgoing_chat_msg("me", chatwin->barejid, tiny);
-#endif
-            } else if (win_type == WIN_PRIVATE) {
-                ProfPrivateWin *privatewin = wins_get_current_private();
-                message_send_private(tiny, privatewin->fulljid);
-                ui_outgoing_private_msg("me", privatewin->fulljid, tiny);
-            } else if (win_type == WIN_MUC) {
-                ProfMucWin *mucwin = wins_get_current_muc();
-                message_send_groupchat(tiny, mucwin->roomjid);
-            }
-            free(tiny);
-        } else {
-            cons_show_error("Couldn't get tinyurl.");
-        }
-    } else {
-        cons_show("/tiny can only be used in chat windows");
+    switch (current->type){
+    case WIN_CHAT:
+    {
+        ProfChatWin *chatwin = wins_get_current_chat();
+        cl_ev_send_msg(chatwin, tiny);
+        break;
+    }
+    case WIN_PRIVATE:
+    {
+        ProfPrivateWin *privatewin = wins_get_current_private();
+        cl_ev_send_priv_msg(privatewin, tiny);
+        break;
+    }
+    case WIN_MUC:
+    {
+        ProfMucWin *mucwin = wins_get_current_muc();
+        cl_ev_send_muc_msg(mucwin, tiny);
+        break;
     }
+    default:
+        break;
+    }
+
+    free(tiny);
 
     return TRUE;
 }
@@ -3135,7 +3186,7 @@ cmd_close(gchar **args, struct cmd_help_t help)
     int count = 0;
 
     if (args[0] == NULL) {
-        index = ui_current_win_index();
+        index = wins_get_current_num();
     } else if (strcmp(args[0], "all") == 0) {
         count = ui_close_all_wins();
         if (count == 0) {
@@ -3170,19 +3221,15 @@ cmd_close(gchar **args, struct cmd_help_t help)
         return TRUE;
     }
 
-    if (!ui_win_exists(index)) {
+    ProfWin *window = wins_get_by_num(index);
+    if (!window) {
         cons_show("Window is not open.");
         return TRUE;
     }
 
     // check for unsaved form
     if (ui_win_has_unsaved_form(index)) {
-        ProfWin *window = wins_get_current();
-        if (wins_is_current(window)) {
-            ui_current_print_line("You have unsaved changes, use /form submit or /form cancel");
-        } else {
-            cons_show("Cannot close form window with unsaved changes, use /form submit or /form cancel");
-        }
+        ui_current_print_line("You have unsaved changes, use /form submit or /form cancel");
         return TRUE;
     }
 
@@ -3203,7 +3250,7 @@ cmd_leave(gchar **args, struct cmd_help_t help)
 {
     jabber_conn_status_t conn_status = jabber_get_connection_status();
     win_type_t win_type = ui_current_win_type();
-    int index = ui_current_win_index();
+    int index = wins_get_current_num();
 
     if (win_type != WIN_MUC) {
         cons_show("You can only use the /leave command in a chat room.");
@@ -3257,10 +3304,46 @@ cmd_wrap(gchar **args, struct cmd_help_t help)
 gboolean
 cmd_time(gchar **args, struct cmd_help_t help)
 {
-    prefs_set_string(PREF_TIME, args[0]);
-    cons_show("Time format set to '%s'", args[0]);
-    wins_resize_all();
-    return TRUE;
+    if (g_strcmp0(args[0], "statusbar") == 0) {
+        if (args[1] == NULL) {
+            cons_show("Current status bar time format is '%s'.", prefs_get_string(PREF_TIME_STATUSBAR));
+            return TRUE;
+        } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
+            prefs_set_string(PREF_TIME_STATUSBAR, args[2]);
+            cons_show("Status bar time format set to '%s'.", args[2]);
+            ui_redraw();
+            return TRUE;
+        } else if (g_strcmp0(args[1], "off") == 0) {
+            prefs_set_string(PREF_TIME_STATUSBAR, "");
+            cons_show("Status bar time display disabled.");
+            ui_redraw();
+            return TRUE;
+        } else {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        }
+    } else if (g_strcmp0(args[0], "main") == 0) {
+        if (args[1] == NULL) {
+            cons_show("Current time format is '%s'.", prefs_get_string(PREF_TIME));
+            return TRUE;
+        } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
+            prefs_set_string(PREF_TIME, args[2]);
+            cons_show("Time format set to '%s'.", args[2]);
+            wins_resize_all();
+            return TRUE;
+        } else if (g_strcmp0(args[1], "off") == 0) {
+            prefs_set_string(PREF_TIME, "");
+            cons_show("Time display disabled.");
+            wins_resize_all();
+            return TRUE;
+        } else {
+            cons_show("Usage: %s", help.usage);
+            return TRUE;
+        }
+    } else {
+        cons_show("Usage: %s", help.usage);
+        return TRUE;
+    }
 }
 
 gboolean
@@ -3481,7 +3564,6 @@ cmd_inpblock(gchar **args, struct cmd_help_t help)
 {
     char *subcmd = args[0];
     char *value = args[1];
-    int intval;
 
     if (g_strcmp0(subcmd, "timeout") == 0) {
         if (value == NULL) {
@@ -3489,10 +3571,16 @@ cmd_inpblock(gchar **args, struct cmd_help_t help)
             return TRUE;
         }
 
-        if (_strtoi(value, &intval, 1, 1000) == 0) {
+        int intval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(value, &intval, 1, 1000, &err_msg);
+        if (res) {
             cons_show("Input blocking set to %d milliseconds.", intval);
             prefs_set_inpblock(intval);
             ui_input_nonblocking(FALSE);
+        } else {
+            cons_show(err_msg);
+            free(err_msg);
         }
 
         return TRUE;
@@ -3522,16 +3610,22 @@ cmd_log(gchar **args, struct cmd_help_t help)
 {
     char *subcmd = args[0];
     char *value = args[1];
-    int intval;
 
     if (strcmp(subcmd, "maxsize") == 0) {
         if (value == NULL) {
             cons_show("Usage: %s", help.usage);
             return TRUE;
         }
-        if (_strtoi(value, &intval, PREFS_MIN_LOG_SIZE, INT_MAX) == 0) {
+
+        int intval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(value, &intval, PREFS_MIN_LOG_SIZE, INT_MAX, &err_msg);
+        if (res) {
             prefs_set_max_log_size(intval);
             cons_show("Log maxinum size set to %d bytes", intval);
+        } else {
+            cons_show(err_msg);
+            free(err_msg);
         }
         return TRUE;
     }
@@ -3571,9 +3665,11 @@ gboolean
 cmd_reconnect(gchar **args, struct cmd_help_t help)
 {
     char *value = args[0];
-    int intval;
 
-    if (_strtoi(value, &intval, 0, INT_MAX) == 0) {
+    int intval = 0;
+    char *err_msg = NULL;
+    gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+    if (res) {
         prefs_set_reconnect(intval);
         if (intval == 0) {
             cons_show("Reconnect disabled.", intval);
@@ -3581,7 +3677,9 @@ cmd_reconnect(gchar **args, struct cmd_help_t help)
             cons_show("Reconnect interval set to %d seconds.", intval);
         }
     } else {
+        cons_show(err_msg);
         cons_show("Usage: %s", help.usage);
+        free(err_msg);
     }
 
     return TRUE;
@@ -3591,9 +3689,11 @@ gboolean
 cmd_autoping(gchar **args, struct cmd_help_t help)
 {
     char *value = args[0];
-    int intval;
 
-    if (_strtoi(value, &intval, 0, INT_MAX) == 0) {
+    int intval = 0;
+    char *err_msg = NULL;
+    gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+    if (res) {
         prefs_set_autoping(intval);
         iq_set_autoping(intval);
         if (intval == 0) {
@@ -3602,7 +3702,9 @@ cmd_autoping(gchar **args, struct cmd_help_t help)
             cons_show("Autoping interval set to %d seconds.", intval);
         }
     } else {
+        cons_show(err_msg);
         cons_show("Usage: %s", help.usage);
+        free(err_msg);
     }
 
     return TRUE;
@@ -3633,7 +3735,6 @@ cmd_autoaway(gchar **args, struct cmd_help_t help)
 {
     char *setting = args[0];
     char *value = args[1];
-    int minutesval;
 
     if ((strcmp(setting, "mode") != 0) && (strcmp(setting, "time") != 0) &&
             (strcmp(setting, "message") != 0) && (strcmp(setting, "check") != 0)) {
@@ -3654,9 +3755,15 @@ cmd_autoaway(gchar **args, struct cmd_help_t help)
     }
 
     if (strcmp(setting, "time") == 0) {
-        if (_strtoi(value, &minutesval, 1, INT_MAX) == 0) {
+        int minutesval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(value, &minutesval, 1, INT_MAX, &err_msg);
+        if (res) {
             prefs_set_autoaway_time(minutesval);
             cons_show("Auto away time set to: %d minutes.", minutesval);
+        } else {
+            cons_show(err_msg);
+            free(err_msg);
         }
 
         return TRUE;
@@ -3693,13 +3800,18 @@ cmd_priority(gchar **args, struct cmd_help_t help)
     }
 
     char *value = args[0];
-    int intval;
 
-    if (_strtoi(value, &intval, -128, 127) == 0) {
+    int intval = 0;
+    char *err_msg = NULL;
+    gboolean res = strtoi_range(value, &intval, -128, 127, &err_msg);
+    if (res) {
         accounts_set_priority_all(jabber_get_account_name(), intval);
         resource_presence_t last_presence = accounts_get_last_presence(jabber_get_account_name());
-        presence_update(last_presence, jabber_get_presence_message(), 0);
+        cl_ev_presence_send(last_presence, jabber_get_presence_message(), 0);
         cons_show("Priority set to %d.", intval);
+    } else {
+        cons_show(err_msg);
+        free(err_msg);
     }
 
     return TRUE;
@@ -3865,6 +3977,42 @@ cmd_history(gchar **args, struct cmd_help_t help)
 }
 
 gboolean
+cmd_carbons(gchar **args, struct cmd_help_t help)
+{
+    gboolean result = _cmd_set_boolean_preference(args[0], help,
+        "Message carbons preference", PREF_CARBONS);
+
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
+
+    if (conn_status == JABBER_CONNECTED) {
+        // enable carbons
+        if (strcmp(args[0], "on") == 0) {
+            iq_enable_carbons();
+        }
+        else if (strcmp(args[0], "off") == 0){
+            iq_disable_carbons();
+        }
+    }
+
+    return result;
+}
+
+gboolean
+cmd_receipts(gchar **args, struct cmd_help_t help)
+{
+    if (g_strcmp0(args[0], "send") == 0) {
+        return _cmd_set_boolean_preference(args[1], help,
+            "Send delivery receipts", PREF_RECEIPTS_SEND);
+    } else if (g_strcmp0(args[0], "request") == 0) {
+        return _cmd_set_boolean_preference(args[1], help,
+            "Request delivery receipets", PREF_RECEIPTS_REQUEST);
+    } else {
+        cons_show("Usage: %s", help.usage);
+        return TRUE;
+    }
+}
+
+gboolean
 cmd_away(gchar **args, struct cmd_help_t help)
 {
     _update_presence(RESOURCE_AWAY, "away", args);
@@ -4013,14 +4161,18 @@ cmd_otr(gchar **args, struct cmd_help_t help)
         return TRUE;
 
     } else if (strcmp(args[0], "start") == 0) {
-        if (args[1] != NULL) {
+        if (args[1]) {
             char *contact = args[1];
             char *barejid = roster_barejid_from_name(contact);
             if (barejid == NULL) {
                 barejid = contact;
             }
 
-            ui_new_chat_win(barejid);
+            ProfChatWin *chatwin = wins_get_chat(barejid);
+            if (!chatwin) {
+                chatwin = ui_ev_new_chat_win(barejid);
+            }
+            ui_ev_focus_win((ProfWin*)chatwin);
 
             if (ui_current_win_is_otr()) {
                 ui_current_print_formatted_line('!', 0, "You are already in an OTR session.");
@@ -4029,7 +4181,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();
-                    message_send_chat(barejid, otr_query_message);
+                    message_send_chat_encrypted(barejid, otr_query_message);
                 } else {
                     ui_gone_secure(barejid, otr_is_trusted(barejid));
                 }
@@ -4047,7 +4199,7 @@ cmd_otr(gchar **args, struct cmd_help_t help)
                 } else {
                     ProfChatWin *chatwin = ui_get_current_chat();
                     char *otr_query_message = otr_start_query();
-                    message_send_chat(chatwin->barejid, otr_query_message);
+                    message_send_chat_encrypted(chatwin->barejid, otr_query_message);
                 }
             }
         }
@@ -4175,7 +4327,7 @@ _update_presence(const resource_presence_t resource_presence,
     if (conn_status != JABBER_CONNECTED) {
         cons_show("You are not currently connected.");
     } else {
-        presence_update(resource_presence, msg, 0);
+        cl_ev_presence_send(resource_presence, msg, 0);
         ui_update_presence(resource_presence, msg, show);
     }
 }
@@ -4213,27 +4365,6 @@ _cmd_set_boolean_preference(gchar *arg, struct cmd_help_t help,
     return TRUE;
 }
 
-static int
-_strtoi(char *str, int *saveptr, int min, int max)
-{
-    char *ptr;
-    int val;
-
-    errno = 0;
-    val = (int)strtol(str, &ptr, 0);
-    if (errno != 0 || *str == '\0' || *ptr != '\0') {
-        cons_show("Could not convert \"%s\" to a number.", str);
-        return -1;
-    } else if (val < min || val > max) {
-        cons_show("Value %s out of range. Must be in %d..%d.", str, min, max);
-        return -1;
-    }
-
-    *saveptr = val;
-
-    return 0;
-}
-
 static void
 _cmd_show_filtered_help(char *heading, gchar *cmd_filter[], int filter_size)
 {
@@ -4249,7 +4380,7 @@ _cmd_show_filtered_help(char *heading, gchar *cmd_filter[], int filter_size)
     }
 
     GList *curr = ordered_commands;
-    while (curr != NULL) {
+    while (curr) {
         Command *cmd = curr->data;
         cons_show("%-12s: %s", cmd->cmd, cmd->help.short_help);
         curr = g_list_next(curr);
diff --git a/src/command/commands.h b/src/command/commands.h
index f4e040a9..7b7e7c93 100644
--- a/src/command/commands.h
+++ b/src/command/commands.h
@@ -1,7 +1,7 @@
 /*
  * commands.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -62,6 +62,9 @@ typedef struct cmd_t {
     CommandHelp help;
 } Command;
 
+gboolean cmd_execute_alias(const char * const inp, gboolean *ran);
+gboolean cmd_execute_default(const char * inp);
+
 gboolean cmd_about(gchar **args, struct cmd_help_t help);
 gboolean cmd_account(gchar **args, struct cmd_help_t help);
 gboolean cmd_autoaway(gchar **args, struct cmd_help_t help);
@@ -85,6 +88,8 @@ gboolean cmd_grlog(gchar **args, struct cmd_help_t help);
 gboolean cmd_group(gchar **args, struct cmd_help_t help);
 gboolean cmd_help(gchar **args, struct cmd_help_t help);
 gboolean cmd_history(gchar **args, struct cmd_help_t help);
+gboolean cmd_carbons(gchar **args, struct cmd_help_t help);
+gboolean cmd_receipts(gchar **args, struct cmd_help_t help);
 gboolean cmd_info(gchar **args, struct cmd_help_t help);
 gboolean cmd_intype(gchar **args, struct cmd_help_t help);
 gboolean cmd_invite(gchar **args, struct cmd_help_t help);
diff --git a/src/common.c b/src/common.c
index 7638da31..772e24d3 100644
--- a/src/common.c
+++ b/src/common.c
@@ -1,7 +1,7 @@
 /*
  * common.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -202,6 +202,33 @@ str_contains(const char str[], int size, char ch)
     return 0;
 }
 
+gboolean
+strtoi_range(char *str, int *saveptr, int min, int max, char **err_msg)
+{
+    char *ptr;
+    int val;
+
+    errno = 0;
+    val = (int)strtol(str, &ptr, 0);
+    if (errno != 0 || *str == '\0' || *ptr != '\0') {
+        GString *err_str = g_string_new("");
+        g_string_printf(err_str, "Could not convert \"%s\" to a number.", str);
+        *err_msg = err_str->str;
+        g_string_free(err_str, FALSE);
+        return FALSE;
+    } else if (val < min || val > max) {
+        GString *err_str = g_string_new("");
+        g_string_printf(err_str, "Value %s out of range. Must be in %d..%d.", str, min, max);
+        *err_msg = err_str->str;
+        g_string_free(err_str, FALSE);
+        return FALSE;
+    }
+
+    *saveptr = val;
+
+    return TRUE;
+}
+
 int
 utf8_display_len(const char * const str)
 {
@@ -224,6 +251,18 @@ utf8_display_len(const char * const str)
     return len;
 }
 
+gboolean
+utf8_is_printable(const wint_t ch)
+{
+    char bytes[MB_CUR_MAX+1];
+    size_t utf_len = wcrtomb(bytes, ch, NULL);
+    bytes[utf_len] = '\0';
+
+    gunichar unichar = g_utf8_get_char(bytes);
+
+    return g_unichar_isprint(unichar) && (ch != KEY_MOUSE);
+}
+
 char *
 prof_getline(FILE *stream)
 {
@@ -248,7 +287,7 @@ prof_getline(FILE *stream)
 
         result = (char *)realloc(s, s_size + buf_size);
         if (result == NULL) {
-            if (s != NULL) {
+            if (s) {
                 free(s);
                 s = NULL;
             }
@@ -286,7 +325,7 @@ release_get_latest()
     curl_easy_perform(handle);
     curl_easy_cleanup(handle);
 
-    if (output.buffer != NULL) {
+    if (output.buffer) {
         output.buffer[output.size++] = '\0';
         return output.buffer;
     } else {
@@ -393,10 +432,10 @@ gchar *
 xdg_get_config_home(void)
 {
     gchar *xdg_config_home = getenv("XDG_CONFIG_HOME");
-    if (xdg_config_home != NULL)
+    if (xdg_config_home)
         g_strstrip(xdg_config_home);
 
-    if ((xdg_config_home != NULL) && (strcmp(xdg_config_home, "") != 0)) {
+    if (xdg_config_home && (strcmp(xdg_config_home, "") != 0)) {
         return strdup(xdg_config_home);
     } else {
         GString *default_path = g_string_new(getenv("HOME"));
@@ -412,10 +451,10 @@ gchar *
 xdg_get_data_home(void)
 {
     gchar *xdg_data_home = getenv("XDG_DATA_HOME");
-    if (xdg_data_home != NULL)
+    if (xdg_data_home)
         g_strstrip(xdg_data_home);
 
-    if ((xdg_data_home != NULL) && (strcmp(xdg_data_home, "") != 0)) {
+    if (xdg_data_home && (strcmp(xdg_data_home, "") != 0)) {
         return strdup(xdg_data_home);
     } else {
         GString *default_path = g_string_new(getenv("HOME"));
@@ -435,7 +474,7 @@ create_unique_id(char *prefix)
     GString *result_str = g_string_new("");
 
     unique_id++;
-    if (prefix != NULL) {
+    if (prefix) {
         g_string_printf(result_str, "prof_%s_%lu", prefix, unique_id);
     } else {
         g_string_printf(result_str, "prof_%lu", unique_id);
@@ -488,25 +527,34 @@ cmp_win_num(gconstpointer a, gconstpointer b)
 int
 get_next_available_win_num(GList *used)
 {
-    used = g_list_sort(used, cmp_win_num);
     // only console used
     if (g_list_length(used) == 1) {
         return 2;
     } else {
+        GList *sorted = NULL;
+        GList *curr = used;
+        while (curr) {
+            sorted = g_list_insert_sorted(sorted, curr->data, cmp_win_num);
+            curr = g_list_next(curr);
+        }
+
         int result = 0;
         int last_num = 1;
-        GList *curr = used;
+        curr = sorted;
         // skip console
         curr = g_list_next(curr);
-        while (curr != NULL) {
+        while (curr) {
             int curr_num = GPOINTER_TO_INT(curr->data);
+
             if (((last_num != 9) && ((last_num + 1) != curr_num)) ||
                     ((last_num == 9) && (curr_num != 0))) {
                 result = last_num + 1;
                 if (result == 10) {
                     result = 0;
                 }
+                g_list_free(sorted);
                 return (result);
+
             } else {
                 last_num = curr_num;
                 if (last_num == 0) {
@@ -520,6 +568,7 @@ get_next_available_win_num(GList *used)
             result = 0;
         }
 
+        g_list_free(sorted);
         return result;
     }
 }
@@ -566,3 +615,25 @@ get_file_or_linked(char *loc, char *basedir)
 
     return true_loc;
 }
+
+char *
+strip_arg_quotes(const char * const input)
+{
+    char *unquoted = strdup(input);
+
+    // Remove starting quote if it exists
+    if(strchr(unquoted, '"')) {
+        if(strchr(unquoted, ' ') + 1 == strchr(unquoted, '"')) {
+            memmove(strchr(unquoted, '"'), strchr(unquoted, '"')+1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
+        }
+    }
+
+    // Remove ending quote if it exists
+    if(strchr(unquoted, '"')) {
+        if(strchr(unquoted, '\0') - 1 == strchr(unquoted, '"')) {
+            memmove(strchr(unquoted, '"'), strchr(unquoted, '"')+1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
+        }
+    }
+
+    return unquoted;
+}
diff --git a/src/common.h b/src/common.h
index 26d4a99a..9521a701 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,7 +1,7 @@
 /*
  * common.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -36,6 +36,13 @@
 #define COMMON_H
 
 #include <stdio.h>
+#include <wchar.h>
+
+#ifdef HAVE_NCURSESW_NCURSES_H
+#include <ncursesw/ncurses.h>
+#elif HAVE_NCURSES_H
+#include <ncurses.h>
+#endif
 
 #include <glib.h>
 
@@ -104,7 +111,9 @@ gboolean mkdir_recursive(const char *dir);
 char * str_replace(const char *string, const char *substr,
     const char *replacement);
 int str_contains(const char str[], int size, char ch);
+gboolean strtoi_range(char *str, int *saveptr, int min, int max, char **err_msg);
 int utf8_display_len(const char * const str);
+gboolean utf8_is_printable(const wint_t ch);
 char * prof_getline(FILE *stream);
 char* release_get_latest(void);
 gboolean release_is_new(char *found_version);
@@ -123,5 +132,6 @@ int cmp_win_num(gconstpointer a, gconstpointer b);
 int get_next_available_win_num(GList *used);
 
 char* get_file_or_linked(char *loc, char *basedir);
+char * strip_arg_quotes(const char * const input);
 
 #endif
diff --git a/src/config/account.c b/src/config/account.c
index 6df00382..857d049b 100644
--- a/src/config/account.c
+++ b/src/config/account.c
@@ -1,7 +1,7 @@
 /*
  * account.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -34,12 +34,14 @@
 
 #include <stdlib.h>
 #include <string.h>
+#include <assert.h>
 
 #include <glib.h>
 
 #include "jid.h"
 #include "config/account.h"
 #include "common.h"
+#include "log.h"
 
 ProfAccount*
 account_new(const gchar * const name, const gchar * const jid,
@@ -55,19 +57,19 @@ account_new(const gchar * const name, const gchar * const jid,
 
     new_account->name = strdup(name);
 
-    if (jid != NULL) {
+    if (jid) {
         new_account->jid = strdup(jid);
     } else {
         new_account->jid = strdup(name);
     }
 
-    if (password != NULL) {
+    if (password) {
         new_account->password = strdup(password);
     } else {
         new_account->password = NULL;
     }
 
-    if (eval_password != NULL) {
+    if (eval_password) {
         new_account->eval_password = strdup(eval_password);
     } else {
         new_account->eval_password = NULL;
@@ -75,13 +77,13 @@ account_new(const gchar * const name, const gchar * const jid,
 
     new_account->enabled = enabled;
 
-    if (server != NULL) {
+    if (server) {
         new_account->server = strdup(server);
     } else {
         new_account->server = NULL;
     }
 
-    if (resource != NULL) {
+    if (resource) {
         new_account->resource = strdup(resource);
     } else {
         new_account->resource = NULL;
@@ -132,7 +134,7 @@ account_new(const gchar * const name, const gchar * const jid,
         new_account->muc_nick = strdup(muc_nick);
     }
 
-    if (otr_policy != NULL) {
+    if (otr_policy) {
         new_account->otr_policy = strdup(otr_policy);
     } else {
         new_account->otr_policy = NULL;
@@ -148,17 +150,55 @@ account_new(const gchar * const name, const gchar * const jid,
 char *
 account_create_full_jid(ProfAccount *account)
 {
-    if (account->resource != NULL) {
+    if (account->resource) {
         return create_fulljid(account->jid, account->resource);
     } else {
         return strdup(account->jid);
     }
 }
 
+gboolean
+account_eval_password(ProfAccount *account)
+{
+    assert(account != NULL);
+    assert(account->eval_password != NULL);
+
+    // Evaluate as shell command to retrieve password
+    GString *cmd = g_string_new("");
+    g_string_append_printf(cmd, "%s 2>/dev/null", account->eval_password);
+
+    FILE *stream = popen(cmd->str, "r");
+    g_string_free(cmd, TRUE);
+    if (stream) {
+        // Limit to READ_BUF_SIZE bytes to prevent overflows in the case of a poorly chosen command
+        account->password = g_malloc(READ_BUF_SIZE);
+        if (!account->password) {
+            log_error("Failed to allocate enough memory to read eval_password output");
+            return FALSE;
+        }
+        account->password = fgets(account->password, READ_BUF_SIZE, stream);
+        pclose(stream);
+        if (!account->password) {
+            log_error("No result from eval_password.");
+            return FALSE;
+        }
+
+        // strip trailing newline
+        if (g_str_has_suffix(account->password, "\n")) {
+            account->password[strlen(account->password)-1] = '\0';
+        }
+    } else {
+        log_error("popen failed when running eval_password.");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
 void
 account_free(ProfAccount *account)
 {
-    if (account != NULL) {
+    if (account) {
         free(account->name);
         free(account->jid);
         free(account->password);
diff --git a/src/config/account.h b/src/config/account.h
index ab43234d..218f8ce7 100644
--- a/src/config/account.h
+++ b/src/config/account.h
@@ -1,7 +1,7 @@
 /*
  * account.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -69,9 +69,8 @@ ProfAccount* account_new(const gchar * const name, const gchar * const jid,
     const gchar * const muc_service, const gchar * const muc_nick,
     const gchar * const otr_policy, GList *otr_manual, GList *otr_opportunistic,
     GList *otr_always);
-
 char* account_create_full_jid(ProfAccount *account);
-
+gboolean account_eval_password(ProfAccount *account);
 void account_free(ProfAccount *account);
 
 #endif
diff --git a/src/config/accounts.c b/src/config/accounts.c
index 4d4d47d0..d68f3a55 100644
--- a/src/config/accounts.c
+++ b/src/config/accounts.c
@@ -1,7 +1,7 @@
 /*
  * accounts.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -147,9 +147,9 @@ accounts_add(const char *account_name, const char *altdomain, const int port)
     const char *barejid = account_name;
     const char *resource = "profanity";
     Jid *jid = jid_create(account_name);
-    if (jid != NULL) {
+    if (jid) {
         barejid = jid->barejid;
-        if (jid->resourcepart != NULL) {
+        if (jid->resourcepart) {
             resource = jid->resourcepart;
         }
     }
@@ -159,7 +159,7 @@ accounts_add(const char *account_name, const char *altdomain, const int port)
         g_key_file_set_boolean(accounts, account_name, "enabled", TRUE);
         g_key_file_set_string(accounts, account_name, "jid", barejid);
         g_key_file_set_string(accounts, account_name, "resource", resource);
-        if (altdomain != NULL) {
+        if (altdomain) {
             g_key_file_set_string(accounts, account_name, "server", altdomain);
         }
         if (port != 0) {
@@ -252,7 +252,7 @@ accounts_get_account(const char * const name)
         gsize length;
         GList *otr_manual = NULL;
         gchar **manual = g_key_file_get_string_list(accounts, name, "otr.manual", &length, NULL);
-        if (manual != NULL) {
+        if (manual) {
             int i = 0;
             for (i = 0; i < length; i++) {
                 otr_manual = g_list_append(otr_manual, strdup(manual[i]));
@@ -262,7 +262,7 @@ accounts_get_account(const char * const name)
 
         GList *otr_opportunistic = NULL;
         gchar **opportunistic = g_key_file_get_string_list(accounts, name, "otr.opportunistic", &length, NULL);
-        if (opportunistic != NULL) {
+        if (opportunistic) {
             int i = 0;
             for (i = 0; i < length; i++) {
                 otr_opportunistic = g_list_append(otr_opportunistic, strdup(opportunistic[i]));
@@ -272,7 +272,7 @@ accounts_get_account(const char * const name)
 
         GList *otr_always = NULL;
         gchar **always = g_key_file_get_string_list(accounts, name, "otr.always", &length, NULL);
-        if (always != NULL) {
+        if (always) {
             int i = 0;
             for (i = 0; i < length; i++) {
                 otr_always = g_list_append(otr_always, strdup(always[i]));
@@ -356,7 +356,7 @@ accounts_rename(const char * const account_name, const char * const new_name)
     int i;
     for (i = 0; i < ARRAY_SIZE(string_keys); i++) {
         char *value = g_key_file_get_string(accounts, account_name, string_keys[i], NULL);
-        if (value != NULL) {
+        if (value) {
             g_key_file_set_string(accounts, new_name, string_keys[i], value);
             g_free(value);
         }
@@ -386,10 +386,10 @@ void
 accounts_set_jid(const char * const account_name, const char * const value)
 {
     Jid *jid = jid_create(value);
-    if (jid != NULL) {
+    if (jid) {
         if (accounts_account_exists(account_name)) {
             g_key_file_set_string(accounts, account_name, "jid", jid->barejid);
-            if (jid->resourcepart != NULL) {
+            if (jid->resourcepart) {
                 g_key_file_set_string(accounts, account_name, "resource", jid->resourcepart);
             }
 
@@ -509,7 +509,7 @@ accounts_add_otr_policy(const char * const account_name, const char * const cont
         GList *glist = NULL;
 
         // list found
-        if (list != NULL) {
+        if (list) {
             int i = 0;
             for (i = 0; i < length; i++) {
                 // item already in list, exit function
@@ -529,7 +529,7 @@ accounts_add_otr_policy(const char * const account_name, const char * const cont
             const gchar* new_list[g_list_length(glist)+1];
             GList *curr = glist;
             i = 0;
-            while (curr != NULL) {
+            while (curr) {
                 new_list[i++] = strdup(curr->data);
                 curr = g_list_next(curr);
             }
@@ -572,7 +572,7 @@ _remove_from_list(GKeyFile *accounts, const char * const account_name, const cha
     gsize length;
     gchar **list = g_key_file_get_string_list(accounts, account_name, key, &length, NULL);
 
-    if (list != NULL) {
+    if (list) {
         int i = 0;
         GList *glist = NULL;
         gboolean deleted = FALSE;
@@ -595,7 +595,7 @@ _remove_from_list(GKeyFile *accounts, const char * const account_name, const cha
                 const gchar* new_list[g_list_length(glist)+1];
                 GList *curr = glist;
                 i = 0;
-                while (curr != NULL) {
+                while (curr) {
                     new_list[i++] = strdup(curr->data);
                     curr = g_list_next(curr);
                 }
@@ -766,7 +766,7 @@ accounts_get_last_presence(const char * const account_name)
         result = RESOURCE_ONLINE;
     }
 
-    if (setting != NULL) {
+    if (setting) {
         g_free(setting);
     }
     return result;
@@ -796,7 +796,7 @@ accounts_get_login_presence(const char * const account_name)
         result = RESOURCE_ONLINE;
     }
 
-    if (setting != NULL) {
+    if (setting) {
         g_free(setting);
     }
     return result;
@@ -809,9 +809,9 @@ _fix_legacy_accounts(const char * const account_name)
     const char *barejid = account_name;
     const char *resource = "profanity";
     Jid *jid = jid_create(account_name);
-    if (jid != NULL) {
+    if (jid) {
         barejid = jid->barejid;
-        if (jid->resourcepart != NULL) {
+        if (jid->resourcepart) {
             resource = jid->resourcepart;
         }
     }
@@ -828,7 +828,7 @@ _fix_legacy_accounts(const char * const account_name)
         _save_accounts();
     }
 
-    // acounts with no muc service or nick
+    // accounts with no muc service or nick
     if (!g_key_file_has_key(accounts, account_name, "muc.service", NULL)) {
         gchar *account_jid = g_key_file_get_string(accounts, account_name, "jid", NULL);
         Jid *jidp = jid_create(account_jid);
diff --git a/src/config/accounts.h b/src/config/accounts.h
index cbbe88e6..50307b5b 100644
--- a/src/config/accounts.h
+++ b/src/config/accounts.h
@@ -1,7 +1,7 @@
 /*
  * accounts.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 652d4ed9..c0d6f6e5 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -1,7 +1,7 @@
 /*
  * preferences.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -62,7 +62,7 @@
 #define PREF_GROUP_ALIAS "alias"
 #define PREF_GROUP_OTR "otr"
 
-#define INPBLOCK_DEFAULT 20
+#define INPBLOCK_DEFAULT 1000
 
 static gchar *prefs_loc;
 static GKeyFile *prefs;
@@ -95,35 +95,17 @@ prefs_load(void)
 
     err = NULL;
     log_maxsize = g_key_file_get_integer(prefs, PREF_GROUP_LOGGING, "maxsize", &err);
-    if (err != NULL) {
+    if (err) {
         log_maxsize = 0;
         g_error_free(err);
     }
 
-    // move pre 0.4.1 OTR preferences to [otr] group
+    // move pre 0.4.6 OTR warn preferences to [ui] group
     err = NULL;
-    gboolean ui_otr_warn = g_key_file_get_boolean(prefs, PREF_GROUP_UI, "otr.warn", &err);
+    gboolean otr_warn = g_key_file_get_boolean(prefs, PREF_GROUP_OTR, "warn", &err);
     if (err == NULL) {
-        g_key_file_set_boolean(prefs, PREF_GROUP_OTR, _get_key(PREF_OTR_WARN), ui_otr_warn);
-        g_key_file_remove_key(prefs, PREF_GROUP_UI, "otr.warn", NULL);
-    } else {
-        g_error_free(err);
-    }
-
-    err = NULL;
-    gchar *ui_otr_log = g_key_file_get_string(prefs, PREF_GROUP_LOGGING, "otr", &err);
-    if (err == NULL) {
-        g_key_file_set_string(prefs, PREF_GROUP_OTR, _get_key(PREF_OTR_LOG), ui_otr_log);
-        g_key_file_remove_key(prefs, PREF_GROUP_LOGGING, "otr", NULL);
-    } else {
-        g_error_free(err);
-    }
-
-    err = NULL;
-    gchar *ui_otr_policy = g_key_file_get_string(prefs, "policy", "otr.policy", &err);
-    if (err == NULL) {
-        g_key_file_set_string(prefs, PREF_GROUP_OTR, _get_key(PREF_OTR_POLICY), ui_otr_policy);
-        g_key_file_remove_group(prefs, "policy", NULL);
+        g_key_file_set_boolean(prefs, PREF_GROUP_UI, _get_key(PREF_OTR_WARN), otr_warn);
+        g_key_file_remove_key(prefs, PREF_GROUP_OTR, "warn", NULL);
     } else {
         g_error_free(err);
     }
@@ -198,7 +180,7 @@ prefs_get_string(preference_t pref)
     char *result = g_key_file_get_string(prefs, group, key, NULL);
 
     if (result == NULL) {
-        if (def != NULL) {
+        if (def) {
             return strdup(def);
         } else {
             return NULL;
@@ -211,7 +193,7 @@ prefs_get_string(preference_t pref)
 void
 prefs_free_string(char *pref)
 {
-    if (pref != NULL) {
+    if (pref) {
         free(pref);
     }
     pref = NULL;
@@ -358,7 +340,7 @@ prefs_get_occupants_size(void)
     gint result = g_key_file_get_integer(prefs, PREF_GROUP_UI, "occupants.size", NULL);
 
     if (result > 99 || result < 1) {
-        return 20;
+        return 15;
     } else {
         return result;
     }
@@ -377,7 +359,7 @@ prefs_get_roster_size(void)
     gint result = g_key_file_get_integer(prefs, PREF_GROUP_UI, "roster.size", NULL);
 
     if (result > 99 || result < 1) {
-        return 20;
+        return 25;
     } else {
         return result;
     }
@@ -437,7 +419,7 @@ prefs_get_aliases(void)
             char *name = keys[i];
             char *value = g_key_file_get_string(prefs, PREF_GROUP_ALIAS, name, NULL);
 
-            if (value != NULL) {
+            if (value) {
                 ProfAlias *alias = malloc(sizeof(struct prof_alias_t));
                 alias->name = strdup(name);
                 alias->value = strdup(value);
@@ -517,6 +499,7 @@ _get_group(preference_t pref)
         case PREF_HISTORY:
         case PREF_MOUSE:
         case PREF_OCCUPANTS:
+        case PREF_OCCUPANTS_JID:
         case PREF_STATUSES:
         case PREF_STATUSES_CONSOLE:
         case PREF_STATUSES_CHAT:
@@ -525,12 +508,14 @@ _get_group(preference_t pref)
         case PREF_PRESENCE:
         case PREF_WRAP:
         case PREF_TIME:
+        case PREF_TIME_STATUSBAR:
         case PREF_ROSTER:
         case PREF_ROSTER_OFFLINE:
         case PREF_ROSTER_RESOURCE:
         case PREF_ROSTER_BY:
         case PREF_RESOURCE_TITLE:
         case PREF_RESOURCE_MESSAGE:
+        case PREF_OTR_WARN:
         case PREF_INPBLOCK_DYNAMIC:
             return PREF_GROUP_UI;
         case PREF_STATES:
@@ -558,8 +543,10 @@ _get_group(preference_t pref)
             return PREF_GROUP_PRESENCE;
         case PREF_CONNECT_ACCOUNT:
         case PREF_DEFAULT_ACCOUNT:
+        case PREF_CARBONS:
+        case PREF_RECEIPTS_SEND:
+        case PREF_RECEIPTS_REQUEST:
             return PREF_GROUP_CONNECTION;
-        case PREF_OTR_WARN:
         case PREF_OTR_LOG:
         case PREF_OTR_POLICY:
             return PREF_GROUP_OTR;
@@ -593,10 +580,18 @@ _get_key(preference_t pref)
             return "intype";
         case PREF_HISTORY:
             return "history";
+        case PREF_CARBONS:
+            return "carbons";
+        case PREF_RECEIPTS_SEND:
+            return "receipts.send";
+        case PREF_RECEIPTS_REQUEST:
+            return "receipts.request";
         case PREF_MOUSE:
             return "mouse";
         case PREF_OCCUPANTS:
             return "occupants";
+        case PREF_OCCUPANTS_JID:
+            return "occupants.jid";
         case PREF_MUC_PRIVILEGES:
             return "privileges";
         case PREF_STATUSES:
@@ -648,7 +643,7 @@ _get_key(preference_t pref)
         case PREF_OTR_LOG:
             return "log";
         case PREF_OTR_WARN:
-            return "warn";
+            return "otr.warn";
         case PREF_OTR_POLICY:
             return "policy";
         case PREF_LOG_ROTATE:
@@ -661,6 +656,8 @@ _get_key(preference_t pref)
             return "wrap";
         case PREF_TIME:
             return "time";
+        case PREF_TIME_STATUSBAR:
+            return "time.statusbar";
         case PREF_ROSTER:
             return "roster";
         case PREF_ROSTER_OFFLINE:
@@ -691,15 +688,24 @@ _get_default_boolean(preference_t pref)
         case PREF_AUTOAWAY_CHECK:
         case PREF_LOG_ROTATE:
         case PREF_LOG_SHARED:
+        case PREF_NOTIFY_MESSAGE:
         case PREF_NOTIFY_MESSAGE_CURRENT:
         case PREF_NOTIFY_ROOM_CURRENT:
+        case PREF_NOTIFY_TYPING:
         case PREF_NOTIFY_TYPING_CURRENT:
+        case PREF_NOTIFY_SUB:
+        case PREF_NOTIFY_INVITE:
         case PREF_SPLASH:
         case PREF_OCCUPANTS:
         case PREF_MUC_PRIVILEGES:
         case PREF_PRESENCE:
         case PREF_WRAP:
         case PREF_INPBLOCK_DYNAMIC:
+        case PREF_RESOURCE_TITLE:
+        case PREF_RESOURCE_MESSAGE:
+        case PREF_ROSTER:
+        case PREF_ROSTER_OFFLINE:
+        case PREF_ROSTER_RESOURCE:
             return TRUE;
         default:
             return FALSE;
@@ -715,7 +721,7 @@ _get_default_string(preference_t pref)
     {
         case PREF_AUTOAWAY_MODE:
         case PREF_NOTIFY_ROOM:
-            return "off";
+            return "on";
         case PREF_OTR_LOG:
             return "redact";
         case PREF_OTR_POLICY:
@@ -725,9 +731,11 @@ _get_default_string(preference_t pref)
         case PREF_STATUSES_MUC:
             return "all";
         case PREF_ROSTER_BY:
-            return "none";
+            return "presence";
         case PREF_TIME:
             return "%H:%M:%S";
+        case PREF_TIME_STATUSBAR:
+            return "%H:%M";
         default:
             return NULL;
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 68286f09..4455eca1 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -1,7 +1,7 @@
 /*
  * preferences.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -59,9 +59,13 @@ typedef enum {
     PREF_FLASH,
     PREF_INTYPE,
     PREF_HISTORY,
+    PREF_CARBONS,
+    PREF_RECEIPTS_SEND,
+    PREF_RECEIPTS_REQUEST,
     PREF_MOUSE,
     PREF_OCCUPANTS,
     PREF_OCCUPANTS_SIZE,
+    PREF_OCCUPANTS_JID,
     PREF_ROSTER,
     PREF_ROSTER_SIZE,
     PREF_ROSTER_OFFLINE,
@@ -71,6 +75,7 @@ typedef enum {
     PREF_PRESENCE,
     PREF_WRAP,
     PREF_TIME,
+    PREF_TIME_STATUSBAR,
     PREF_STATUSES,
     PREF_STATUSES_CONSOLE,
     PREF_STATUSES_CHAT,
diff --git a/src/config/theme.c b/src/config/theme.c
index 6d3c5938..f73dee19 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -1,7 +1,7 @@
 /*
  * theme.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -121,6 +121,7 @@ static struct colours_t {
         NCURSES_COLOR_T otruntrusted;
         NCURSES_COLOR_T rosterheader;
         NCURSES_COLOR_T occupantsheader;
+        NCURSES_COLOR_T receiptsent;
 } colour_prefs;
 
 static NCURSES_COLOR_T _lookup_colour(const char * const colour);
@@ -159,7 +160,7 @@ _theme_load_file(const char * const theme_name)
 {
     // use default theme
     if (theme_name == NULL || strcmp(theme_name, "default") == 0) {
-        if (theme != NULL) {
+        if (theme) {
             g_key_file_free(theme);
         }
         theme = g_key_file_new();
@@ -172,12 +173,12 @@ _theme_load_file(const char * const theme_name)
             return FALSE;
         }
 
-        if (theme_loc != NULL) {
+        if (theme_loc) {
             g_string_free(theme_loc, TRUE);
         }
         theme_loc = new_theme_file;
         log_info("Loading theme \"%s\"", theme_name);
-        if (theme != NULL) {
+        if (theme) {
             g_key_file_free(theme);
         }
         theme = g_key_file_new();
@@ -192,7 +193,9 @@ GSList *
 theme_list(void)
 {
     GSList *result = NULL;
-    _theme_list_dir(_get_themes_dir(), &result);
+    char *themes_dir = _get_themes_dir();
+    _theme_list_dir(themes_dir, &result);
+    free(themes_dir);
 #ifdef THEMES_PATH
     _theme_list_dir(THEMES_PATH, &result);
 #endif
@@ -202,10 +205,10 @@ theme_list(void)
 void
 theme_close(void)
 {
-    if (theme != NULL) {
+    if (theme) {
         g_key_file_free(theme);
     }
-    if (theme_loc != NULL) {
+    if (theme_loc) {
         g_string_free(theme_loc, TRUE);
     }
     if (bold_items) {
@@ -249,47 +252,48 @@ theme_init_colours(void)
     // chat
     init_pair(25, colour_prefs.me, colour_prefs.bkgnd);
     init_pair(26, colour_prefs.them, colour_prefs.bkgnd);
+    init_pair(27, colour_prefs.receiptsent, colour_prefs.bkgnd);
 
     // room chat
-    init_pair(27, colour_prefs.roominfo, colour_prefs.bkgnd);
-    init_pair(28, colour_prefs.roommention, colour_prefs.bkgnd);
+    init_pair(28, colour_prefs.roominfo, colour_prefs.bkgnd);
+    init_pair(29, colour_prefs.roommention, colour_prefs.bkgnd);
 
     // statuses
-    init_pair(29, colour_prefs.online, colour_prefs.bkgnd);
-    init_pair(30, colour_prefs.offline, colour_prefs.bkgnd);
-    init_pair(31, colour_prefs.away, colour_prefs.bkgnd);
-    init_pair(32, colour_prefs.chat, colour_prefs.bkgnd);
-    init_pair(33, colour_prefs.dnd, colour_prefs.bkgnd);
-    init_pair(34, colour_prefs.xa, colour_prefs.bkgnd);
+    init_pair(30, colour_prefs.online, colour_prefs.bkgnd);
+    init_pair(31, colour_prefs.offline, colour_prefs.bkgnd);
+    init_pair(32, colour_prefs.away, colour_prefs.bkgnd);
+    init_pair(33, colour_prefs.chat, colour_prefs.bkgnd);
+    init_pair(34, colour_prefs.dnd, colour_prefs.bkgnd);
+    init_pair(35, colour_prefs.xa, colour_prefs.bkgnd);
 
     // states
-    init_pair(35, colour_prefs.typing, colour_prefs.bkgnd);
-    init_pair(36, colour_prefs.gone, colour_prefs.bkgnd);
+    init_pair(36, colour_prefs.typing, colour_prefs.bkgnd);
+    init_pair(37, colour_prefs.gone, colour_prefs.bkgnd);
 
     // subscription status
-    init_pair(37, colour_prefs.subscribed, colour_prefs.bkgnd);
-    init_pair(38, colour_prefs.unsubscribed, colour_prefs.bkgnd);
+    init_pair(38, colour_prefs.subscribed, colour_prefs.bkgnd);
+    init_pair(39, colour_prefs.unsubscribed, colour_prefs.bkgnd);
 
     // otr messages
-    init_pair(39, colour_prefs.otrstartedtrusted, colour_prefs.bkgnd);
-    init_pair(40, colour_prefs.otrstarteduntrusted, colour_prefs.bkgnd);
-    init_pair(41, colour_prefs.otrended, colour_prefs.bkgnd);
-    init_pair(42, colour_prefs.otrtrusted, colour_prefs.bkgnd);
-    init_pair(43, colour_prefs.otruntrusted, colour_prefs.bkgnd);
+    init_pair(40, colour_prefs.otrstartedtrusted, colour_prefs.bkgnd);
+    init_pair(41, colour_prefs.otrstarteduntrusted, colour_prefs.bkgnd);
+    init_pair(42, colour_prefs.otrended, colour_prefs.bkgnd);
+    init_pair(43, colour_prefs.otrtrusted, colour_prefs.bkgnd);
+    init_pair(44, colour_prefs.otruntrusted, colour_prefs.bkgnd);
 
     // subwin headers
-    init_pair(44, colour_prefs.rosterheader, colour_prefs.bkgnd);
-    init_pair(45, colour_prefs.occupantsheader, colour_prefs.bkgnd);
+    init_pair(45, colour_prefs.rosterheader, colour_prefs.bkgnd);
+    init_pair(46, colour_prefs.occupantsheader, colour_prefs.bkgnd);
 
     // raw
-    init_pair(46, COLOR_WHITE, colour_prefs.bkgnd);
-    init_pair(47, COLOR_GREEN, colour_prefs.bkgnd);
-    init_pair(48, COLOR_RED, colour_prefs.bkgnd);
-    init_pair(49, COLOR_YELLOW, colour_prefs.bkgnd);
-    init_pair(50, COLOR_BLUE, colour_prefs.bkgnd);
-    init_pair(51, COLOR_CYAN, colour_prefs.bkgnd);
-    init_pair(52, COLOR_BLACK, colour_prefs.bkgnd);
-    init_pair(53, COLOR_MAGENTA, colour_prefs.bkgnd);
+    init_pair(47, COLOR_WHITE, colour_prefs.bkgnd);
+    init_pair(48, COLOR_GREEN, colour_prefs.bkgnd);
+    init_pair(49, COLOR_RED, colour_prefs.bkgnd);
+    init_pair(50, COLOR_YELLOW, colour_prefs.bkgnd);
+    init_pair(51, COLOR_BLUE, colour_prefs.bkgnd);
+    init_pair(52, COLOR_CYAN, colour_prefs.bkgnd);
+    init_pair(53, COLOR_BLACK, colour_prefs.bkgnd);
+    init_pair(54, COLOR_MAGENTA, colour_prefs.bkgnd);
 }
 
 static NCURSES_COLOR_T
@@ -395,6 +399,7 @@ _load_colours(void)
     _set_colour("them",                     &colour_prefs.them,                 COLOR_GREEN,    THEME_THEM);
     _set_colour("roster.header",            &colour_prefs.rosterheader,         COLOR_YELLOW,   THEME_ROSTER_HEADER);
     _set_colour("occupants.header",         &colour_prefs.occupantsheader,      COLOR_YELLOW,   THEME_OCCUPANTS_HEADER);
+    _set_colour("receipt.sent",             &colour_prefs.receiptsent,          COLOR_RED,      THEME_RECEIPT_SENT);
 }
 
 static void
@@ -419,20 +424,22 @@ _set_boolean_preference(char *prefstr, preference_t pref)
 static void
 _load_preferences(void)
 {
-    _set_boolean_preference("intype", PREF_INTYPE);
     _set_boolean_preference("beep", PREF_BEEP);
     _set_boolean_preference("flash", PREF_FLASH);
-    _set_boolean_preference("privileges", PREF_MUC_PRIVILEGES);
-    _set_boolean_preference("presence", PREF_PRESENCE);
+    _set_boolean_preference("splash", PREF_SPLASH);
     _set_boolean_preference("wrap", PREF_WRAP);
-
     _set_string_preference("time", PREF_TIME);
-    _set_string_preference("statuses.muc", PREF_STATUSES_MUC);
+    _set_string_preference("time.statusbar", PREF_TIME_STATUSBAR);
+
+    _set_boolean_preference("resource.title", PREF_RESOURCE_TITLE);
+    _set_boolean_preference("resource.message", PREF_RESOURCE_MESSAGE);
+
     _set_string_preference("statuses.console", PREF_STATUSES_CONSOLE);
     _set_string_preference("statuses.chat", PREF_STATUSES_CHAT);
+    _set_string_preference("statuses.muc", PREF_STATUSES_MUC);
 
     _set_boolean_preference("occupants", PREF_OCCUPANTS);
-
+    _set_boolean_preference("occupants.jid", PREF_OCCUPANTS_JID);
     if (g_key_file_has_key(theme, "ui", "occupants.size", NULL)) {
         gint occupants_size = g_key_file_get_integer(theme, "ui", "occupants.size", NULL);
         prefs_set_occupants_size(occupants_size);
@@ -442,12 +449,16 @@ _load_preferences(void)
     _set_boolean_preference("roster.offline", PREF_ROSTER_OFFLINE);
     _set_boolean_preference("roster.resource", PREF_ROSTER_RESOURCE);
     _set_string_preference("roster.by", PREF_ROSTER_BY);
-
     if (g_key_file_has_key(theme, "ui", "roster.size", NULL)) {
         gint roster_size = g_key_file_get_integer(theme, "ui", "roster.size", NULL);
         prefs_set_roster_size(roster_size);
     }
 
+    _set_boolean_preference("privileges", PREF_MUC_PRIVILEGES);
+
+    _set_boolean_preference("presence", PREF_PRESENCE);
+    _set_boolean_preference("intype", PREF_INTYPE);
+
     _set_boolean_preference("otr.warn", PREF_OTR_WARN);
 }
 
@@ -465,9 +476,9 @@ void
 _theme_list_dir(const gchar * const dir, GSList **result)
 {
     GDir *themes = g_dir_open(dir, 0, NULL);
-    if (themes != NULL) {
+    if (themes) {
         const gchar *theme = g_dir_read_name(themes);
-        while (theme != NULL) {
+        while (theme) {
             *result = g_slist_append(*result, strdup(theme));
             theme = g_dir_read_name(themes);
         }
@@ -481,7 +492,7 @@ _theme_find(const char * const theme_name)
     GString *path = NULL;
     gchar *themes_dir = _get_themes_dir();
 
-    if (themes_dir != NULL) {
+    if (themes_dir) {
         path = g_string_new(themes_dir);
         g_free(themes_dir);
         g_string_append(path, "/");
@@ -557,42 +568,43 @@ theme_attrs(theme_item_t attrs)
     case THEME_STATUS_NEW:              result = COLOR_PAIR(24); break;
     case THEME_ME:                      result = COLOR_PAIR(25); break;
     case THEME_THEM:                    result = COLOR_PAIR(26); break;
-    case THEME_ROOMINFO:                result = COLOR_PAIR(27); break;
-    case THEME_ROOMMENTION:             result = COLOR_PAIR(28); break;
-    case THEME_ONLINE:                  result = COLOR_PAIR(29); break;
-    case THEME_OFFLINE:                 result = COLOR_PAIR(30); break;
-    case THEME_AWAY:                    result = COLOR_PAIR(31); break;
-    case THEME_CHAT:                    result = COLOR_PAIR(32); break;
-    case THEME_DND:                     result = COLOR_PAIR(33); break;
-    case THEME_XA:                      result = COLOR_PAIR(34); break;
-    case THEME_TYPING:                  result = COLOR_PAIR(35); break;
-    case THEME_GONE:                    result = COLOR_PAIR(36); break;
-    case THEME_SUBSCRIBED:              result = COLOR_PAIR(37); break;
-    case THEME_UNSUBSCRIBED:            result = COLOR_PAIR(38); break;
-    case THEME_OTR_STARTED_TRUSTED:     result = COLOR_PAIR(39); break;
-    case THEME_OTR_STARTED_UNTRUSTED:   result = COLOR_PAIR(40); break;
-    case THEME_OTR_ENDED:               result = COLOR_PAIR(41); break;
-    case THEME_OTR_TRUSTED:             result = COLOR_PAIR(42); break;
-    case THEME_OTR_UNTRUSTED:           result = COLOR_PAIR(43); break;
-    case THEME_ROSTER_HEADER:           result = COLOR_PAIR(44); break;
-    case THEME_OCCUPANTS_HEADER:        result = COLOR_PAIR(45); break;
-    case THEME_WHITE:                   result = COLOR_PAIR(46); break;
-    case THEME_WHITE_BOLD:              result = COLOR_PAIR(46); break;
-    case THEME_GREEN:                   result = COLOR_PAIR(47); break;
-    case THEME_GREEN_BOLD:              result = COLOR_PAIR(47); break;
-    case THEME_RED:                     result = COLOR_PAIR(48); break;
-    case THEME_RED_BOLD:                result = COLOR_PAIR(48); break;
-    case THEME_YELLOW:                  result = COLOR_PAIR(49); break;
-    case THEME_YELLOW_BOLD:             result = COLOR_PAIR(49); break;
-    case THEME_BLUE:                    result = COLOR_PAIR(50); break;
-    case THEME_BLUE_BOLD:               result = COLOR_PAIR(50); break;
-    case THEME_CYAN:                    result = COLOR_PAIR(51); break;
-    case THEME_CYAN_BOLD:               result = COLOR_PAIR(51); break;
-    case THEME_BLACK:                   result = COLOR_PAIR(52); break;
-    case THEME_BLACK_BOLD:              result = COLOR_PAIR(52); break;
-    case THEME_MAGENTA:                 result = COLOR_PAIR(53); break;
-    case THEME_MAGENTA_BOLD:            result = COLOR_PAIR(53); break;
-    default:                           break;
+    case THEME_RECEIPT_SENT:            result = COLOR_PAIR(27); break;
+    case THEME_ROOMINFO:                result = COLOR_PAIR(28); break;
+    case THEME_ROOMMENTION:             result = COLOR_PAIR(29); break;
+    case THEME_ONLINE:                  result = COLOR_PAIR(30); break;
+    case THEME_OFFLINE:                 result = COLOR_PAIR(31); break;
+    case THEME_AWAY:                    result = COLOR_PAIR(32); break;
+    case THEME_CHAT:                    result = COLOR_PAIR(33); break;
+    case THEME_DND:                     result = COLOR_PAIR(34); break;
+    case THEME_XA:                      result = COLOR_PAIR(35); break;
+    case THEME_TYPING:                  result = COLOR_PAIR(36); break;
+    case THEME_GONE:                    result = COLOR_PAIR(37); break;
+    case THEME_SUBSCRIBED:              result = COLOR_PAIR(38); break;
+    case THEME_UNSUBSCRIBED:            result = COLOR_PAIR(39); break;
+    case THEME_OTR_STARTED_TRUSTED:     result = COLOR_PAIR(40); break;
+    case THEME_OTR_STARTED_UNTRUSTED:   result = COLOR_PAIR(41); break;
+    case THEME_OTR_ENDED:               result = COLOR_PAIR(42); break;
+    case THEME_OTR_TRUSTED:             result = COLOR_PAIR(43); break;
+    case THEME_OTR_UNTRUSTED:           result = COLOR_PAIR(44); break;
+    case THEME_ROSTER_HEADER:           result = COLOR_PAIR(45); break;
+    case THEME_OCCUPANTS_HEADER:        result = COLOR_PAIR(46); break;
+    case THEME_WHITE:                   result = COLOR_PAIR(47); break;
+    case THEME_WHITE_BOLD:              result = COLOR_PAIR(47); break;
+    case THEME_GREEN:                   result = COLOR_PAIR(48); break;
+    case THEME_GREEN_BOLD:              result = COLOR_PAIR(48); break;
+    case THEME_RED:                     result = COLOR_PAIR(49); break;
+    case THEME_RED_BOLD:                result = COLOR_PAIR(49); break;
+    case THEME_YELLOW:                  result = COLOR_PAIR(50); break;
+    case THEME_YELLOW_BOLD:             result = COLOR_PAIR(50); break;
+    case THEME_BLUE:                    result = COLOR_PAIR(51); break;
+    case THEME_BLUE_BOLD:               result = COLOR_PAIR(51); break;
+    case THEME_CYAN:                    result = COLOR_PAIR(52); break;
+    case THEME_CYAN_BOLD:               result = COLOR_PAIR(52); break;
+    case THEME_BLACK:                   result = COLOR_PAIR(53); break;
+    case THEME_BLACK_BOLD:              result = COLOR_PAIR(53); break;
+    case THEME_MAGENTA:                 result = COLOR_PAIR(54); break;
+    case THEME_MAGENTA_BOLD:            result = COLOR_PAIR(54); break;
+    default:                            break;
     }
 
     if (g_hash_table_lookup(bold_items, GINT_TO_POINTER(attrs))) {
diff --git a/src/config/theme.h b/src/config/theme.h
index a6a580f8..13099eb4 100644
--- a/src/config/theme.h
+++ b/src/config/theme.h
@@ -1,7 +1,7 @@
 /*
  * theme.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -90,6 +90,7 @@ typedef enum {
     THEME_OTR_UNTRUSTED,
     THEME_OCCUPANTS_HEADER,
     THEME_ROSTER_HEADER,
+    THEME_RECEIPT_SENT,
     THEME_NONE,
     THEME_WHITE,
     THEME_WHITE_BOLD,
diff --git a/src/contact.c b/src/contact.c
index f16f1679..713af955 100644
--- a/src/contact.c
+++ b/src/contact.c
@@ -1,7 +1,7 @@
 /*
  * contact.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -45,7 +45,9 @@
 
 struct p_contact_t {
     char *barejid;
+    gchar *barejid_collate_key;
     char *name;
+    gchar *name_collate_key;
     GSList *groups;
     char *subscription;
     char *offline_message;
@@ -62,21 +64,24 @@ p_contact_new(const char * const barejid, const char * const name,
 {
     PContact contact = malloc(sizeof(struct p_contact_t));
     contact->barejid = strdup(barejid);
+    contact->barejid_collate_key = g_utf8_collate_key(contact->barejid, -1);
 
-    if (name != NULL) {
+    if (name) {
         contact->name = strdup(name);
+        contact->name_collate_key = g_utf8_collate_key(contact->name, -1);
     } else {
         contact->name = NULL;
+        contact->name_collate_key = NULL;
     }
 
     contact->groups = groups;
 
-    if (subscription != NULL)
+    if (subscription)
         contact->subscription = strdup(subscription);
     else
         contact->subscription = strdup("none");
 
-    if (offline_message != NULL)
+    if (offline_message)
         contact->offline_message = strdup(offline_message);
     else
         contact->offline_message = NULL;
@@ -96,15 +101,17 @@ void
 p_contact_set_name(const PContact contact, const char * const name)
 {
     FREE_SET_NULL(contact->name);
-    if (name != NULL) {
+    FREE_SET_NULL(contact->name_collate_key);
+    if (name) {
         contact->name = strdup(name);
+        contact->name_collate_key = g_utf8_collate_key(contact->name, -1);
     }
 }
 
 void
 p_contact_set_groups(const PContact contact, GSList *groups)
 {
-    if (contact->groups != NULL) {
+    if (contact->groups) {
         g_slist_free_full(contact->groups, g_free);
         contact->groups = NULL;
     }
@@ -116,7 +123,7 @@ gboolean
 p_contact_in_group(const PContact contact, const char * const group)
 {
     GSList *groups = contact->groups;
-    while (groups != NULL) {
+    while (groups) {
         if (strcmp(groups->data, group) == 0) {
             return TRUE;
         }
@@ -144,17 +151,19 @@ p_contact_remove_resource(PContact contact, const char * const resource)
 void
 p_contact_free(PContact contact)
 {
-    if (contact != NULL) {
+    if (contact) {
         free(contact->barejid);
+        free(contact->barejid_collate_key);
         free(contact->name);
+        free(contact->name_collate_key);
         free(contact->subscription);
         free(contact->offline_message);
 
-        if (contact->groups != NULL) {
+        if (contact->groups) {
             g_slist_free_full(contact->groups, g_free);
         }
 
-        if (contact->last_activity != NULL) {
+        if (contact->last_activity) {
             g_date_time_unref(contact->last_activity);
         }
 
@@ -171,15 +180,27 @@ p_contact_barejid(const PContact contact)
 }
 
 const char *
+p_contact_barejid_collate_key(const PContact contact)
+{
+    return contact->barejid_collate_key;
+}
+
+const char *
 p_contact_name(const PContact contact)
 {
     return contact->name;
 }
 
 const char *
+p_contact_name_collate_key(const PContact contact)
+{
+    return contact->name_collate_key;
+}
+
+const char *
 p_contact_name_or_jid(const PContact contact)
 {
-    if (contact->name != NULL) {
+    if (contact->name) {
         return contact->name;
     } else {
         return contact->barejid;
@@ -247,7 +268,7 @@ _get_most_available_resource(PContact contact)
     Resource *current = curr->data;
     Resource *highest = current;
     curr = g_list_next(curr);
-    while (curr != NULL) {
+    while (curr) {
         current = curr->data;
 
         // priority is same as current highest, choose presence
@@ -381,14 +402,14 @@ void
 p_contact_set_presence(const PContact contact, Resource *resource)
 {
     g_hash_table_replace(contact->available_resources, strdup(resource->name), resource);
-    autocomplete_add(contact->resource_ac, strdup(resource->name));
+    autocomplete_add(contact->resource_ac, resource->name);
 }
 
 void
 p_contact_set_subscription(const PContact contact, const char * const subscription)
 {
     FREE_SET_NULL(contact->subscription);
-    if (subscription != NULL) {
+    if (subscription) {
         contact->subscription = strdup(subscription);
     }
 }
@@ -402,12 +423,12 @@ p_contact_set_pending_out(const PContact contact, gboolean pending_out)
 void
 p_contact_set_last_activity(const PContact contact, GDateTime *last_activity)
 {
-    if (contact->last_activity != NULL) {
+    if (contact->last_activity) {
         g_date_time_unref(contact->last_activity);
         contact->last_activity = NULL;
     }
 
-    if (last_activity != NULL) {
+    if (last_activity) {
         contact->last_activity = g_date_time_ref(last_activity);
     }
 }
diff --git a/src/contact.h b/src/contact.h
index 17a3b210..343b230f 100644
--- a/src/contact.h
+++ b/src/contact.h
@@ -1,7 +1,7 @@
 /*
  * contact.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -47,7 +47,9 @@ void p_contact_add_resource(PContact contact, Resource *resource);
 gboolean p_contact_remove_resource(PContact contact, const char * const resource);
 void p_contact_free(PContact contact);
 const char* p_contact_barejid(PContact contact);
+const char* p_contact_barejid_collate_key(PContact contact);
 const char* p_contact_name(PContact contact);
+const char* p_contact_name_collate_key(PContact contact);
 const char* p_contact_name_or_jid(const PContact contact);
 const char* p_contact_presence(PContact contact);
 const char* p_contact_status(PContact contact);
diff --git a/src/event/client_events.c b/src/event/client_events.c
new file mode 100644
index 00000000..f0f763a6
--- /dev/null
+++ b/src/event/client_events.c
@@ -0,0 +1,95 @@
+/*
+ * client_events.c
+ *
+ * Copyright (C) 2012 - 2015 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 <glib.h>
+
+#include "config.h"
+#include "log.h"
+#include "ui/ui.h"
+#include "ui/windows.h"
+#include "xmpp/xmpp.h"
+#ifdef HAVE_LIBOTR
+#include "otr/otr.h"
+#endif
+
+jabber_conn_status_t
+cl_ev_connect_jid(const char * const jid, const char * const passwd, const char * const altdomain, const int port)
+{
+    cons_show("Connecting as %s", jid);
+    return jabber_connect_with_details(jid, passwd, altdomain, port);
+}
+
+jabber_conn_status_t
+cl_ev_connect_account(ProfAccount *account)
+{
+    char *jid = account_create_full_jid(account);
+    cons_show("Connecting with account %s as %s", account->name, jid);
+    free(jid);
+
+    return jabber_connect_with_account(account);
+}
+
+void
+cl_ev_presence_send(const resource_presence_t presence_type, const char * const msg, const int idle)
+{
+    presence_send(presence_type, msg, idle);
+}
+
+void
+cl_ev_send_msg(ProfChatWin *chatwin, const char * const msg)
+{
+    chat_state_active(chatwin->state);
+
+#ifdef HAVE_LIBOTR
+    otr_on_message_send(chatwin, msg);
+#else
+    char *id = message_send_chat(chatwin->barejid, msg);
+    chat_log_msg_out(chatwin->barejid, msg);
+    ui_outgoing_chat_msg(chatwin, msg, id);
+    free(id);
+#endif
+}
+
+void
+cl_ev_send_muc_msg(ProfMucWin *mucwin, const char * const msg)
+{
+    message_send_groupchat(mucwin->roomjid, msg);
+}
+
+void
+cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char * const msg)
+{
+    message_send_private(privwin->fulljid, msg);
+    ui_outgoing_private_msg(privwin, msg);
+}
\ No newline at end of file
diff --git a/src/event/client_events.h b/src/event/client_events.h
new file mode 100644
index 00000000..207299c5
--- /dev/null
+++ b/src/event/client_events.h
@@ -0,0 +1,47 @@
+/*
+ * client_events.h
+ *
+ * Copyright (C) 2012 - 2015 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 CLIENT_EVENTS_H
+#define CLIENT_EVENTS_H
+
+jabber_conn_status_t cl_ev_connect_jid(const char * const jid, const char * const passwd, const char * const altdomain, const int port);
+jabber_conn_status_t cl_ev_connect_account(ProfAccount *account);
+
+void cl_ev_presence_send(const resource_presence_t presence_type, const char * const msg, const int idle);
+
+void cl_ev_send_msg(ProfChatWin *chatwin, const char * const msg);
+void cl_ev_send_muc_msg(ProfMucWin *mucwin, const char * const msg);
+void cl_ev_send_priv_msg(ProfPrivateWin *privwin, const char * const msg);
+
+#endif
\ No newline at end of file
diff --git a/src/event/server_events.c b/src/event/server_events.c
new file mode 100644
index 00000000..e2e910a3
--- /dev/null
+++ b/src/event/server_events.c
@@ -0,0 +1,522 @@
+/*
+ * server_events.c
+ *
+ * Copyright (C) 2012 - 2015 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 <string.h>
+#include <stdlib.h>
+
+#include "config.h"
+
+#include "chat_session.h"
+#include "log.h"
+#include "muc.h"
+#include "config/preferences.h"
+#include "config/account.h"
+#include "roster_list.h"
+
+#ifdef HAVE_LIBOTR
+#include "otr/otr.h"
+#endif
+
+#include "ui/ui.h"
+
+void
+sv_ev_login_account_success(char *account_name)
+{
+    ProfAccount *account = accounts_get_account(account_name);
+
+#ifdef HAVE_LIBOTR
+    otr_on_connect(account);
+#endif
+
+    ui_handle_login_account_success(account);
+
+    // attempt to rejoin rooms with passwords
+    GList *curr = muc_rooms();
+    while (curr) {
+        char *password = muc_password(curr->data);
+        if (password) {
+            char *nick = muc_nick(curr->data);
+            presence_join_room(curr->data, nick, password);
+        }
+        curr = g_list_next(curr);
+    }
+    g_list_free(curr);
+
+    log_info("%s logged in successfully", account->jid);
+    account_free(account);
+}
+
+void
+sv_ev_roster_received(void)
+{
+    if (prefs_get_boolean(PREF_ROSTER)) {
+        ui_show_roster();
+    }
+}
+
+void
+sv_ev_lost_connection(void)
+{
+    cons_show_error("Lost connection.");
+    roster_clear();
+    muc_invites_clear();
+    chat_sessions_clear();
+    ui_disconnected();
+}
+
+void
+sv_ev_failed_login(void)
+{
+    cons_show_error("Login failed.");
+    log_info("Login failed");
+}
+
+void
+sv_ev_room_invite(jabber_invite_t invite_type,
+    const char * const invitor, const char * const room,
+    const char * const reason, const char * const password)
+{
+    if (!muc_active(room) && !muc_invites_contain(room)) {
+        cons_show_room_invite(invitor, room, reason);
+        muc_invites_add(room, password);
+    }
+}
+
+void
+sv_ev_room_broadcast(const char *const room_jid,
+    const char * const message)
+{
+    if (muc_roster_complete(room_jid)) {
+        ui_room_broadcast(room_jid, message);
+    } else {
+        muc_pending_broadcasts_add(room_jid, message);
+    }
+}
+
+void
+sv_ev_room_subject(const char * const room, const char * const nick, const char * const subject)
+{
+    muc_set_subject(room, subject);
+    if (muc_roster_complete(room)) {
+        ui_room_subject(room, nick, subject);
+    }
+}
+
+void
+sv_ev_room_history(const char * const room_jid, const char * const nick,
+    GTimeVal tv_stamp, const char * const message)
+{
+    ui_room_history(room_jid, nick, tv_stamp, message);
+}
+
+void
+sv_ev_room_message(const char * const room_jid, const char * const nick,
+    const char * const message)
+{
+    ui_room_message(room_jid, nick, message);
+
+    if (prefs_get_boolean(PREF_GRLOG)) {
+        Jid *jid = jid_create(jabber_get_fulljid());
+        groupchat_log_chat(jid->barejid, room_jid, nick, message);
+        jid_destroy(jid);
+    }
+}
+
+void
+sv_ev_incoming_private_message(const char * const fulljid, char *message)
+{
+    ui_incoming_private_msg(fulljid, message, NULL);
+}
+
+void
+sv_ev_carbon(char *barejid, char *message)
+{
+    ui_outgoing_chat_msg_carbon(barejid, message);
+}
+
+void
+sv_ev_incoming_message(char *barejid, char *resource, char *message)
+{
+#ifdef HAVE_LIBOTR
+    otr_on_message_recv(barejid, resource, message);
+#else
+    ui_incoming_msg(barejid, resource, message, NULL);
+    chat_log_msg_in(barejid, message);
+#endif
+}
+
+void
+sv_ev_delayed_private_message(const char * const fulljid, char *message, GTimeVal tv_stamp)
+{
+    ui_incoming_private_msg(fulljid, message, &tv_stamp);
+}
+
+void
+sv_ev_delayed_message(char *barejid, char *message, GTimeVal tv_stamp)
+{
+    ui_incoming_msg(barejid, NULL, message, &tv_stamp);
+    chat_log_msg_in_delayed(barejid, message, &tv_stamp);
+}
+
+void
+sv_ev_message_receipt(char *barejid, char *id)
+{
+    ui_message_receipt(barejid, id);
+}
+
+void
+sv_ev_typing(char *barejid, char *resource)
+{
+    ui_contact_typing(barejid, resource);
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_typing(barejid, resource);
+    }
+}
+
+void
+sv_ev_paused(char *barejid, char *resource)
+{
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_paused(barejid, resource);
+    }
+}
+
+void
+sv_ev_inactive(char *barejid, char *resource)
+{
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_inactive(barejid, resource);
+    }
+}
+
+void
+sv_ev_gone(const char * const barejid, const char * const resource)
+{
+    ui_recipient_gone(barejid, resource);
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_gone(barejid, resource);
+    }
+}
+
+void
+sv_ev_activity(const char * const barejid, const char * const resource, gboolean send_states)
+{
+    if (ui_chat_win_exists(barejid)) {
+        chat_session_recipient_active(barejid, resource, send_states);
+    }
+}
+
+void
+sv_ev_subscription(const char *barejid, jabber_subscr_t type)
+{
+    switch (type) {
+    case PRESENCE_SUBSCRIBE:
+        /* TODO: auto-subscribe if needed */
+        cons_show("Received authorization request from %s", barejid);
+        log_info("Received authorization request from %s", barejid);
+        ui_print_system_msg_from_recipient(barejid, "Authorization request, type '/sub allow' to accept or '/sub deny' to reject");
+        if (prefs_get_boolean(PREF_NOTIFY_SUB)) {
+            notify_subscription(barejid);
+        }
+        break;
+    case PRESENCE_SUBSCRIBED:
+        cons_show("Subscription received from %s", barejid);
+        log_info("Subscription received from %s", barejid);
+        ui_print_system_msg_from_recipient(barejid, "Subscribed");
+        break;
+    case PRESENCE_UNSUBSCRIBED:
+        cons_show("%s deleted subscription", barejid);
+        log_info("%s deleted subscription", barejid);
+        ui_print_system_msg_from_recipient(barejid, "Unsubscribed");
+        break;
+    default:
+        /* unknown type */
+        break;
+    }
+}
+
+void
+sv_ev_contact_offline(char *barejid, char *resource, char *status)
+{
+    gboolean updated = roster_contact_offline(barejid, resource, status);
+
+    if (resource && updated) {
+        ui_contact_offline(barejid, resource, status);
+    }
+
+    rosterwin_roster();
+    chat_session_remove(barejid);
+}
+
+void
+sv_ev_contact_online(char *barejid, Resource *resource, GDateTime *last_activity)
+{
+    gboolean updated = roster_update_presence(barejid, resource, last_activity);
+
+    if (updated) {
+        ui_contact_online(barejid, resource, last_activity);
+    }
+
+    rosterwin_roster();
+    chat_session_remove(barejid);
+}
+
+void
+sv_ev_leave_room(const char * const room)
+{
+    muc_leave(room);
+    ui_leave_room(room);
+}
+
+void
+sv_ev_room_destroy(const char * const room)
+{
+    muc_leave(room);
+    ui_room_destroy(room);
+}
+
+void
+sv_ev_room_destroyed(const char * const room, const char * const new_jid, const char * const password,
+    const char * const reason)
+{
+    muc_leave(room);
+    ui_room_destroyed(room, reason, new_jid, password);
+}
+
+void
+sv_ev_room_kicked(const char * const room, const char * const actor, const char * const reason)
+{
+    muc_leave(room);
+    ui_room_kicked(room, actor, reason);
+}
+
+void
+sv_ev_room_banned(const char * const room, const char * const actor, const char * const reason)
+{
+    muc_leave(room);
+    ui_room_banned(room, actor, reason);
+}
+
+void
+sv_ev_room_occupant_offline(const char * const room, const char * const nick,
+    const char * const show, const char * const status)
+{
+    muc_roster_remove(room, nick);
+
+    char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
+    if (g_strcmp0(muc_status_pref, "none") != 0) {
+        ui_room_member_offline(room, nick);
+    }
+    prefs_free_string(muc_status_pref);
+    occupantswin_occupants(room);
+}
+
+void
+sv_ev_room_occupent_kicked(const char * const room, const char * const nick, const char * const actor,
+    const char * const reason)
+{
+    muc_roster_remove(room, nick);
+    ui_room_member_kicked(room, nick, actor, reason);
+    occupantswin_occupants(room);
+}
+
+void
+sv_ev_room_occupent_banned(const char * const room, const char * const nick, const char * const actor,
+    const char * const reason)
+{
+    muc_roster_remove(room, nick);
+    ui_room_member_banned(room, nick, actor, reason);
+    occupantswin_occupants(room);
+}
+
+void
+sv_ev_roster_update(const char * const barejid, const char * const name,
+    GSList *groups, const char * const subscription, gboolean pending_out)
+{
+    roster_update(barejid, name, groups, subscription, pending_out);
+    rosterwin_roster();
+}
+
+void
+sv_ev_xmpp_stanza(const char * const msg)
+{
+    ui_handle_stanza(msg);
+}
+
+void
+sv_ev_muc_self_online(const char * const room, const char * const nick, gboolean config_required,
+    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
+    const char * const jid, const char * const show, const char * const status)
+{
+    muc_roster_add(room, nick, jid, role, affiliation, show, status);
+    char *old_role = muc_role_str(room);
+    char *old_affiliation = muc_affiliation_str(room);
+    muc_set_role(room, role);
+    muc_set_affiliation(room, affiliation);
+
+    // handle self nick change
+    if (muc_nick_change_pending(room)) {
+        muc_nick_change_complete(room, nick);
+        ui_room_nick_change(room, nick);
+
+    // handle roster complete
+    } else if (!muc_roster_complete(room)) {
+        if (muc_autojoin(room)) {
+            ui_room_join(room, FALSE);
+        } else {
+            ui_room_join(room, TRUE);
+        }
+
+        iq_room_info_request(room, FALSE);
+
+        muc_invites_remove(room);
+        muc_roster_set_complete(room);
+
+        // show roster if occupants list disabled by default
+        if (!prefs_get_boolean(PREF_OCCUPANTS)) {
+            GList *occupants = muc_roster(room);
+            ui_room_roster(room, occupants, NULL);
+            g_list_free(occupants);
+        }
+
+        char *subject = muc_subject(room);
+        if (subject) {
+            ui_room_subject(room, NULL, subject);
+        }
+
+        GList *pending_broadcasts = muc_pending_broadcasts(room);
+        if (pending_broadcasts) {
+            GList *curr = pending_broadcasts;
+            while (curr) {
+                ui_room_broadcast(room, curr->data);
+                curr = g_list_next(curr);
+            }
+        }
+
+        // room configuration required
+        if (config_required) {
+            muc_set_requires_config(room, TRUE);
+            ui_room_requires_config(room);
+        }
+
+    // check for change in role/affiliation
+    } else {
+        if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
+            // both changed
+            if ((g_strcmp0(role, old_role) != 0) && (g_strcmp0(affiliation, old_affiliation) != 0)) {
+                ui_room_role_and_affiliation_change(room, role, affiliation, actor, reason);
+
+            // role changed
+            } else if (g_strcmp0(role, old_role) != 0) {
+                ui_room_role_change(room, role, actor, reason);
+
+            // affiliation changed
+            } else if (g_strcmp0(affiliation, old_affiliation) != 0) {
+                ui_room_affiliation_change(room, affiliation, actor, reason);
+            }
+        }
+    }
+
+    occupantswin_occupants(room);
+}
+
+void
+sv_ev_muc_occupant_online(const char * const room, const char * const nick, const char * const jid,
+    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
+    const char * const show, const char * const status)
+{
+    Occupant *occupant = muc_roster_item(room, nick);
+
+    const char *old_role = NULL;
+    const char *old_affiliation = NULL;
+    if (occupant) {
+        old_role = muc_occupant_role_str(occupant);
+        old_affiliation = muc_occupant_affiliation_str(occupant);
+    }
+
+    gboolean updated = muc_roster_add(room, nick, jid, role, affiliation, show, status);
+
+    // not yet finished joining room
+    if (!muc_roster_complete(room)) {
+        return;
+    }
+
+    // handle nickname change
+    char *old_nick = muc_roster_nick_change_complete(room, nick);
+    if (old_nick) {
+        ui_room_member_nick_change(room, old_nick, nick);
+        free(old_nick);
+        occupantswin_occupants(room);
+        return;
+    }
+
+    // joined room
+    if (!occupant) {
+        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
+        if (g_strcmp0(muc_status_pref, "none") != 0) {
+            ui_room_member_online(room, nick, role, affiliation, show, status);
+        }
+        prefs_free_string(muc_status_pref);
+        occupantswin_occupants(room);
+        return;
+    }
+
+    // presence updated
+    if (updated) {
+        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
+        if (g_strcmp0(muc_status_pref, "all") == 0) {
+            ui_room_member_presence(room, nick, show, status);
+        }
+        prefs_free_string(muc_status_pref);
+        occupantswin_occupants(room);
+
+    // presence unchanged, check for role/affiliation change
+    } else {
+        if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
+            // both changed
+            if ((g_strcmp0(role, old_role) != 0) && (g_strcmp0(affiliation, old_affiliation) != 0)) {
+                ui_room_occupant_role_and_affiliation_change(room, nick, role, affiliation, actor, reason);
+
+            // role changed
+            } else if (g_strcmp0(role, old_role) != 0) {
+                ui_room_occupant_role_change(room, nick, role, actor, reason);
+
+            // affiliation changed
+            } else if (g_strcmp0(affiliation, old_affiliation) != 0) {
+                ui_room_occupant_affiliation_change(room, nick, affiliation, actor, reason);
+            }
+        }
+        occupantswin_occupants(room);
+    }
+}
diff --git a/src/event/server_events.h b/src/event/server_events.h
new file mode 100644
index 00000000..46d485da
--- /dev/null
+++ b/src/event/server_events.h
@@ -0,0 +1,91 @@
+/*
+ * server_events.h
+ *
+ * Copyright (C) 2012 - 2015 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 SERVER_EVENTS_H
+#define SERVER_EVENTS_H
+
+#include "xmpp/xmpp.h"
+
+void sv_ev_login_account_success(char *account_name);
+void sv_ev_lost_connection(void);
+void sv_ev_failed_login(void);
+void sv_ev_room_invite(jabber_invite_t invite_type,
+    const char * const invitor, const char * const room,
+    const char * const reason, const char * const password);
+void sv_ev_room_broadcast(const char *const room_jid,
+    const char * const message);
+void sv_ev_room_subject(const char * const room, const char * const nick, const char * const subject);
+void sv_ev_room_history(const char * const room_jid, const char * const nick,
+    GTimeVal tv_stamp, const char * const message);
+void sv_ev_room_message(const char * const room_jid, const char * const nick,
+    const char * const message);
+void sv_ev_incoming_message(char *barejid, char *resource, char *message);
+void sv_ev_incoming_private_message(const char * const fulljid, char *message);
+void sv_ev_delayed_message(char *fulljid, char *message, GTimeVal tv_stamp);
+void sv_ev_delayed_private_message(const char * const fulljid, char *message, GTimeVal tv_stamp);
+void sv_ev_typing(char *barejid, char *resource);
+void sv_ev_paused(char *barejid, char *resource);
+void sv_ev_inactive(char *barejid, char *resource);
+void sv_ev_activity(char *barejid, char *resource, gboolean send_states);
+void sv_ev_gone(const char * const barejid, const char * const resource);
+void sv_ev_subscription(const char *from, jabber_subscr_t type);
+void sv_ev_message_receipt(char *barejid, char *id);
+void sv_ev_contact_offline(char *contact, char *resource, char *status);
+void sv_ev_contact_online(char *contact, Resource *resource,
+    GDateTime *last_activity);
+void sv_ev_leave_room(const char * const room);
+void sv_ev_room_destroy(const char * const room);
+void sv_ev_room_occupant_offline(const char * const room, const char * const nick,
+    const char * const show, const char * const status);
+void sv_ev_room_destroyed(const char * const room, const char * const new_jid, const char * const password,
+    const char * const reason);
+void sv_ev_room_kicked(const char * const room, const char * const actor, const char * const reason);
+void sv_ev_room_occupent_kicked(const char * const room, const char * const nick, const char * const actor,
+    const char * const reason);
+void sv_ev_room_banned(const char * const room, const char * const actor, const char * const reason);
+void sv_ev_room_occupent_banned(const char * const room, const char * const nick, const char * const actor,
+    const char * const reason);
+void sv_ev_carbon(char *barejid, char *message);
+void sv_ev_xmpp_stanza(const char * const msg);
+void sv_ev_muc_self_online(const char * const room, const char * const nick, gboolean config_required,
+    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
+    const char * const jid, const char * const show, const char * const status);
+void sv_ev_muc_occupant_online(const char * const room, const char * const nick, const char * const jid,
+    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
+    const char * const show_str, const char * const status_str);
+void sv_ev_roster_update(const char * const barejid, const char * const name,
+    GSList *groups, const char * const subscription, gboolean pending_out);
+void sv_ev_roster_received(void);
+
+#endif
diff --git a/src/event/ui_events.c b/src/event/ui_events.c
new file mode 100644
index 00000000..ff1d7273
--- /dev/null
+++ b/src/event/ui_events.c
@@ -0,0 +1,56 @@
+/*
+ * ui_events.c
+ *
+ * Copyright (C) 2012 - 2015 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 "ui/ui.h"
+#include "ui/windows.h"
+
+void
+ui_ev_focus_win(ProfWin *win)
+{
+    if (!wins_is_current(win)) {
+        ui_switch_win(win);
+    }
+}
+
+ProfChatWin*
+ui_ev_new_chat_win(const char * const barejid)
+{
+    return ui_new_chat_win(barejid);
+}
+
+ProfPrivateWin*
+ui_ev_new_private_win(const char * const fulljid)
+{
+    return ui_new_private_win(fulljid);
+}
\ No newline at end of file
diff --git a/src/tools/history.h b/src/event/ui_events.h
index 7b334718..3f7fed02 100644
--- a/src/tools/history.h
+++ b/src/event/ui_events.h
@@ -1,7 +1,7 @@
 /*
- * history.h
+ * ui_events.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -32,14 +32,11 @@
  *
  */
 
-#ifndef HISTORY_H
-#define HISTORY_H
+#ifndef UI_EVENTS_H
+#define UI_EVENTS_H
 
-typedef struct history_t  *History;
+void ui_ev_focus_win(ProfWin *win);
+ProfChatWin* ui_ev_new_chat_win(const char * const barejid);
+ProfPrivateWin* ui_ev_new_private_win(const char * const fulljid);
 
-History history_new(unsigned int size);
-char * history_previous(History history, char *item);
-char * history_next(History history, char *item);
-void history_append(History history, char *item);
-
-#endif
+#endif
\ No newline at end of file
diff --git a/src/jid.c b/src/jid.c
index 690f36ca..baeeb279 100644
--- a/src/jid.c
+++ b/src/jid.c
@@ -1,7 +1,7 @@
 /*
  * jid.c
  *
- * Copyright (C) 2012 -2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -80,19 +80,20 @@ jid_create(const gchar * const str)
     gchar *domain_start = trimmed;
 
 
-    if (atp != NULL) {
+    if (atp) {
         result->localpart = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, atp));
         domain_start = atp + 1;
     }
 
-    if (slashp != NULL) {
+    if (slashp) {
         result->resourcepart = g_strdup(slashp + 1);
         result->domainpart = g_utf8_substring(domain_start, 0, g_utf8_pointer_to_offset(domain_start, slashp));
-        result->barejid = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, slashp));
+        char *barejidraw = g_utf8_substring(trimmed, 0, g_utf8_pointer_to_offset(trimmed, slashp));
+        result->barejid = g_utf8_strdown(barejidraw, -1);
         result->fulljid = g_strdup(trimmed);
     } else {
         result->domainpart = g_strdup(domain_start);
-        result->barejid = g_strdup(trimmed);
+        result->barejid = g_utf8_strdown(trimmed, -1);
     }
 
     if (result->domainpart == NULL) {
@@ -119,7 +120,7 @@ jid_create_from_bare_and_resource(const char * const room, const char * const ni
 void
 jid_destroy(Jid *jid)
 {
-    if (jid != NULL) {
+    if (jid) {
         g_free(jid->str);
         g_free(jid->localpart);
         g_free(jid->domainpart);
@@ -144,7 +145,9 @@ jid_is_valid_room_form(Jid *jid)
 char *
 create_fulljid(const char * const barejid, const char * const resource)
 {
-    GString *full_jid = g_string_new(barejid);
+    gchar *barejidlower = g_utf8_strdown(barejid, -1);
+    GString *full_jid = g_string_new(barejidlower);
+    g_free(barejidlower);
     g_string_append(full_jid, "/");
     g_string_append(full_jid, resource);
 
@@ -166,8 +169,8 @@ get_nick_from_full_jid(const char * const full_room_jid)
     char **tokens = g_strsplit(full_room_jid, "/", 0);
     char *nick_part = NULL;
 
-    if (tokens != NULL) {
-        if (tokens[0] != NULL && tokens[1] != NULL) {
+    if (tokens) {
+        if (tokens[0] && tokens[1]) {
             nick_part = strdup(tokens[1]);
         }
 
diff --git a/src/jid.h b/src/jid.h
index 64c147f9..6d530c5a 100644
--- a/src/jid.h
+++ b/src/jid.h
@@ -1,7 +1,7 @@
 /*
  * jid.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/log.c b/src/log.c
index c525c3d9..a7727e8b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,7 +1,7 @@
 /*
  * log.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -46,6 +46,7 @@
 
 #include "common.h"
 #include "config/preferences.h"
+#include "xmpp/xmpp.h"
 
 #define PROF "prof"
 
@@ -66,7 +67,7 @@ struct dated_chat_log {
 };
 
 static gboolean _log_roll_needed(struct dated_chat_log *dated_log);
-static struct dated_chat_log * _create_log(char *other, const  char * const login);
+static struct dated_chat_log * _create_log(const char * const other, const  char * const login);
 static struct dated_chat_log * _create_groupchat_log(char *room, const char * const login);
 static void _free_chat_log(struct dated_chat_log *dated_log);
 static gboolean _key_equals(void *key1, void *key2);
@@ -78,6 +79,8 @@ static gchar * _get_chatlog_dir(void);
 static gchar * _get_main_log_file(void);
 static void _rotate_log_file(void);
 static char* _log_string_from_level(log_level_t level);
+static void _chat_log_chat(const char * const login, const char * const other,
+    const gchar * const msg, chat_log_direction_t direction, GTimeVal *tv_stamp);
 
 void
 log_debug(const char * const msg, ...)
@@ -163,7 +166,7 @@ log_close(void)
 {
     g_string_free(mainlogfile, TRUE);
     g_time_zone_unref(tz);
-    if (logp != NULL) {
+    if (logp) {
         fclose(logp);
     }
 }
@@ -171,7 +174,7 @@ log_close(void)
 void
 log_msg(log_level_t level, const char * const area, const char * const msg)
 {
-    if (level >= level_filter && logp != NULL) {
+    if (level >= level_filter && logp) {
         dt = g_date_time_new_now(tz);
 
         char *level_str = _log_string_from_level(level);
@@ -236,7 +239,7 @@ chat_log_init(void)
 {
     session_started = g_date_time_new_now_local();
     log_info("Initialising chat logs");
-    logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, g_free,
+    logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, free,
         (GDestroyNotify)_free_chat_log);
 }
 
@@ -244,13 +247,80 @@ void
 groupchat_log_init(void)
 {
     log_info("Initialising groupchat logs");
-    groupchat_logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, g_free,
+    groupchat_logs = g_hash_table_new_full(g_str_hash, (GEqualFunc) _key_equals, free,
         (GDestroyNotify)_free_chat_log);
 }
 
 void
-chat_log_chat(const gchar * const login, gchar *other,
-    const gchar * const msg, chat_log_direction_t direction, GTimeVal *tv_stamp)
+chat_log_msg_out(const char * const barejid, const char * const msg)
+{
+    if (prefs_get_boolean(PREF_CHLOG)) {
+        const char *jid = jabber_get_fulljid();
+        Jid *jidp = jid_create(jid);
+        _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL);
+        jid_destroy(jidp);
+    }
+}
+
+void
+chat_log_otr_msg_out(const char * const barejid, const char * const msg)
+{
+    if (prefs_get_boolean(PREF_CHLOG)) {
+        const char *jid = jabber_get_fulljid();
+        Jid *jidp = jid_create(jid);
+        char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
+        if (strcmp(pref_otr_log, "on") == 0) {
+            _chat_log_chat(jidp->barejid, barejid, msg, PROF_OUT_LOG, NULL);
+        } else if (strcmp(pref_otr_log, "redact") == 0) {
+            _chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_OUT_LOG, NULL);
+        }
+        prefs_free_string(pref_otr_log);
+        jid_destroy(jidp);
+    }
+}
+
+void
+chat_log_otr_msg_in(const char * const barejid, const char * const msg, gboolean was_decrypted)
+{
+    if (prefs_get_boolean(PREF_CHLOG)) {
+        const char *jid = jabber_get_fulljid();
+        Jid *jidp = jid_create(jid);
+        char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
+        if (!was_decrypted || (strcmp(pref_otr_log, "on") == 0)) {
+            _chat_log_chat(jidp->barejid, barejid, msg, PROF_IN_LOG, NULL);
+        } else if (strcmp(pref_otr_log, "redact") == 0) {
+            _chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_IN_LOG, NULL);
+        }
+        prefs_free_string(pref_otr_log);
+        jid_destroy(jidp);
+    }
+}
+
+void
+chat_log_msg_in(const char * const barejid, const char * const msg)
+{
+    if (prefs_get_boolean(PREF_CHLOG)) {
+        const char *jid = jabber_get_fulljid();
+        Jid *jidp = jid_create(jid);
+        _chat_log_chat(jidp->barejid, barejid, msg, PROF_IN_LOG, NULL);
+        jid_destroy(jidp);
+    }
+}
+
+void
+chat_log_msg_in_delayed(const char * const barejid, const char * msg, GTimeVal *tv_stamp)
+{
+    if (prefs_get_boolean(PREF_CHLOG)) {
+        const char *jid = jabber_get_fulljid();
+        Jid *jidp = jid_create(jid);
+        _chat_log_chat(jidp->barejid, barejid, msg, PROF_IN_LOG, tv_stamp);
+        jid_destroy(jidp);
+    }
+}
+
+static void
+_chat_log_chat(const char * const login, const char * const other,
+    const char * const msg, chat_log_direction_t direction, GTimeVal *tv_stamp)
 {
     struct dated_chat_log *dated_log = g_hash_table_lookup(logs, other);
 
@@ -277,7 +347,7 @@ chat_log_chat(const gchar * const login, gchar *other,
 
     FILE *logp = fopen(dated_log->filename, "a");
     g_chmod(dated_log->filename, S_IRUSR | S_IWUSR);
-    if (logp != NULL) {
+    if (logp) {
         if (direction == PROF_IN_LOG) {
             if (strncmp(msg, "/me ", 4) == 0) {
                 fprintf(logp, "%s - *%s %s\n", date_fmt, other, msg + 4);
@@ -326,7 +396,7 @@ groupchat_log_chat(const gchar * const login, const gchar * const room,
 
     FILE *logp = fopen(dated_log->filename, "a");
     g_chmod(dated_log->filename, S_IRUSR | S_IWUSR);
-    if (logp != NULL) {
+    if (logp) {
         if (strncmp(msg, "/me ", 4) == 0) {
             fprintf(logp, "%s - *%s %s\n", date_fmt, nick, msg + 4);
         } else {
@@ -363,7 +433,7 @@ chat_log_get_previous(const gchar * const login, const gchar * const recipient)
         char *filename = _get_log_filename(recipient, login, log_date, FALSE);
 
         FILE *logp = fopen(filename, "r");
-        if (logp != NULL) {
+        if (logp) {
             GString *header = g_string_new("");
             g_string_append_printf(header, "%d/%d/%d:",
                 g_date_time_get_day_of_month(log_date),
@@ -396,13 +466,13 @@ chat_log_get_previous(const gchar * const login, const gchar * const recipient)
 void
 chat_log_close(void)
 {
-    g_hash_table_remove_all(logs);
-    g_hash_table_remove_all(groupchat_logs);
+    g_hash_table_destroy(logs);
+    g_hash_table_destroy(groupchat_logs);
     g_date_time_unref(session_started);
 }
 
 static struct dated_chat_log *
-_create_log(char *other, const char * const login)
+_create_log(const char * const other, const char * const login)
 {
     GDateTime *now = g_date_time_new_now_local();
     char *filename = _get_log_filename(other, login, now, TRUE);
@@ -448,12 +518,12 @@ _log_roll_needed(struct dated_chat_log *dated_log)
 static void
 _free_chat_log(struct dated_chat_log *dated_log)
 {
-    if (dated_log != NULL) {
-        if (dated_log->filename != NULL) {
+    if (dated_log) {
+        if (dated_log->filename) {
             g_free(dated_log->filename);
             dated_log->filename = NULL;
         }
-        if (dated_log->date != NULL) {
+        if (dated_log->date) {
             g_date_time_unref(dated_log->date);
             dated_log->date = NULL;
         }
diff --git a/src/log.h b/src/log.h
index 8ffc6f22..0689e2e6 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,7 +1,7 @@
 /*
  * log.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -64,8 +64,14 @@ void log_msg(log_level_t level, const char * const area,
 log_level_t log_level_from_string(char *log_level);
 
 void chat_log_init(void);
-void chat_log_chat(const gchar * const login, gchar *other,
-    const gchar * const msg, chat_log_direction_t direction, GTimeVal *tv_stamp);
+
+void chat_log_msg_out(const char * const barejid, const char * const msg);
+void chat_log_otr_msg_out(const char * const barejid, const char * const msg);
+
+void chat_log_msg_in(const char * const barejid, const char * const msg);
+void chat_log_msg_in_delayed(const char * const barejid, const char * msg, GTimeVal *tv_stamp);
+void chat_log_otr_msg_in(const char * const barejid, const char * const msg, gboolean was_decrypted);
+
 void chat_log_close(void);
 GSList * chat_log_get_previous(const gchar * const login,
     const gchar * const recipient);
diff --git a/src/main.c b/src/main.c
index f3b6a17f..3bb7eeb6 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,7 +1,7 @@
 /*
  * main.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -40,13 +40,7 @@
 #endif
 
 #include "profanity.h"
-
-#ifdef HAVE_LIBOTR
-#include "otr/otr.h"
-#endif
-#include "xmpp/xmpp.h"
-
-#include "ui/ui.h"
+#include "command/command.h"
 
 static gboolean disable_tls = FALSE;
 static gboolean version = FALSE;
@@ -56,6 +50,11 @@ static char *account_name = NULL;
 int
 main(int argc, char **argv)
 {
+    if (argc == 2 && g_strcmp0(argv[1], "docgen") == 0 && g_strcmp0(PACKAGE_STATUS, "development") == 0) {
+        command_docgen();
+        return 0;
+    }
+
     static GOptionEntry entries[] =
     {
         { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Show version information", NULL },
@@ -90,7 +89,7 @@ main(int argc, char **argv)
             g_print("Profanity, version %s\n", PACKAGE_VERSION);
         }
 
-        g_print("Copyright (C) 2012 - 2014 James Booth <%s>.\n", PACKAGE_BUGREPORT);
+        g_print("Copyright (C) 2012 - 2015 James Booth <%s>.\n", PACKAGE_BUGREPORT);
         g_print("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n");
         g_print("\n");
         g_print("This is free software; you are free to change and redistribute it.\n");
diff --git a/src/muc.c b/src/muc.c
index f50f3879..d283b55e 100644
--- a/src/muc.c
+++ b/src/muc.c
@@ -1,7 +1,7 @@
 /*
  * muc.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -62,9 +62,11 @@ typedef struct _muc_room_t {
     Autocomplete jid_ac;
     GHashTable *nick_changes;
     gboolean roster_received;
+    muc_member_type_t member_type;
 } ChatRoom;
 
 GHashTable *rooms = NULL;
+GHashTable *invite_passwords = NULL;
 Autocomplete invite_ac;
 
 static void _free_room(ChatRoom *room);
@@ -82,6 +84,7 @@ muc_init(void)
 {
     invite_ac = autocomplete_new();
     rooms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_free_room);
+    invite_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 }
 
 void
@@ -89,19 +92,25 @@ muc_close(void)
 {
     autocomplete_free(invite_ac);
     g_hash_table_destroy(rooms);
+    g_hash_table_destroy(invite_passwords);
     rooms = NULL;
+    invite_passwords = NULL;
 }
 
 void
-muc_invites_add(const char * const room)
+muc_invites_add(const char * const room, const char * const password)
 {
     autocomplete_add(invite_ac, room);
+    if (password) {
+        g_hash_table_replace(invite_passwords, strdup(room), strdup(password));
+    }
 }
 
 void
 muc_invites_remove(const char * const room)
 {
     autocomplete_remove(invite_ac, room);
+    g_hash_table_remove(invite_passwords, room);
 }
 
 gint
@@ -116,6 +125,12 @@ muc_invites(void)
     return autocomplete_create_list(invite_ac);
 }
 
+char *
+muc_invite_password(const char * const room)
+{
+    return g_hash_table_lookup(invite_passwords, room);
+}
+
 gboolean
 muc_invites_contain(const char * const room)
 {
@@ -150,6 +165,7 @@ void
 muc_invites_clear(void)
 {
     autocomplete_clear(invite_ac);
+    g_hash_table_remove_all(invite_passwords);
 }
 
 void
@@ -177,6 +193,7 @@ muc_join(const char * const room, const char * const nick,
     new_room->roster_received = FALSE;
     new_room->pending_nick_change = FALSE;
     new_room->autojoin = autojoin;
+    new_room->member_type = MUC_MEMBER_TYPE_UNKNOWN;
 
     g_hash_table_insert(rooms, strdup(room), new_room);
 }
@@ -208,6 +225,19 @@ muc_set_requires_config(const char * const room, gboolean val)
     }
 }
 
+void
+muc_set_features(const char * const room, GSList *features)
+{
+    ChatRoom *chat_room = g_hash_table_lookup(rooms, room);
+    if (chat_room && features) {
+        if (g_slist_find_custom(features, "muc_membersonly", (GCompareFunc)g_strcmp0)) {
+            chat_room->member_type = MUC_MEMBER_TYPE_MEMBERS_ONLY;
+        } else {
+            chat_room->member_type = MUC_MEMBER_TYPE_PUBLIC;
+        }
+    }
+}
+
 /*
  * Returns TRUE if the user is currently in the room
  */
@@ -315,7 +345,7 @@ muc_nick_change_pending(const char * const room)
 }
 
 /*
- * Change the current nuck name for the room, call once
+ * Change the current nick name for the room, call once
  * the service has responded
  */
 void
@@ -763,6 +793,18 @@ muc_set_affiliation(const char * const room, const char * const affiliation)
     }
 }
 
+muc_member_type_t
+muc_member_type(const char * const room)
+{
+    ChatRoom *chat_room = g_hash_table_lookup(rooms, room);
+    if (chat_room) {
+        return chat_room->member_type;
+    } else {
+        return MUC_MEMBER_TYPE_UNKNOWN;
+    }
+}
+
+
 static void
 _free_room(ChatRoom *room)
 {
@@ -790,16 +832,10 @@ _free_room(ChatRoom *room)
 static
 gint _compare_occupants(Occupant *a, Occupant *b)
 {
-    const char * utf8_str_a = a->nick;
-    const char * utf8_str_b = b->nick;
-
-    gchar *key_a = g_utf8_collate_key(utf8_str_a, -1);
-    gchar *key_b = g_utf8_collate_key(utf8_str_b, -1);
-
-    gint result = g_strcmp0(key_a, key_b);
+    const char * utf8_str_a = a->nick_collate_key;
+    const char * utf8_str_b = b->nick_collate_key;
 
-    g_free(key_a);
-    g_free(key_b);
+    gint result = g_strcmp0(utf8_str_a, utf8_str_b);
 
     return result;
 }
@@ -905,8 +941,10 @@ _muc_occupant_new(const char *const nick, const char * const jid, muc_role_t rol
 
     if (nick) {
         occupant->nick = strdup(nick);
+        occupant->nick_collate_key = g_utf8_collate_key(occupant->nick, -1);
     } else {
         occupant->nick = NULL;
+        occupant->nick_collate_key = NULL;
     }
 
     if (jid) {
@@ -934,6 +972,7 @@ _occupant_free(Occupant *occupant)
 {
     if (occupant) {
         free(occupant->nick);
+        free(occupant->nick_collate_key);
         free(occupant->jid);
         free(occupant->status);
         free(occupant);
diff --git a/src/muc.h b/src/muc.h
index 01f8b44b..ad96f3d9 100644
--- a/src/muc.h
+++ b/src/muc.h
@@ -1,7 +1,7 @@
 /*
  * muc.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -56,8 +56,15 @@ typedef enum {
     MUC_AFFILIATION_OWNER
 } muc_affiliation_t;
 
+typedef enum {
+    MUC_MEMBER_TYPE_UNKNOWN,
+    MUC_MEMBER_TYPE_PUBLIC,
+    MUC_MEMBER_TYPE_MEMBERS_ONLY
+} muc_member_type_t;
+
 typedef struct _muc_occupant_t {
     char *nick;
+    gchar *nick_collate_key;
     char *jid;
     muc_role_t role;
     muc_affiliation_t affiliation;
@@ -76,6 +83,8 @@ gboolean muc_autojoin(const char * const room);
 
 GList* muc_rooms(void);
 
+void muc_set_features(const char * const room, GSList *features);
+
 char* muc_nick(const char * const room);
 char* muc_password(const char * const room);
 
@@ -108,7 +117,7 @@ GSList * muc_occupants_by_affiliation(const char * const room, muc_affiliation_t
 void muc_occupant_nick_change_start(const char * const room, const char * const new_nick, const char * const old_nick);
 char* muc_roster_nick_change_complete(const char * const room, const char * const nick);
 
-void muc_invites_add(const char * const room);
+void muc_invites_add(const char * const room, const char * const password);
 void muc_invites_remove(const char * const room);
 gint muc_invites_count(void);
 GSList* muc_invites(void);
@@ -116,6 +125,7 @@ gboolean muc_invites_contain(const char * const room);
 void muc_invites_reset_ac(void);
 char* muc_invites_find(const char * const search_str);
 void muc_invites_clear(void);
+char* muc_invite_password(const char * const room);
 
 void muc_set_subject(const char * const room, const char * const subject);
 char* muc_subject(const char * const room);
@@ -134,4 +144,6 @@ void muc_set_affiliation(const char * const room, const char * const affiliation
 char *muc_role_str(const char * const room);
 char *muc_affiliation_str(const char * const room);
 
+muc_member_type_t muc_member_type(const char * const room);
+
 #endif
diff --git a/src/otr/otr.c b/src/otr/otr.c
index bd1c2ce3..e568af56 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -1,7 +1,7 @@
 /*
  * otr.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -110,7 +110,7 @@ static void
 cb_inject_message(void *opdata, const char *accountname,
     const char *protocol, const char *recipient, const char *message)
 {
-    message_send_chat(recipient, message);
+    message_send_chat_encrypted(recipient, message);
 }
 
 static void
@@ -179,7 +179,7 @@ otr_init(void)
 void
 otr_shutdown(void)
 {
-    if (jid != NULL) {
+    if (jid) {
         free(jid);
     }
 }
@@ -193,7 +193,7 @@ otr_poll(void)
 void
 otr_on_connect(ProfAccount *account)
 {
-    if (jid != NULL) {
+    if (jid) {
         free(jid);
     }
     jid = strdup(account->jid);
@@ -270,6 +270,89 @@ otr_on_connect(ProfAccount *account)
 }
 
 void
+otr_on_message_recv(const char * const barejid, const char * const resource, const char * const message)
+{
+    gboolean was_decrypted = FALSE;
+    char *decrypted;
+
+    prof_otrpolicy_t policy = otr_get_policy(barejid);
+    char *whitespace_base = strstr(message, OTRL_MESSAGE_TAG_BASE);
+
+    //check for OTR whitespace (opportunistic or always)
+    if (policy == PROF_OTRPOLICY_OPPORTUNISTIC || policy == PROF_OTRPOLICY_ALWAYS) {
+        if (whitespace_base) {
+            if (strstr(message, OTRL_MESSAGE_TAG_V2) || strstr(message, OTRL_MESSAGE_TAG_V1)) {
+                // Remove whitespace pattern for proper display in UI
+                // Handle both BASE+TAGV1/2(16+8) and BASE+TAGV1+TAGV2(16+8+8)
+                int tag_length = 24;
+                if (strstr(message, OTRL_MESSAGE_TAG_V2) && strstr(message, OTRL_MESSAGE_TAG_V1)) {
+                    tag_length = 32;
+                }
+                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...");
+                message_send_chat_encrypted(barejid, otr_query_message);
+            }
+        }
+    }
+    decrypted = otr_decrypt_message(barejid, message, &was_decrypted);
+
+    // internal OTR message
+    if (decrypted == NULL) {
+        return;
+    }
+
+    if (policy == PROF_OTRPOLICY_ALWAYS && !was_decrypted && !whitespace_base) {
+        char *otr_query_message = otr_start_query();
+        cons_show("Attempting to start OTR session...");
+        message_send_chat_encrypted(barejid, otr_query_message);
+    }
+
+    ui_incoming_msg(barejid, resource, decrypted, NULL);
+    chat_log_otr_msg_in(barejid, decrypted, was_decrypted);
+    otr_free_message(decrypted);
+}
+
+void
+otr_on_message_send(ProfChatWin *chatwin, const char * const message)
+{
+    char *id = NULL;
+
+    prof_otrpolicy_t policy = otr_get_policy(chatwin->barejid);
+
+    if (otr_is_secure(chatwin->barejid)) {
+        char *encrypted = otr_encrypt_message(chatwin->barejid, message);
+        if (encrypted) {
+            id = message_send_chat_encrypted(chatwin->barejid, encrypted);
+            chat_log_otr_msg_out(chatwin->barejid, message);
+            ui_outgoing_chat_msg(chatwin, message, id);
+            otr_free_message(encrypted);
+        } else {
+            ui_win_error_line((ProfWin*)chatwin, "Failed to encrypt and send message.");
+            return;
+        }
+
+    } else if (policy == PROF_OTRPOLICY_ALWAYS) {
+        ui_win_error_line((ProfWin*)chatwin, "Failed to send message. OTR policy set to: always");
+        return;
+
+    } else if (policy == PROF_OTRPOLICY_OPPORTUNISTIC) {
+        char *otr_tagged_msg = otr_tag_message(message);
+        id = message_send_chat_encrypted(chatwin->barejid, otr_tagged_msg);
+        ui_outgoing_chat_msg(chatwin, message, id);
+        chat_log_msg_out(chatwin->barejid, message);
+        free(otr_tagged_msg);
+
+    } else {
+        id = message_send_chat(chatwin->barejid, message);
+        ui_outgoing_chat_msg(chatwin, message, id);
+        chat_log_msg_out(chatwin->barejid, message);
+    }
+
+    free(id);
+}
+
+void
 otr_keygen(ProfAccount *account)
 {
     if (data_loaded) {
@@ -277,7 +360,7 @@ otr_keygen(ProfAccount *account)
         return;
     }
 
-    if (jid != NULL) {
+    if (jid) {
         free(jid);
     }
     jid = strdup(account->jid);
@@ -365,6 +448,18 @@ otr_key_loaded(void)
     return data_loaded;
 }
 
+char *
+otr_tag_message(const char * const msg)
+{
+    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);
+    char *result = otr_message->str;
+    g_string_free(otr_message, FALSE);
+
+    return result;
+}
+
 gboolean
 otr_is_secure(const char * const recipient)
 {
@@ -421,7 +516,7 @@ otr_trust(const char * const recipient)
     }
 
     if (context->active_fingerprint) {
-        if (context->active_fingerprint->trust != NULL) {
+        if (context->active_fingerprint->trust) {
             free(context->active_fingerprint->trust);
         }
         context->active_fingerprint->trust = strdup("trusted");
@@ -445,7 +540,7 @@ otr_untrust(const char * const recipient)
     }
 
     if (context->active_fingerprint) {
-        if (context->active_fingerprint->trust != NULL) {
+        if (context->active_fingerprint->trust) {
             free(context->active_fingerprint->trust);
         }
         context->active_fingerprint->trust = NULL;
@@ -534,7 +629,7 @@ otr_get_their_fingerprint(const char * const recipient)
 {
     ConnContext *context = otrlib_context_find(user_state, recipient, jid);
 
-    if (context != NULL) {
+    if (context) {
         Fingerprint *fingerprint = context->active_fingerprint;
         char readable[45];
         otrl_privkey_hash_to_human(readable, fingerprint->fingerprint);
@@ -563,7 +658,7 @@ otr_get_policy(const char * const recipient)
     }
 
     // check default account setting
-    if (account->otr_policy != NULL) {
+    if (account->otr_policy) {
         prof_otrpolicy_t result;
         if (g_strcmp0(account->otr_policy, "manual") == 0) {
             result = PROF_OTRPOLICY_MANUAL;
@@ -625,7 +720,7 @@ otr_decrypt_message(const char * const from, const char * const message, gboolea
         OtrlTLV *tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
         if (tlv) {
 
-            if (context != NULL) {
+            if (context) {
                 otrl_context_force_plaintext(context);
                 ui_gone_insecure(from);
             }
@@ -637,7 +732,7 @@ otr_decrypt_message(const char * const from, const char * const message, gboolea
         return NULL;
 
     // message was decrypted, return to user
-    } else if (decrypted != NULL) {
+    } else if (decrypted) {
         *was_decrypted = TRUE;
         return decrypted;
 
diff --git a/src/otr/otr.h b/src/otr/otr.h
index 155e287b..e020c0c8 100644
--- a/src/otr/otr.h
+++ b/src/otr/otr.h
@@ -1,7 +1,7 @@
 /*
  * otr.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -39,6 +39,7 @@
 #include <libotr/message.h>
 
 #include "config/accounts.h"
+#include "ui/window.h"
 
 typedef enum {
     PROF_OTRPOLICY_MANUAL,
@@ -56,8 +57,14 @@ char* otr_libotr_version(void);
 char* otr_start_query(void);
 void otr_poll(void);
 void otr_on_connect(ProfAccount *account);
+
+void otr_on_message_recv(const char * const barejid, const char * const resource, const char * const message);
+void otr_on_message_send(ProfChatWin *chatwin, const char * const message);
+
 void otr_keygen(ProfAccount *account);
 
+char* otr_tag_message(const char * const msg);
+
 gboolean otr_key_loaded(void);
 gboolean otr_is_secure(const char * const recipient);
 
diff --git a/src/otr/otrlib.h b/src/otr/otrlib.h
index da1c8547..0310c9e6 100644
--- a/src/otr/otrlib.h
+++ b/src/otr/otrlib.h
@@ -1,7 +1,7 @@
 /*
  * otrlib.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/otr/otrlibv3.c b/src/otr/otrlibv3.c
index 77b00660..0b81796c 100644
--- a/src/otr/otrlibv3.c
+++ b/src/otr/otrlibv3.c
@@ -1,7 +1,7 @@
 /*
  * otrlibv3.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -87,7 +87,7 @@ otrlib_end_session(OtrlUserState user_state, const char * const recipient, char
     ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp",
         0, NULL, NULL, NULL);
 
-    if (context != NULL) {
+    if (context) {
         otrl_message_disconnect(user_state, ops, NULL, jid, "xmpp", recipient);
     }
 }
@@ -171,7 +171,7 @@ otrlib_handle_tlvs(OtrlUserState user_state, OtrlMessageAppOps *ops, ConnContext
         } else {
             context->smstate->nextExpected = OTRL_SMP_EXPECT1;
             if (context->smstate->received_question == 0) {
-                if ((context->active_fingerprint->trust != NULL) && (context->active_fingerprint->trust[0] != '\0')) {
+                if (context->active_fingerprint->trust && (context->active_fingerprint->trust[0] != '\0')) {
                     ui_smp_successful(context->username);
                     ui_trust(context->username);
                 } else {
@@ -193,7 +193,7 @@ otrlib_handle_tlvs(OtrlUserState user_state, OtrlMessageAppOps *ops, ConnContext
             otrl_message_abort_smp(user_state, ops, NULL, context);
         } else {
             context->smstate->nextExpected = OTRL_SMP_EXPECT1;
-            if ((context->active_fingerprint->trust != NULL) && (context->active_fingerprint->trust[0] != '\0')) {
+            if (context->active_fingerprint->trust && (context->active_fingerprint->trust[0] != '\0')) {
                 ui_smp_successful(context->username);
                 ui_trust(context->username);
             } else {
diff --git a/src/otr/otrlibv4.c b/src/otr/otrlibv4.c
index 62379d0f..fc1f5285 100644
--- a/src/otr/otrlibv4.c
+++ b/src/otr/otrlibv4.c
@@ -1,7 +1,7 @@
 /*
  * otrlibv4.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -113,12 +113,57 @@ cb_handle_msg_event(void *opdata, OtrlMessageEvent msg_event,
     ConnContext *context, const char *message,
     gcry_error_t err)
 {
-    if (err != 0) {
-        if (message != NULL) {
-            cons_show_error("%s", message);
-        } else {
-            cons_show_error("OTR error event with no message.");
-        }
+    GString *err_msg;
+    switch (msg_event)
+    {
+        case OTRL_MSGEVENT_ENCRYPTION_REQUIRED:
+            ui_handle_otr_error(context->username, "OTR: Policy requires encryption, but attempting to send an unencrypted message.");
+            break;
+        case OTRL_MSGEVENT_ENCRYPTION_ERROR:
+             ui_handle_otr_error(context->username, "OTR: Error occured while encrypting a message, message not sent.");
+            break;
+        case OTRL_MSGEVENT_CONNECTION_ENDED:
+            ui_handle_otr_error(context->username, "OTR: Message not sent because contact has ended the private conversation.");
+            break;
+        case OTRL_MSGEVENT_SETUP_ERROR:
+            ui_handle_otr_error(context->username, "OTR: A private conversation could not be set up.");
+            break;
+        case OTRL_MSGEVENT_MSG_REFLECTED:
+            ui_handle_otr_error(context->username, "OTR: Received our own OTR message.");
+            break;
+        case OTRL_MSGEVENT_MSG_RESENT:
+            ui_handle_otr_error(context->username, "OTR: The previous message was resent.");
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE:
+            ui_handle_otr_error(context->username, "OTR: Received an encrypted message but no private connection established.");
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_UNREADABLE:
+            ui_handle_otr_error(context->username, "OTR: Cannot read the received message.");
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_MALFORMED:
+            ui_handle_otr_error(context->username, "OTR: The message received contains malformed data.");
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_GENERAL_ERR:
+            err_msg = g_string_new("OTR: Received error: ");
+            g_string_append(err_msg, message);
+            g_string_append(err_msg, ".");
+            ui_handle_otr_error(context->username, err_msg->str);
+            g_string_free(err_msg, TRUE);
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED:
+            err_msg = g_string_new("OTR: Received an unencrypted message: ");
+            g_string_append(err_msg, message);
+            ui_handle_otr_error(context->username, err_msg->str);
+            g_string_free(err_msg, TRUE);
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED:
+            ui_handle_otr_error(context->username, "OTR: Cannot recognize the type of message received.");
+            break;
+        case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE:
+            ui_handle_otr_error(context->username, "OTR: Received and discarded a message intended for another instance.");
+            break;
+        default:
+            break;
     }
 }
 
@@ -208,7 +253,7 @@ otrlib_end_session(OtrlUserState user_state, const char * const recipient, char
     ConnContext *context = otrl_context_find(user_state, recipient, jid, "xmpp",
         OTRL_INSTAG_MASTER, 0, NULL, NULL, NULL);
 
-    if (context != NULL) {
+    if (context) {
         otrl_message_disconnect(user_state, ops, NULL, jid, "xmpp", recipient, 0);
     }
 }
diff --git a/src/profanity.c b/src/profanity.c
index 13297124..55a73430 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -1,7 +1,7 @@
 /*
  * profanity.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -63,6 +63,7 @@
 #include "xmpp/xmpp.h"
 #include "ui/ui.h"
 #include "ui/windows.h"
+#include "event/client_events.h"
 
 static void _check_autoaway(void);
 static void _init(const int disable_tls, char *log_level);
@@ -71,6 +72,7 @@ static void _create_directories(void);
 static void _connect_default(const char * const account);
 
 static gboolean idle = FALSE;
+static gboolean cont = TRUE;
 
 void
 prof_run(const int disable_tls, char *log_level, char *account_name)
@@ -79,25 +81,27 @@ prof_run(const int disable_tls, char *log_level, char *account_name)
     _connect_default(account_name);
     ui_update();
 
-    char *line = NULL;
-    gboolean cmd_result = TRUE;
-
     log_info("Starting main event loop");
 
-    while(cmd_result) {
-        while(!line) {
-            _check_autoaway();
-            line = ui_readline();
+    char *line = NULL;
+    while(cont) {
+        _check_autoaway();
+
+        line = ui_readline();
+        if (line) {
+            cont = cmd_process_input(line);
+            free(line);
+            line = NULL;
+        } else {
+            cont = TRUE;
+        }
+
 #ifdef HAVE_LIBOTR
-            otr_poll();
+        otr_poll();
 #endif
-            notify_remind();
-            jabber_process_events();
-            ui_update();
-        }
-        cmd_result = cmd_process_input(line);
-        ui_input_clear();
-        FREE_SET_NULL(line);
+        notify_remind();
+        jabber_process_events();
+        ui_update();
     }
 }
 
@@ -109,14 +113,14 @@ prof_handle_idle(void)
         GSList *recipients = ui_get_chat_recipients();
         GSList *curr = recipients;
 
-        while (curr != NULL) {
+        while (curr) {
             char *barejid = curr->data;
             ProfChatWin *chatwin = wins_get_chat(barejid);
             chat_state_handle_idle(chatwin->barejid, chatwin->state);
             curr = g_slist_next(curr);
         }
 
-        if (recipients != NULL) {
+        if (recipients) {
             g_slist_free(recipients);
         }
     }
@@ -169,12 +173,12 @@ _check_autoaway()
 
                 // handle away mode
                 if (strcmp(pref_autoaway_mode, "away") == 0) {
-                    presence_update(RESOURCE_AWAY, pref_autoaway_message, 0);
+                    cl_ev_presence_send(RESOURCE_AWAY, pref_autoaway_message, 0);
                     ui_auto_away();
 
                 // handle idle mode
                 } else if (strcmp(pref_autoaway_mode, "idle") == 0) {
-                    presence_update(RESOURCE_ONLINE, pref_autoaway_message, idle_ms / 1000);
+                    cl_ev_presence_send(RESOURCE_ONLINE, pref_autoaway_message, idle_ms / 1000);
                 }
 
                 prefs_free_string(pref_autoaway_message);
@@ -188,10 +192,10 @@ _check_autoaway()
             // handle check
             if (prefs_get_boolean(PREF_AUTOAWAY_CHECK)) {
                 if (strcmp(pref_autoaway_mode, "away") == 0) {
-                    presence_update(RESOURCE_ONLINE, NULL, 0);
+                    cl_ev_presence_send(RESOURCE_ONLINE, NULL, 0);
                     ui_end_auto_away();
                 } else if (strcmp(pref_autoaway_mode, "idle") == 0) {
-                    presence_update(RESOURCE_ONLINE, NULL, 0);
+                    cl_ev_presence_send(RESOURCE_ONLINE, NULL, 0);
                     ui_titlebar_presence(CONTACT_ONLINE);
                 }
             }
@@ -209,6 +213,7 @@ _init(const int disable_tls, char *log_level)
     signal(SIGPIPE, SIG_IGN);
     signal(SIGINT, SIG_IGN);
     signal(SIGTSTP, SIG_IGN);
+    signal(SIGWINCH, ui_sigwinch_handler);
     _create_directories();
     log_level_t prof_log_level = log_level_from_string(log_level);
     prefs_load();
diff --git a/src/profanity.h b/src/profanity.h
index f01169f8..269c616a 100644
--- a/src/profanity.h
+++ b/src/profanity.h
@@ -1,7 +1,7 @@
 /*
  * profanity.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/resource.c b/src/resource.c
index 844cb1d3..1598c7b2 100644
--- a/src/resource.c
+++ b/src/resource.c
@@ -1,7 +1,7 @@
 /*
  * resource.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -46,7 +46,7 @@ Resource * resource_new(const char * const name, resource_presence_t presence,
     Resource *new_resource = malloc(sizeof(struct resource_t));
     new_resource->name = strdup(name);
     new_resource->presence = presence;
-    if (status != NULL) {
+    if (status) {
         new_resource->status = strdup(status);
     } else {
         new_resource->status = NULL;
@@ -88,7 +88,7 @@ resource_compare_availability(Resource *first, Resource *second)
 
 void resource_destroy(Resource *resource)
 {
-    if (resource != NULL) {
+    if (resource) {
         free(resource->name);
         free(resource->status);
         free(resource);
diff --git a/src/resource.h b/src/resource.h
index 5c39d969..43bb6d18 100644
--- a/src/resource.h
+++ b/src/resource.h
@@ -1,7 +1,7 @@
 /*
  * resource.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/roster_list.c b/src/roster_list.c
index 44d05ff0..2d9df860 100644
--- a/src/roster_list.c
+++ b/src/roster_list.c
@@ -1,7 +1,7 @@
 /*
  * roster_list.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -42,6 +42,7 @@
 #include "contact.h"
 #include "jid.h"
 #include "tools/autocomplete.h"
+#include "config/preferences.h"
 
 // nicknames
 static Autocomplete name_ac;
@@ -91,7 +92,7 @@ roster_update_presence(const char * const barejid, Resource *resource,
     assert(barejid != NULL);
     assert(resource != NULL);
 
-    PContact contact = g_hash_table_lookup(contacts, barejid);
+    PContact contact = roster_get_contact(barejid);
     if (contact == NULL) {
         return FALSE;
     }
@@ -109,14 +110,45 @@ roster_update_presence(const char * const barejid, Resource *resource,
 PContact
 roster_get_contact(const char * const barejid)
 {
-    return g_hash_table_lookup(contacts, barejid);
+    gchar *barejidlower = g_utf8_strdown(barejid, -1);
+    PContact contact = g_hash_table_lookup(contacts, barejidlower);
+    g_free(barejidlower);
+
+    return contact;
+}
+
+char *
+roster_get_msg_display_name(const char * const barejid, const char * const resource)
+{
+    GString *result = g_string_new("");
+
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
+        if (p_contact_name(contact)) {
+            g_string_append(result, p_contact_name(contact));
+        } else {
+            g_string_append(result, barejid);
+        }
+    } else {
+        g_string_append(result, barejid);
+    }
+
+    if (resource && prefs_get_boolean(PREF_RESOURCE_MESSAGE)) {
+        g_string_append(result, "/");
+        g_string_append(result, resource);
+    }
+
+    char *result_str = result->str;
+    g_string_free(result, FALSE);
+
+    return result_str;
 }
 
 gboolean
 roster_contact_offline(const char * const barejid,
     const char * const resource, const char * const status)
 {
-    PContact contact = g_hash_table_lookup(contacts, barejid);
+    PContact contact = roster_get_contact(barejid);
 
     if (contact == NULL) {
         return FALSE;
@@ -174,7 +206,7 @@ roster_change_name(PContact contact, const char * const new_name)
     const char *current_name = NULL;
     const char *barejid = p_contact_barejid(contact);
 
-    if (p_contact_name(contact) != NULL) {
+    if (p_contact_name(contact)) {
         current_name = strdup(p_contact_name(contact));
     }
 
@@ -191,9 +223,9 @@ roster_remove(const char * const name, const char * const barejid)
 
     // remove each fulljid
     PContact contact = roster_get_contact(barejid);
-    if (contact != NULL) {
+    if (contact) {
         GList *resources = p_contact_get_available_resources(contact);
-        while (resources != NULL) {
+        while (resources) {
             GString *fulljid = g_string_new(strdup(barejid));
             g_string_append(fulljid, "/");
             g_string_append(fulljid, resources->data);
@@ -212,7 +244,7 @@ void
 roster_update(const char * const barejid, const char * const name,
     GSList *groups, const char * const subscription, gboolean pending_out)
 {
-    PContact contact = g_hash_table_lookup(contacts, barejid);
+    PContact contact = roster_get_contact(barejid);
     assert(contact != NULL);
 
     p_contact_set_subscription(contact, subscription);
@@ -220,7 +252,7 @@ roster_update(const char * const barejid, const char * const name,
 
     const char * const new_name = name;
     const char * current_name = NULL;
-    if (p_contact_name(contact) != NULL) {
+    if (p_contact_name(contact)) {
         current_name = strdup(p_contact_name(contact));
     }
 
@@ -229,7 +261,7 @@ roster_update(const char * const barejid, const char * const name,
     _replace_name(current_name, new_name, barejid);
 
     // add groups
-    while (groups != NULL) {
+    while (groups) {
         autocomplete_add(groups_ac, groups->data);
         groups = g_slist_next(groups);
     }
@@ -239,8 +271,8 @@ gboolean
 roster_add(const char * const barejid, const char * const name, GSList *groups,
     const char * const subscription, gboolean pending_out)
 {
-    PContact contact = g_hash_table_lookup(contacts, barejid);
-    if (contact != NULL) {
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
         return FALSE;
     }
 
@@ -248,7 +280,7 @@ roster_add(const char * const barejid, const char * const name, GSList *groups,
         pending_out);
 
     // add groups
-    while (groups != NULL) {
+    while (groups) {
         autocomplete_add(groups_ac, groups->data);
         groups = g_slist_next(groups);
     }
@@ -286,7 +318,7 @@ roster_get_contacts_by_presence(const char * const presence)
         }
     }
 
-    // resturn all contact structs
+    // return all contact structs
     return result;
 }
 
@@ -303,7 +335,7 @@ roster_get_contacts(void)
         result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
     }
 
-    // resturn all contact structs
+    // return all contact structs
     return result;
 }
 
@@ -321,7 +353,7 @@ roster_get_contacts_online(void)
             result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
     }
 
-    // resturn all contact structs
+    // return all contact structs
     return result;
 }
 
@@ -371,7 +403,7 @@ roster_get_nogroup(void)
         }
     }
 
-    // resturn all contact structs
+    // return all contact structs
     return result;
 }
 
@@ -386,7 +418,7 @@ roster_get_group(const char * const group)
     g_hash_table_iter_init(&iter, contacts);
     while (g_hash_table_iter_next(&iter, &key, &value)) {
         GSList *groups = p_contact_groups(value);
-        while (groups != NULL) {
+        while (groups) {
             if (strcmp(groups->data, group) == 0) {
                 result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_contacts);
                 break;
@@ -395,7 +427,7 @@ roster_get_group(const char * const group)
         }
     }
 
-    // resturn all contact structs
+    // return all contact structs
     return result;
 }
 
@@ -445,12 +477,12 @@ _replace_name(const char * const current_name, const char * const new_name,
     const char * const barejid)
 {
     // current handle exists already
-    if (current_name != NULL) {
+    if (current_name) {
         autocomplete_remove(name_ac, current_name);
         g_hash_table_remove(name_to_barejid, current_name);
         _add_name_and_barejid(new_name, barejid);
     // no current handle
-    } else if (new_name != NULL) {
+    } else if (new_name) {
         autocomplete_remove(name_ac, barejid);
         g_hash_table_remove(name_to_barejid, barejid);
         _add_name_and_barejid(new_name, barejid);
@@ -460,7 +492,7 @@ _replace_name(const char * const current_name, const char * const new_name,
 static void
 _add_name_and_barejid(const char * const name, const char * const barejid)
 {
-    if (name != NULL) {
+    if (name) {
         autocomplete_add(name_ac, name);
         g_hash_table_insert(name_to_barejid, strdup(name), strdup(barejid));
     } else {
@@ -475,24 +507,18 @@ gint _compare_contacts(PContact a, PContact b)
     const char * utf8_str_a = NULL;
     const char * utf8_str_b = NULL;
 
-    if (p_contact_name(a) != NULL) {
-        utf8_str_a = p_contact_name(a);
+    if (p_contact_name_collate_key(a)) {
+        utf8_str_a = p_contact_name_collate_key(a);
     } else {
-        utf8_str_a = p_contact_barejid(a);
+        utf8_str_a = p_contact_barejid_collate_key(a);
     }
-    if (p_contact_name(b) != NULL) {
-        utf8_str_b = p_contact_name(b);
+    if (p_contact_name_collate_key(b)) {
+        utf8_str_b = p_contact_name_collate_key(b);
     } else {
-        utf8_str_b = p_contact_barejid(b);
+        utf8_str_b = p_contact_barejid_collate_key(b);
     }
 
-    gchar *key_a = g_utf8_collate_key(utf8_str_a, -1);
-    gchar *key_b = g_utf8_collate_key(utf8_str_b, -1);
-
-    gint result = g_strcmp0(key_a, key_b);
-
-    g_free(key_a);
-    g_free(key_b);
+    gint result = g_strcmp0(utf8_str_a, utf8_str_b);
 
     return result;
 }
diff --git a/src/roster_list.h b/src/roster_list.h
index e193085b..4417c763 100644
--- a/src/roster_list.h
+++ b/src/roster_list.h
@@ -1,7 +1,7 @@
 /*
  * roster_list.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -67,5 +67,6 @@ char * roster_group_autocomplete(const char * const search_str);
 char * roster_barejid_autocomplete(const char * const search_str);
 GSList * roster_get_contacts_by_presence(const char * const presence);
 GSList * roster_get_nogroup(void);
+char * roster_get_msg_display_name(const char * const barejid, const char * const resource);
 
 #endif
diff --git a/src/server_events.c b/src/server_events.c
deleted file mode 100644
index fbf534ac..00000000
--- a/src/server_events.c
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * server_events.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 <string.h>
-#include <stdlib.h>
-
-#include "config.h"
-
-#include "chat_session.h"
-#include "log.h"
-#include "muc.h"
-#include "config/preferences.h"
-#include "config/account.h"
-#include "roster_list.h"
-
-#ifdef HAVE_LIBOTR
-#include "otr/otr.h"
-#include <libotr/proto.h>
-#endif
-
-#include "ui/ui.h"
-
-void
-handle_room_join_error(const char * const room, const char * const err)
-{
-    if (muc_active(room)) {
-        muc_leave(room);
-    }
-    ui_handle_room_join_error(room, err);
-}
-
-// handle presence stanza errors
-void
-handle_presence_error(const char *from, const char * const type,
-    const char *err_msg)
-{
-    // handle error from recipient
-    if (from != NULL) {
-        ui_handle_recipient_error(from, err_msg);
-
-    // handle errors from no recipient
-    } else {
-        ui_handle_error(err_msg);
-    }
-}
-
-// handle message stanza errors
-void
-handle_message_error(const char * const jid, const char * const type,
-    const char * const err_msg)
-{
-    // handle errors from no recipient
-    if (jid == NULL) {
-        ui_handle_error(err_msg);
-
-    // handle recipient not found ('from' contains a value and type is 'cancel')
-    } else if (type != NULL && (strcmp(type, "cancel") == 0)) {
-        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 {
-        ui_handle_recipient_error(jid, err_msg);
-    }
-}
-
-void
-handle_login_account_success(char *account_name)
-{
-    ProfAccount *account = accounts_get_account(account_name);
-
-#ifdef HAVE_LIBOTR
-    otr_on_connect(account);
-#endif
-
-    ui_handle_login_account_success(account);
-
-    // attempt to rejoin rooms with passwords
-    GList *curr = muc_rooms();
-    while (curr != NULL) {
-        char *password = muc_password(curr->data);
-        if (password != NULL) {
-            char *nick = muc_nick(curr->data);
-            presence_join_room(curr->data, nick, password);
-        }
-        curr = g_list_next(curr);
-    }
-    g_list_free(curr);
-
-    log_info("%s logged in successfully", account->jid);
-    account_free(account);
-}
-
-void
-handle_roster_received(void)
-{
-    if (prefs_get_boolean(PREF_ROSTER)) {
-        ui_show_roster();
-    }
-}
-
-void
-handle_lost_connection(void)
-{
-    cons_show_error("Lost connection.");
-    roster_clear();
-    muc_invites_clear();
-    chat_sessions_clear();
-    ui_disconnected();
-}
-
-void
-handle_failed_login(void)
-{
-    cons_show_error("Login failed.");
-    log_info("Login failed");
-}
-
-void
-handle_software_version_result(const char * const jid, const char * const  presence,
-    const char * const name, const char * const version, const char * const os)
-{
-    cons_show_software_version(jid, presence, name, version, os);
-}
-
-void
-handle_disco_info(const char *from, GSList *identities, GSList *features)
-{
-    cons_show_disco_info(from, identities, features);
-}
-
-void
-handle_room_disco_info(const char * const room, GSList *identities, GSList *features)
-{
-    ui_show_room_disco_info(room, identities, features);
-}
-
-void
-handle_disco_info_error(const char * const from, const char * const error)
-{
-    if (from) {
-        cons_show_error("Service discovery failed for %s: %s", from, error);
-    } else {
-        cons_show_error("Service discovery failed: %s", error);
-    }
-}
-
-void
-handle_room_info_error(const char * const room, const char * const error)
-{
-    ui_handle_room_info_error(room, error);
-}
-
-void
-handle_room_list(GSList *rooms, const char *conference_node)
-{
-    cons_show_room_list(rooms, conference_node);
-}
-
-void
-handle_room_affiliation_list_result_error(const char * const room, const char * const affiliation,
-    const char * const error)
-{
-    log_debug("Error retrieving %s list for room %s: %s", affiliation, room, error);
-    ui_handle_room_affiliation_list_error(room, affiliation, error);
-}
-
-void
-handle_room_affiliation_list(const char * const room, const char * const affiliation, GSList *jids)
-{
-    muc_jid_autocomplete_add_all(room, jids);
-    ui_handle_room_affiliation_list(room, affiliation, jids);
-}
-
-void
-handle_room_role_set_error(const char * const room, const char * const nick, const char * const role,
-    const char * const error)
-{
-    log_debug("Error setting role %s list for room %s, user %s: %s", role, room, nick, error);
-    ui_handle_room_role_set_error(room, nick, role, error);
-}
-
-void
-handle_room_role_list_result_error(const char * const room, const char * const role, const char * const error)
-{
-    log_debug("Error retrieving %s list for room %s: %s", role, room, error);
-    ui_handle_room_role_list_error(room, role, error);
-}
-
-void
-handle_room_role_list(const char * const room, const char * const role, GSList *nicks)
-{
-    ui_handle_room_role_list(room, role, nicks);
-}
-
-void
-handle_room_affiliation_set_error(const char * const room, const char * const jid, const char * const affiliation,
-    const char * const error)
-{
-    log_debug("Error setting affiliation %s list for room %s, user %s: %s", affiliation, room, jid, error);
-    ui_handle_room_affiliation_set_error(room, jid, affiliation, error);
-}
-
-void
-handle_disco_items(GSList *items, const char *jid)
-{
-    cons_show_disco_items(items, jid);
-}
-
-void
-handle_room_invite(jabber_invite_t invite_type,
-    const char * const invitor, const char * const room,
-    const char * const reason)
-{
-    if (!muc_active(room) && !muc_invites_contain(room)) {
-        cons_show_room_invite(invitor, room, reason);
-        muc_invites_add(room);
-    }
-}
-
-void
-handle_room_broadcast(const char *const room_jid,
-    const char * const message)
-{
-    if (muc_roster_complete(room_jid)) {
-        ui_room_broadcast(room_jid, message);
-    } else {
-        muc_pending_broadcasts_add(room_jid, message);
-    }
-}
-
-void
-handle_room_subject(const char * const room, const char * const nick, const char * const subject)
-{
-    muc_set_subject(room, subject);
-    if (muc_roster_complete(room)) {
-        ui_room_subject(room, nick, subject);
-    }
-}
-
-void
-handle_room_history(const char * const room_jid, const char * const nick,
-    GTimeVal tv_stamp, const char * const message)
-{
-    ui_room_history(room_jid, nick, tv_stamp, message);
-}
-
-void
-handle_room_message(const char * const room_jid, const char * const nick,
-    const char * const message)
-{
-    ui_room_message(room_jid, nick, message);
-
-    if (prefs_get_boolean(PREF_GRLOG)) {
-        Jid *jid = jid_create(jabber_get_fulljid());
-        groupchat_log_chat(jid->barejid, room_jid, nick, message);
-        jid_destroy(jid);
-    }
-}
-
-void
-handle_incoming_private_message(char *fulljid, char *message)
-{
-    ui_incoming_private_msg(fulljid, message, NULL);
-}
-
-void
-handle_incoming_message(char *barejid, char *resource, char *message)
-{
-#ifdef HAVE_LIBOTR
-    gboolean was_decrypted = FALSE;
-    char *newmessage;
-
-    prof_otrpolicy_t policy = otr_get_policy(barejid);
-    char *whitespace_base = strstr(message,OTRL_MESSAGE_TAG_BASE);
-
-    //check for OTR whitespace (opportunistic or always)
-    if (policy == PROF_OTRPOLICY_OPPORTUNISTIC || policy == PROF_OTRPOLICY_ALWAYS) {
-        if (whitespace_base) {
-            if (strstr(message, OTRL_MESSAGE_TAG_V2) || strstr(message, OTRL_MESSAGE_TAG_V1)) {
-                // Remove whitespace pattern for proper display in UI
-                // Handle both BASE+TAGV1/2(16+8) and BASE+TAGV1+TAGV2(16+8+8)
-                int tag_length	=	24;
-                if (strstr(message, OTRL_MESSAGE_TAG_V2) && strstr(message, OTRL_MESSAGE_TAG_V1)) {
-                    tag_length = 32;
-                }
-                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...");
-                message_send_chat(barejid, otr_query_message);
-            }
-        }
-    }
-    newmessage = otr_decrypt_message(barejid, message, &was_decrypted);
-
-    // internal OTR message
-    if (newmessage == NULL) {
-        return;
-    }
-
-    if (policy == PROF_OTRPOLICY_ALWAYS && !was_decrypted && !whitespace_base) {
-        char *otr_query_message = otr_start_query();
-        cons_show("Attempting to start OTR session...");
-        message_send_chat(barejid, otr_query_message);
-    }
-
-    ui_incoming_msg(barejid, resource, newmessage, NULL);
-
-    if (prefs_get_boolean(PREF_CHLOG)) {
-        const char *jid = jabber_get_fulljid();
-        Jid *jidp = jid_create(jid);
-
-        char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
-        if (!was_decrypted || (strcmp(pref_otr_log, "on") == 0)) {
-            chat_log_chat(jidp->barejid, barejid, newmessage, PROF_IN_LOG, NULL);
-        } else if (strcmp(pref_otr_log, "redact") == 0) {
-            chat_log_chat(jidp->barejid, barejid, "[redacted]", PROF_IN_LOG, NULL);
-        }
-        prefs_free_string(pref_otr_log);
-
-        jid_destroy(jidp);
-    }
-
-    otr_free_message(newmessage);
-#else
-    ui_incoming_msg(barejid, resource, message, NULL);
-
-    if (prefs_get_boolean(PREF_CHLOG)) {
-        const char *jid = jabber_get_fulljid();
-        Jid *jidp = jid_create(jid);
-        chat_log_chat(jidp->barejid, barejid, message, PROF_IN_LOG, NULL);
-        jid_destroy(jidp);
-    }
-#endif
-}
-
-void
-handle_delayed_private_message(char *fulljid, char *message, GTimeVal tv_stamp)
-{
-    ui_incoming_private_msg(fulljid, message, &tv_stamp);
-}
-
-void
-handle_delayed_message(char *barejid, char *message, GTimeVal tv_stamp)
-{
-    ui_incoming_msg(barejid, NULL, message, &tv_stamp);
-
-    if (prefs_get_boolean(PREF_CHLOG)) {
-        const char *jid = jabber_get_fulljid();
-        Jid *jidp = jid_create(jid);
-        chat_log_chat(jidp->barejid, barejid, message, PROF_IN_LOG, &tv_stamp);
-        jid_destroy(jidp);
-    }
-}
-
-void
-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_recipient_gone(barejid, resource);
-    if (ui_chat_win_exists(barejid)) {
-        chat_session_recipient_gone(barejid, resource);
-    }
-}
-
-void
-handle_activity(const char * const barejid, const char * const resource, gboolean send_states)
-{
-    if (ui_chat_win_exists(barejid)) {
-        chat_session_recipient_active(barejid, resource, send_states);
-    }
-}
-
-void
-handle_subscription(const char *barejid, jabber_subscr_t type)
-{
-    switch (type) {
-    case PRESENCE_SUBSCRIBE:
-        /* TODO: auto-subscribe if needed */
-        cons_show("Received authorization request from %s", barejid);
-        log_info("Received authorization request from %s", barejid);
-        ui_print_system_msg_from_recipient(barejid, "Authorization request, type '/sub allow' to accept or '/sub deny' to reject");
-        if (prefs_get_boolean(PREF_NOTIFY_SUB)) {
-            notify_subscription(barejid);
-        }
-        break;
-    case PRESENCE_SUBSCRIBED:
-        cons_show("Subscription received from %s", barejid);
-        log_info("Subscription received from %s", barejid);
-        ui_print_system_msg_from_recipient(barejid, "Subscribed");
-        break;
-    case PRESENCE_UNSUBSCRIBED:
-        cons_show("%s deleted subscription", barejid);
-        log_info("%s deleted subscription", barejid);
-        ui_print_system_msg_from_recipient(barejid, "Unsubscribed");
-        break;
-    default:
-        /* unknown type */
-        break;
-    }
-}
-
-void
-handle_contact_offline(char *barejid, char *resource, char *status)
-{
-    gboolean updated = roster_contact_offline(barejid, resource, status);
-
-    if (resource != NULL && updated) {
-        ui_contact_offline(barejid, resource, status);
-    }
-
-    rosterwin_roster();
-    chat_session_remove(barejid);
-}
-
-void
-handle_contact_online(char *barejid, Resource *resource,
-    GDateTime *last_activity)
-{
-    gboolean updated = roster_update_presence(barejid, resource, last_activity);
-
-    if (updated) {
-        char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
-        char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
-        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_online(contact, resource, last_activity);
-
-                // show in console of "online" and presence online
-                } else if (g_strcmp0(show_console, "online") == 0 &&
-                        resource->presence == RESOURCE_ONLINE) {
-                    cons_show_contact_online(contact, resource, last_activity);
-
-                }
-
-                // show in chat win if "all"
-                if (g_strcmp0(show_chat_win, "all") == 0) {
-                    ui_chat_win_contact_online(contact, resource, last_activity);
-
-                // show in char win if "online" and presence online
-                } else if (g_strcmp0(show_chat_win, "online") == 0 &&
-                        resource->presence == RESOURCE_ONLINE) {
-                    ui_chat_win_contact_online(contact, resource, last_activity);
-                }
-            }
-        }
-        prefs_free_string(show_console);
-        prefs_free_string(show_chat_win);
-    }
-
-    rosterwin_roster();
-    chat_session_remove(barejid);
-}
-
-void
-handle_leave_room(const char * const room)
-{
-    muc_leave(room);
-    ui_leave_room(room);
-}
-
-void
-handle_room_destroy(const char * const room)
-{
-    muc_leave(room);
-    ui_room_destroy(room);
-}
-
-void
-handle_room_destroyed(const char * const room, const char * const new_jid, const char * const password,
-    const char * const reason)
-{
-    muc_leave(room);
-    ui_room_destroyed(room, reason, new_jid, password);
-}
-
-void
-handle_room_kicked(const char * const room, const char * const actor, const char * const reason)
-{
-    muc_leave(room);
-    ui_room_kicked(room, actor, reason);
-}
-
-void
-handle_room_banned(const char * const room, const char * const actor, const char * const reason)
-{
-    muc_leave(room);
-    ui_room_banned(room, actor, reason);
-}
-
-void
-handle_room_configure(const char * const room, DataForm *form)
-{
-    ui_handle_room_configuration(room, form);
-}
-
-void
-handle_room_configuration_form_error(const char * const room, const char * const message)
-{
-    ui_handle_room_configuration_form_error(room, message);
-}
-
-void
-handle_room_config_submit_result(const char * const room)
-{
-    ui_handle_room_config_submit_result(room);
-}
-
-void
-handle_room_config_submit_result_error(const char * const room, const char * const message)
-{
-    ui_handle_room_config_submit_result_error(room, message);
-}
-
-void
-handle_room_kick_result_error(const char * const room, const char * const nick, const char * const error)
-{
-    ui_handle_room_kick_error(room, nick, error);
-}
-
-void
-handle_room_occupant_offline(const char * const room, const char * const nick,
-    const char * const show, const char * const status)
-{
-    muc_roster_remove(room, nick);
-
-    char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
-    if (g_strcmp0(muc_status_pref, "none") != 0) {
-        ui_room_member_offline(room, nick);
-    }
-    prefs_free_string(muc_status_pref);
-    occupantswin_occupants(room);
-}
-
-void
-handle_room_occupent_kicked(const char * const room, const char * const nick, const char * const actor,
-    const char * const reason)
-{
-    muc_roster_remove(room, nick);
-    ui_room_member_kicked(room, nick, actor, reason);
-    occupantswin_occupants(room);
-}
-
-void
-handle_room_occupent_banned(const char * const room, const char * const nick, const char * const actor,
-    const char * const reason)
-{
-    muc_roster_remove(room, nick);
-    ui_room_member_banned(room, nick, actor, reason);
-    occupantswin_occupants(room);
-}
-
-void
-handle_group_add(const char * const contact,
-    const char * const group)
-{
-    ui_group_added(contact, group);
-}
-
-void
-handle_group_remove(const char * const contact,
-    const char * const group)
-{
-    ui_group_removed(contact, group);
-}
-
-void
-handle_roster_remove(const char * const barejid)
-{
-    ui_roster_remove(barejid);
-}
-
-void
-handle_roster_add(const char * const barejid, const char * const name)
-{
-    ui_roster_add(barejid, name);
-}
-
-void
-handle_roster_update(const char * const barejid, const char * const name,
-    GSList *groups, const char * const subscription, gboolean pending_out)
-{
-    roster_update(barejid, name, groups, subscription, pending_out);
-    rosterwin_roster();
-}
-
-void
-handle_autoping_cancel(void)
-{
-    prefs_set_autoping(0);
-    cons_show_error("Server ping not supported, autoping disabled.");
-}
-
-void
-handle_xmpp_stanza(const char * const msg)
-{
-    ui_handle_stanza(msg);
-}
-
-void
-handle_ping_result(const char * const from, int millis)
-{
-    if (from == NULL) {
-        cons_show("Ping response from server: %dms.", millis);
-    } else {
-        cons_show("Ping response from %s: %dms.", from, millis);
-    }
-}
-
-void
-handle_ping_error_result(const char * const from, const char * const error)
-{
-    if (error == NULL) {
-        cons_show_error("Error returned from pinging %s.", from);
-    } else {
-        cons_show_error("Error returned from pinging %s: %s.", from, error);
-    }
-}
-
-void
-handle_muc_self_online(const char * const room, const char * const nick, gboolean config_required,
-    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
-    const char * const jid, const char * const show, const char * const status)
-{
-    muc_roster_add(room, nick, jid, role, affiliation, show, status);
-    char *old_role = muc_role_str(room);
-    char *old_affiliation = muc_affiliation_str(room);
-    muc_set_role(room, role);
-    muc_set_affiliation(room, affiliation);
-
-    // handle self nick change
-    if (muc_nick_change_pending(room)) {
-        muc_nick_change_complete(room, nick);
-        ui_room_nick_change(room, nick);
-
-    // handle roster complete
-    } else if (!muc_roster_complete(room)) {
-        if (muc_autojoin(room)) {
-            ui_room_join(room, FALSE);
-        } else {
-            ui_room_join(room, TRUE);
-        }
-        muc_invites_remove(room);
-        muc_roster_set_complete(room);
-
-        // show roster if occupants list disabled by default
-        if (!prefs_get_boolean(PREF_OCCUPANTS)) {
-            GList *occupants = muc_roster(room);
-            ui_room_roster(room, occupants, NULL);
-            g_list_free(occupants);
-        }
-
-        char *subject = muc_subject(room);
-        if (subject != NULL) {
-            ui_room_subject(room, NULL, subject);
-        }
-
-        GList *pending_broadcasts = muc_pending_broadcasts(room);
-        if (pending_broadcasts != NULL) {
-            GList *curr = pending_broadcasts;
-            while (curr != NULL) {
-                ui_room_broadcast(room, curr->data);
-                curr = g_list_next(curr);
-            }
-        }
-
-        // room configuration required
-        if (config_required) {
-            muc_set_requires_config(room, TRUE);
-            ui_room_requires_config(room);
-        }
-
-    // check for change in role/affiliation
-    } else {
-        if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
-            // both changed
-            if ((g_strcmp0(role, old_role) != 0) && (g_strcmp0(affiliation, old_affiliation) != 0)) {
-                ui_room_role_and_affiliation_change(room, role, affiliation, actor, reason);
-
-            // role changed
-            } else if (g_strcmp0(role, old_role) != 0) {
-                ui_room_role_change(room, role, actor, reason);
-
-            // affiliation changed
-            } else if (g_strcmp0(affiliation, old_affiliation) != 0) {
-                ui_room_affiliation_change(room, affiliation, actor, reason);
-            }
-        }
-    }
-
-    occupantswin_occupants(room);
-}
-
-void
-handle_muc_occupant_online(const char * const room, const char * const nick, const char * const jid,
-    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
-    const char * const show, const char * const status)
-{
-    Occupant *occupant = muc_roster_item(room, nick);
-
-    const char *old_role = NULL;
-    const char *old_affiliation = NULL;
-    if (occupant) {
-        old_role = muc_occupant_role_str(occupant);
-        old_affiliation = muc_occupant_affiliation_str(occupant);
-    }
-
-    gboolean updated = muc_roster_add(room, nick, jid, role, affiliation, show, status);
-
-    // not yet finished joining room
-    if (!muc_roster_complete(room)) {
-        return;
-    }
-
-    // handle nickname change
-    char *old_nick = muc_roster_nick_change_complete(room, nick);
-    if (old_nick) {
-        ui_room_member_nick_change(room, old_nick, nick);
-        free(old_nick);
-        occupantswin_occupants(room);
-        return;
-    }
-
-    // joined room
-    if (!occupant) {
-        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
-        if (g_strcmp0(muc_status_pref, "none") != 0) {
-            ui_room_member_online(room, nick, role, affiliation, show, status);
-        }
-        prefs_free_string(muc_status_pref);
-        occupantswin_occupants(room);
-        return;
-    }
-
-    // presence updated
-    if (updated) {
-        char *muc_status_pref = prefs_get_string(PREF_STATUSES_MUC);
-        if (g_strcmp0(muc_status_pref, "all") == 0) {
-            ui_room_member_presence(room, nick, show, status);
-        }
-        prefs_free_string(muc_status_pref);
-        occupantswin_occupants(room);
-
-    // presence unchanged, check for role/affiliation change
-    } else {
-        if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
-            // both changed
-            if ((g_strcmp0(role, old_role) != 0) && (g_strcmp0(affiliation, old_affiliation) != 0)) {
-                ui_room_occupant_role_and_affiliation_change(room, nick, role, affiliation, actor, reason);
-
-            // role changed
-            } else if (g_strcmp0(role, old_role) != 0) {
-                ui_room_occupant_role_change(room, nick, role, actor, reason);
-
-            // affiliation changed
-            } else if (g_strcmp0(affiliation, old_affiliation) != 0) {
-                ui_room_occupant_affiliation_change(room, nick, affiliation, actor, reason);
-            }
-        }
-        occupantswin_occupants(room);
-    }
-}
\ No newline at end of file
diff --git a/src/server_events.h b/src/server_events.h
deleted file mode 100644
index 6a12dc6e..00000000
--- a/src/server_events.h
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * server_events.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 SERVER_EVENTS_H
-#define SERVER_EVENTS_H
-
-#include "xmpp/xmpp.h"
-
-void handle_login_account_success(char *account_name);
-void handle_lost_connection(void);
-void handle_failed_login(void);
-void handle_software_version_result(const char * const jid, const char * const  presence,
-    const char * const name, const char * const version, const char * const os);
-void handle_disco_info(const char *from, GSList *identities, GSList *features);
-void handle_disco_info_error(const char * const from, const char * const error);
-void handle_room_list(GSList *rooms, const char *conference_node);
-void handle_disco_items(GSList *items, const char *jid);
-void handle_room_invite(jabber_invite_t invite_type,
-    const char * const invitor, const char * const room,
-    const char * const reason);
-void handle_room_broadcast(const char *const room_jid,
-    const char * const message);
-void handle_room_subject(const char * const room, const char * const nick, const char * const subject);
-void handle_room_history(const char * const room_jid, const char * const nick,
-    GTimeVal tv_stamp, const char * const message);
-void handle_room_message(const char * const room_jid, const char * const nick,
-    const char * const message);
-void handle_room_join_error(const char * const room, const char * const err);
-void handle_room_info_error(const char * const room, const char * const error);
-void handle_room_disco_info(const char * const room, GSList *identities, GSList *features);
-void handle_room_affiliation_list_result_error(const char * const room, const char * const affiliation,
-    const char * const error);
-void handle_room_affiliation_list(const char * const room, const char * const affiliation, GSList *jids);
-void handle_room_affiliation_set_error(const char * const room, const char * const jid, const char * const affiliation,
-    const char * const error);
-void handle_room_role_list_result_error(const char * const from, const char * const role, const char * const error);
-void handle_room_role_list(const char * const from, const char * const role, GSList *nicks);
-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 *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 *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,
-    GDateTime *last_activity);
-void handle_leave_room(const char * const room);
-void handle_room_destroy(const char * const room);
-void handle_room_occupant_offline(const char * const room, const char * const nick,
-    const char * const show, const char * const status);
-void handle_room_destroyed(const char * const room, const char * const new_jid, const char * const password,
-    const char * const reason);
-void handle_room_kicked(const char * const room, const char * const actor, const char * const reason);
-void handle_room_occupent_kicked(const char * const room, const char * const nick, const char * const actor,
-    const char * const reason);
-void handle_room_banned(const char * const room, const char * const actor, const char * const reason);
-void handle_room_occupent_banned(const char * const room, const char * const nick, const char * const actor,
-    const char * const reason);
-void handle_group_add(const char * const contact,
-    const char * const group);
-void handle_group_remove(const char * const contact,
-    const char * const group);
-void handle_roster_remove(const char * const barejid);
-void handle_roster_add(const char * const barejid, const char * const name);
-void handle_autoping_cancel(void);
-void handle_message_error(const char * const from, const char * const type,
-    const char * const err_msg);
-void handle_presence_error(const char *from, const char * const type,
-    const char *err_msg);
-void handle_xmpp_stanza(const char * const msg);
-void handle_ping_result(const char * const from, int millis);
-void handle_ping_error_result(const char * const from, const char * const error);
-void handle_room_configure(const char * const room, DataForm *form);
-void handle_room_configuration_form_error(const char * const from, const char * const message);
-void handle_room_config_submit_result(const char * const room);
-void handle_room_config_submit_result_error(const char * const room, const char * const message);
-void handle_muc_self_online(const char * const room, const char * const nick, gboolean config_required,
-    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
-    const char * const jid, const char * const show, const char * const status);
-void handle_muc_occupant_online(const char * const room, const char * const nick, const char * const jid,
-    const char * const role, const char * const affiliation, const char * const actor, const char * const reason,
-    const char * const show_str, const char * const status_str);
-void handle_roster_update(const char * const barejid, const char * const name,
-    GSList *groups, const char * const subscription, gboolean pending_out);
-void handle_roster_received(void);
-
-#endif
diff --git a/src/tools/autocomplete.c b/src/tools/autocomplete.c
index 2623c828..7624f1df 100644
--- a/src/tools/autocomplete.c
+++ b/src/tools/autocomplete.c
@@ -1,7 +1,7 @@
 /*
  * autocomplete.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/autocomplete.h b/src/tools/autocomplete.h
index 70cd8f30..c4b94b09 100644
--- a/src/tools/autocomplete.h
+++ b/src/tools/autocomplete.h
@@ -1,7 +1,7 @@
 /*
  * autocomplete.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/history.c b/src/tools/history.c
deleted file mode 100644
index def10feb..00000000
--- a/src/tools/history.c
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * history.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 <string.h>
-
-#include <glib.h>
-
-#include "history.h"
-
-struct history_session_t {
-    GList *items;
-    GList *sess_curr;
-    GList *sess_new;
-    GList *orig_curr;
-};
-
-struct history_t {
-    GList *items;
-    guint max_size;
-    struct history_session_t session;
-};
-
-static void _replace_history_with_session(History history);
-static gboolean _adding_new(History history);
-static void _reset_session(History history);
-static gboolean _has_session(History history);
-static void _remove_first(History history);
-static void _update_current_session_item(History history, char *item);
-static void _add_to_history(History history, char *item);
-static void _remove_last_session_item(History history);
-static void _replace_current_with_original(History history);
-static void _create_session(History history);
-static void _session_previous(History history);
-static void _session_next(History history);
-
-History
-history_new(unsigned int size)
-{
-    History new_history = malloc(sizeof(struct history_t));
-    new_history->items = NULL;
-    new_history->max_size = size;
-
-    _reset_session(new_history);
-
-    return new_history;
-}
-
-void
-history_append(History history, char *item)
-{
-    char *copied = "";
-    if (item != NULL) {
-        copied = strdup(item);
-    }
-
-    if (history->items == NULL) {
-        _add_to_history(history, copied);
-        return;
-    }
-
-    if (!_has_session(history)) {
-        if (g_list_length(history->items) == history->max_size) {
-            _remove_first(history);
-        }
-
-        _add_to_history(history, copied);
-
-    } else {
-        _update_current_session_item(history, copied);
-
-        if (_adding_new(history)) {
-            if (strcmp(history->session.sess_curr->data, "") == 0) {
-                _remove_last_session_item(history);
-            }
-
-            _replace_history_with_session(history);
-
-        } else {
-            _remove_last_session_item(history);
-
-            char *new = strdup(history->session.sess_curr->data);
-            history->session.items = g_list_append(history->session.items, new);
-
-            _replace_current_with_original(history);
-            _replace_history_with_session(history);
-        }
-    }
-}
-
-char *
-history_previous(History history, char *item)
-{
-    // no history
-    if (history->items == NULL) {
-        return NULL;
-    }
-
-    char *copied = "";
-    if (item != NULL) {
-        copied = strdup(item);
-    }
-
-    if (!_has_session(history)) {
-        _create_session(history);
-
-        // add the new item
-        history->session.items = g_list_append(history->session.items, copied);
-        history->session.sess_new = g_list_last(history->session.items);
-
-        char *result = strdup(history->session.sess_curr->data);
-        return result;
-    } else {
-        _update_current_session_item(history, copied);
-        _session_previous(history);
-    }
-
-    char *result = strdup(history->session.sess_curr->data);
-    return result;
-}
-
-char *
-history_next(History history, char *item)
-{
-    // no history, or no session, return NULL
-    if ((history->items == NULL) || (history->session.items == NULL)) {
-        return NULL;
-    }
-
-    if (g_list_next(history->session.sess_curr) == NULL) {
-        return NULL;
-    }
-
-    char *copied = "";
-    if (item != NULL) {
-        copied = strdup(item);
-    }
-
-    _update_current_session_item(history, copied);
-    _session_next(history);
-
-    char *result = strdup(history->session.sess_curr->data);
-    return result;
-}
-
-static void
-_replace_history_with_session(History history)
-{
-    g_list_free(history->items);
-    history->items = g_list_copy(history->session.items);
-
-    if (g_list_length(history->items) > history->max_size) {
-        _remove_first(history);
-    }
-
-    _reset_session(history);
-}
-
-static gboolean
-_adding_new(History history)
-{
-    return (history->session.sess_curr ==
-        g_list_last(history->session.items));
-}
-
-static void
-_reset_session(History history)
-{
-    history->session.items = NULL;
-    history->session.sess_curr = NULL;
-    history->session.sess_new = NULL;
-    history->session.orig_curr = NULL;
-}
-
-static gboolean
-_has_session(History history)
-{
-    return (history->session.items != NULL);
-}
-
-static void
-_remove_first(History history)
-{
-    GList *first = g_list_first(history->items);
-    char *first_item = first->data;
-    history->items = g_list_remove(history->items, first_item);
-}
-
-static void
-_update_current_session_item(History history, char *item)
-{
-    history->session.sess_curr->data = item;
-}
-
-static void
-_add_to_history(History history, char *item)
-{
-    history->items = g_list_append(history->items, item);
-}
-
-static void
-_remove_last_session_item(History history)
-{
-    history->session.items = g_list_reverse(history->session.items);
-    GList *first = g_list_first(history->session.items);
-    history->session.items =
-        g_list_remove(history->session.items, first->data);
-    history->session.items = g_list_reverse(history->session.items);
-}
-
-static void
-_replace_current_with_original(History history)
-{
-    history->session.sess_curr->data = strdup(history->session.orig_curr->data);
-}
-
-static void
-_create_session(History history)
-{
-    history->session.items = g_list_copy(history->items);
-    history->session.sess_curr = g_list_last(history->session.items);
-    history->session.orig_curr = g_list_last(history->items);
-}
-
-static void
-_session_previous(History history)
-{
-    history->session.sess_curr =
-        g_list_previous(history->session.sess_curr);
-    if (history->session.orig_curr == NULL)
-        history->session.orig_curr = g_list_last(history->items);
-    else
-        history->session.orig_curr =
-            g_list_previous(history->session.orig_curr);
-
-    if (history->session.sess_curr == NULL) {
-        history->session.sess_curr = g_list_first(history->session.items);
-        history->session.orig_curr = g_list_first(history->items);
-    }
-}
-
-static void
-_session_next(History history)
-{
-    history->session.sess_curr = g_list_next(history->session.sess_curr);
-    history->session.orig_curr = g_list_next(history->session.orig_curr);
-
-    if (history->session.sess_curr == NULL) {
-        history->session.sess_curr = g_list_last(history->session.items);
-        history->session.orig_curr = NULL;
-    }
-}
diff --git a/src/tools/parser.c b/src/tools/parser.c
index e91b227d..d3a23264 100644
--- a/src/tools/parser.c
+++ b/src/tools/parser.c
@@ -1,7 +1,7 @@
 /*
  * parser.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -47,9 +47,9 @@
  *
  * inp - The line of input
  * min - The minimum allowed number of arguments
- * max - The maxmimum allowed number of arguments
+ * max - The maximum allowed number of arguments
  *
- * Returns - An NULL terminated array of strings representing the aguments
+ * Returns - An NULL terminated array of strings representing the arguments
  * of the command, or NULL if the validation fails.
  *
  * E.g. the following input line:
@@ -69,7 +69,7 @@ parse_args(const char * const inp, int min, int max, gboolean *result)
         return NULL;
     }
 
-    // copy and strip input of leading/trailing whitepsace
+    // copy and strip input of leading/trailing whitespace
     char *copy = strdup(inp);
     g_strstrip(copy);
 
@@ -156,7 +156,7 @@ parse_args(const char * const inp, int min, int max, gboolean *result)
         token = g_slist_next(token);
         int arg_count = 0;
 
-        while (token != NULL) {
+        while (token) {
             args[arg_count++] = strdup(token->data);
             token = g_slist_next(token);
         }
@@ -181,9 +181,9 @@ parse_args(const char * const inp, int min, int max, gboolean *result)
  *
  * inp - The line of input
  * min - The minimum allowed number of arguments
- * max - The maxmimum allowed number of arguments
+ * max - The maximum allowed number of arguments
  *
- * Returns - An NULL terminated array of strings representing the aguments
+ * Returns - An NULL terminated array of strings representing the arguments
  * of the command, or NULL if the validation fails.
  *
  * E.g. the following input line:
@@ -303,7 +303,7 @@ parse_args_with_freetext(const char * const inp, int min, int max, gboolean *res
         token = g_slist_next(token);
         int arg_count = 0;
 
-        while (token != NULL) {
+        while (token) {
             args[arg_count++] = strdup(token->data);
             token = g_slist_next(token);
         }
@@ -419,7 +419,7 @@ parse_options(gchar **args, gchar **opt_keys, gboolean *res)
         }
 
         // check if duplicate
-        if (g_list_find_custom(found_keys, args[curr], (GCompareFunc)g_strcmp0) != NULL) {
+        if (g_list_find_custom(found_keys, args[curr], (GCompareFunc)g_strcmp0)) {
             *res = FALSE;
             g_list_free(keys);
             return options;
@@ -450,7 +450,7 @@ parse_options(gchar **args, gchar **opt_keys, gboolean *res)
 void
 options_destroy(GHashTable *options)
 {
-    if (options != NULL) {
+    if (options) {
         g_hash_table_destroy(options);
     }
 }
diff --git a/src/tools/parser.h b/src/tools/parser.h
index eeb97df3..34fa55a0 100644
--- a/src/tools/parser.h
+++ b/src/tools/parser.h
@@ -1,7 +1,7 @@
 /*
  * parser.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/tinyurl.c b/src/tools/tinyurl.c
index 3addc646..92ff97b8 100644
--- a/src/tools/tinyurl.c
+++ b/src/tools/tinyurl.c
@@ -1,7 +1,7 @@
 /*
  * tinyurl.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -74,7 +74,7 @@ tinyurl_get(char *url)
 
     g_string_free(full_url, TRUE);
 
-    if (output.buffer != NULL) {
+    if (output.buffer) {
         output.buffer[output.size++] = '\0';
         return output.buffer;
     } else {
diff --git a/src/tools/tinyurl.h b/src/tools/tinyurl.h
index 9557228f..f69570c3 100644
--- a/src/tools/tinyurl.h
+++ b/src/tools/tinyurl.h
@@ -1,7 +1,7 @@
 /*
  * tinyurl.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/buffer.c b/src/ui/buffer.c
index 52397b4b..0848b60f 100644
--- a/src/ui/buffer.c
+++ b/src/ui/buffer.c
@@ -1,7 +1,7 @@
 /*
  * buffer.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -81,7 +81,7 @@ buffer_free(ProfBuff buffer)
 
 void
 buffer_push(ProfBuff buffer, const char show_char, GDateTime *time,
-    int flags, theme_item_t theme_item, const char * const from, const char * const message)
+    int flags, theme_item_t theme_item, const char * const from, const char * const message, DeliveryReceipt *receipt)
 {
     ProfBuffEntry *e = malloc(sizeof(struct prof_buff_entry_t));
     e->show_char = show_char;
@@ -90,6 +90,7 @@ buffer_push(ProfBuff buffer, const char show_char, GDateTime *time,
     e->time = time;
     e->from = strdup(from);
     e->message = strdup(message);
+    e->receipt = receipt;
 
     if (g_slist_length(buffer->entries) == BUFF_SIZE) {
         _free_entry(buffer->entries->data);
@@ -99,6 +100,24 @@ buffer_push(ProfBuff buffer, const char show_char, GDateTime *time,
     buffer->entries = g_slist_append(buffer->entries, e);
 }
 
+gboolean
+buffer_mark_received(ProfBuff buffer, const char * const id)
+{
+    GSList *entries = buffer->entries;
+    while (entries) {
+        ProfBuffEntry *entry = entries->data;
+        if (entry->receipt && g_strcmp0(entry->receipt->id, id) == 0) {
+            if (!entry->receipt->received) {
+                entry->receipt->received = TRUE;
+                return TRUE;
+            }
+        }
+        entries = g_slist_next(entries);
+    }
+
+    return FALSE;
+}
+
 ProfBuffEntry*
 buffer_yield_entry(ProfBuff buffer, int entry)
 {
@@ -112,5 +131,9 @@ _free_entry(ProfBuffEntry *entry)
     free(entry->message);
     free(entry->from);
     g_date_time_unref(entry->time);
+    if (entry->receipt) {
+        free(entry->receipt->id);
+        free(entry->receipt);
+    }
     free(entry);
 }
\ No newline at end of file
diff --git a/src/ui/buffer.h b/src/ui/buffer.h
index 34d6b04f..cad7eee0 100644
--- a/src/ui/buffer.h
+++ b/src/ui/buffer.h
@@ -1,7 +1,7 @@
 /*
  * buffer.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -40,6 +40,11 @@
 
 #include <glib.h>
 
+typedef struct delivery_receipt_t {
+    char *id;
+    gboolean received;
+} DeliveryReceipt;
+
 typedef struct prof_buff_entry_t {
     char show_char;
     GDateTime *time;
@@ -47,13 +52,18 @@ typedef struct prof_buff_entry_t {
     theme_item_t theme_item;
     char *from;
     char *message;
+    DeliveryReceipt *receipt;
 } ProfBuffEntry;
 
 typedef struct prof_buff_t *ProfBuff;
 
 ProfBuff buffer_create();
 void buffer_free(ProfBuff buffer);
-void buffer_push(ProfBuff buffer, const char show_char, GDateTime *time, int flags, theme_item_t theme_item, const char * const from, const char * const message);
+void buffer_push(ProfBuff buffer, const char show_char, GDateTime *time, int flags, theme_item_t theme_item,
+    const char * const from, const char * const message, DeliveryReceipt *receipt);
 int buffer_size(ProfBuff buffer);
 ProfBuffEntry* buffer_yield_entry(ProfBuff buffer, int entry);
+gboolean buffer_mark_received(ProfBuff buffer, const char * const id);
+
+
 #endif
diff --git a/src/ui/console.c b/src/ui/console.c
index 689f218c..895efda4 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1,7 +1,7 @@
 /*
  * console.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -67,14 +67,14 @@ void
 cons_show_time(void)
 {
     ProfWin *console = wins_get_console();
-    win_save_print(console, '-', NULL, NO_EOL, 0, "", "");
+    win_print(console, '-', NULL, NO_EOL, 0, "", "");
 }
 
 void
 cons_show_word(const char * const word)
 {
     ProfWin *console = wins_get_console();
-    win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", word);
+    win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", word);
 }
 
 void
@@ -86,7 +86,7 @@ cons_debug(const char * const msg, ...)
         va_start(arg, msg);
         GString *fmt_msg = g_string_new(NULL);
         g_string_vprintf(fmt_msg, msg, arg);
-        win_save_println(console, fmt_msg->str);
+        win_println(console, fmt_msg->str);
         g_string_free(fmt_msg, TRUE);
         va_end(arg);
     }
@@ -100,7 +100,7 @@ cons_show(const char * const msg, ...)
     va_start(arg, msg);
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, msg, arg);
-    win_save_println(console, fmt_msg->str);
+    win_println(console, fmt_msg->str);
     g_string_free(fmt_msg, TRUE);
     va_end(arg);
 }
@@ -113,7 +113,7 @@ cons_show_error(const char * const msg, ...)
     va_start(arg, msg);
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, msg, arg);
-    win_save_print(console, '-', NULL, 0, THEME_ERROR, "", fmt_msg->str);
+    win_print(console, '-', NULL, 0, THEME_ERROR, "", fmt_msg->str);
     g_string_free(fmt_msg, TRUE);
     va_end(arg);
 
@@ -126,8 +126,8 @@ cons_show_typing(const char * const barejid)
     ProfWin *console = wins_get_console();
     const char * display_usr = NULL;
     PContact contact = roster_get_contact(barejid);
-    if (contact != NULL) {
-        if (p_contact_name(contact) != NULL) {
+    if (contact) {
+        if (p_contact_name(contact)) {
             display_usr = p_contact_name(contact);
         } else {
             display_usr = barejid;
@@ -136,7 +136,7 @@ cons_show_typing(const char * const barejid)
         display_usr = barejid;
     }
 
-    win_save_vprint(console, '-', NULL, 0, THEME_TYPING, "", "!! %s is typing a message...", display_usr);
+    win_vprint(console, '-', NULL, 0, THEME_TYPING, "", "!! %s is typing a message...", display_usr);
     cons_alert();
 }
 
@@ -149,7 +149,7 @@ cons_show_incoming_message(const char * const short_from, const int win_index)
     if (ui_index == 10) {
         ui_index = 0;
     }
-    win_save_vprint(console, '-', NULL, 0, THEME_INCOMING, "", "<< incoming from %s (%d)", short_from, ui_index);
+    win_vprint(console, '-', NULL, 0, THEME_INCOMING, "", "<< incoming from %s (%d)", short_from, ui_index);
 
     cons_alert();
 }
@@ -167,23 +167,23 @@ cons_about(void)
 
         if (strcmp(PACKAGE_STATUS, "development") == 0) {
 #ifdef HAVE_GIT_VERSION
-            win_save_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+            win_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-            win_save_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %sdev", PACKAGE_VERSION);
+            win_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %sdev", PACKAGE_VERSION);
 #endif
         } else {
-            win_save_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %s", PACKAGE_VERSION);
+            win_vprint(console, '-', NULL, 0, 0, "", "Welcome to Profanity, version %s", PACKAGE_VERSION);
         }
     }
 
-    win_save_vprint(console, '-', NULL, 0, 0, "", "Copyright (C) 2012 - 2014 James Booth <%s>.", PACKAGE_BUGREPORT);
-    win_save_println(console, "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>");
-    win_save_println(console, "");
-    win_save_println(console, "This is free software; you are free to change and redistribute it.");
-    win_save_println(console, "There is NO WARRANTY, to the extent permitted by law.");
-    win_save_println(console, "");
-    win_save_println(console, "Type '/help' to show complete help.");
-    win_save_println(console, "");
+    win_vprint(console, '-', NULL, 0, 0, "", "Copyright (C) 2012 - 2015 James Booth <%s>.", PACKAGE_BUGREPORT);
+    win_println(console, "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>");
+    win_println(console, "");
+    win_println(console, "This is free software; you are free to change and redistribute it.");
+    win_println(console, "There is NO WARRANTY, to the extent permitted by law.");
+    win_println(console, "");
+    win_println(console, "Type '/help' to show complete help.");
+    win_println(console, "");
 
     if (prefs_get_boolean(PREF_VERCHECK)) {
         cons_check_version(FALSE);
@@ -200,18 +200,18 @@ cons_check_version(gboolean not_available_msg)
     ProfWin *console = wins_get_console();
     char *latest_release = release_get_latest();
 
-    if (latest_release != NULL) {
+    if (latest_release) {
         gboolean relase_valid = g_regex_match_simple("^\\d+\\.\\d+\\.\\d+$", latest_release, 0, 0);
 
         if (relase_valid) {
             if (release_is_new(latest_release)) {
-                win_save_vprint(console, '-', NULL, 0, 0, "", "A new version of Profanity is available: %s", latest_release);
-                win_save_println(console, "Check <http://www.profanity.im> for details.");
-                win_save_println(console, "");
+                win_vprint(console, '-', NULL, 0, 0, "", "A new version of Profanity is available: %s", latest_release);
+                win_println(console, "Check <http://www.profanity.im> for details.");
+                win_println(console, "");
             } else {
                 if (not_available_msg) {
-                    win_save_println(console, "No new version available.");
-                    win_save_println(console, "");
+                    win_println(console, "No new version available.");
+                    win_println(console, "");
                 }
             }
 
@@ -225,16 +225,16 @@ void
 cons_show_login_success(ProfAccount *account)
 {
     ProfWin *console = wins_get_console();
-    win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "%s logged in successfully, ", account->jid);
+    win_vprint(console, '-', NULL, NO_EOL, 0, "", "%s logged in successfully, ", account->jid);
 
     resource_presence_t presence = accounts_get_login_presence(account->name);
     const char *presence_str = string_from_resource_presence(presence);
 
     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
-    win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", presence_str);
-    win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " (priority %d)",
+    win_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", presence_str);
+    win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " (priority %d)",
         accounts_get_priority_for_presence_type(account->name, presence));
-    win_save_print(console, '-', NULL, NO_DATE, 0, "", ".");
+    win_print(console, '-', NULL, NO_DATE, 0, "", ".");
     cons_alert();
 }
 
@@ -247,10 +247,11 @@ cons_show_wins(void)
     GSList *window_strings = wins_create_summary();
 
     GSList *curr = window_strings;
-    while (curr != NULL) {
-        win_save_println(console, curr->data);
+    while (curr) {
+        win_println(console, curr->data);
         curr = g_slist_next(curr);
     }
+    g_slist_free_full(window_strings, free);
 
     cons_show("");
     cons_alert();
@@ -264,7 +265,7 @@ cons_show_room_invites(GSList *invites)
         cons_show("No outstanding chat room invites.");
     } else {
         cons_show("Chat room invites, use /join or /decline commands:");
-        while (invites != NULL) {
+        while (invites) {
             cons_show("  %s", invites->data);
             invites = g_slist_next(invites);
         }
@@ -293,53 +294,53 @@ cons_show_caps(const char * const fulljid, resource_presence_t presence)
         const char *resource_presence = string_from_resource_presence(presence);
 
         theme_item_t presence_colour = theme_main_presence_attrs(resource_presence);
-        win_save_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "%s", fulljid);
-        win_save_print(console, '-', NULL, NO_DATE, 0, "", ":");
+        win_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "%s", fulljid);
+        win_print(console, '-', NULL, NO_DATE, 0, "", ":");
 
         // show identity
-        if ((caps->category != NULL) || (caps->type != NULL) || (caps->name != NULL)) {
-            win_save_print(console, '-', NULL, NO_EOL, 0, "", "Identity: ");
-            if (caps->name != NULL) {
-                win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
-                if ((caps->category != NULL) || (caps->type != NULL)) {
-                    win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+        if (caps->category || caps->type || caps->name) {
+            win_print(console, '-', NULL, NO_EOL, 0, "", "Identity: ");
+            if (caps->name) {
+                win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
+                if (caps->category || caps->type) {
+                    win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                 }
             }
-            if (caps->type != NULL) {
-                win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
-                if (caps->category != NULL) {
-                    win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+            if (caps->type) {
+                win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
+                if (caps->category) {
+                    win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                 }
             }
-            if (caps->category != NULL) {
-                win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
+            if (caps->category) {
+                win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
             }
-            win_save_newline(console);
+            win_newline(console);
         }
-        if (caps->software != NULL) {
-            win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "Software: %s", caps->software);
+        if (caps->software) {
+            win_vprint(console, '-', NULL, NO_EOL, 0, "", "Software: %s", caps->software);
         }
-        if (caps->software_version != NULL) {
-            win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
+        if (caps->software_version) {
+            win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
         }
-        if ((caps->software != NULL) || (caps->software_version != NULL)) {
-            win_save_newline(console);
+        if (caps->software || caps->software_version) {
+            win_newline(console);
         }
-        if (caps->os != NULL) {
-            win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "OS: %s", caps->os);
+        if (caps->os) {
+            win_vprint(console, '-', NULL, NO_EOL, 0, "", "OS: %s", caps->os);
         }
-        if (caps->os_version != NULL) {
-            win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
+        if (caps->os_version) {
+            win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
         }
-        if ((caps->os != NULL) || (caps->os_version != NULL)) {
-            win_save_newline(console);
+        if (caps->os || caps->os_version) {
+            win_newline(console);
         }
 
-        if (caps->features != NULL) {
-            win_save_println(console, "Features:");
+        if (caps->features) {
+            win_println(console, "Features:");
             GSList *feature = caps->features;
-            while (feature != NULL) {
-                win_save_vprint(console, '-', NULL, 0, 0, "", " %s", feature->data);
+            while (feature) {
+                win_vprint(console, '-', NULL, 0, 0, "", " %s", feature->data);
                 feature = g_slist_next(feature);
             }
         }
@@ -357,19 +358,19 @@ cons_show_software_version(const char * const jid, const char * const  presence,
     const char * const name, const char * const version, const char * const os)
 {
     ProfWin *console = wins_get_console();
-    if ((name != NULL) || (version != NULL) || (os != NULL)) {
+    if (name || version || os) {
         cons_show("");
         theme_item_t presence_colour = theme_main_presence_attrs(presence);
-        win_save_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "%s", jid);
-        win_save_print(console, '-', NULL, NO_DATE, 0, "", ":");
+        win_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "%s", jid);
+        win_print(console, '-', NULL, NO_DATE, 0, "", ":");
     }
-    if (name != NULL) {
+    if (name) {
         cons_show("Name    : %s", name);
     }
-    if (version != NULL) {
+    if (version) {
         cons_show("Version : %s", version);
     }
-    if (os != NULL) {
+    if (os) {
         cons_show("OS      : %s", os);
     }
 
@@ -385,7 +386,7 @@ cons_show_received_subs(void)
     } else {
         cons_show("Outstanding subscription requests from:",
             g_slist_length(received));
-        while (received != NULL) {
+        while (received) {
             cons_show("  %s", received->data);
             received = g_slist_next(received);
         }
@@ -402,17 +403,18 @@ cons_show_sent_subs(void)
         GSList *contacts = roster_get_contacts();
         PContact contact = NULL;
         cons_show("Awaiting subscription responses from:");
-        while (contacts != NULL) {
-            contact = (PContact) contacts->data;
+        GSList *curr = contacts;
+        while (curr) {
+            contact = (PContact) curr->data;
             if (p_contact_pending_out(contact)) {
                 cons_show("  %s", p_contact_barejid(contact));
             }
-            contacts = g_slist_next(contacts);
+            curr = g_slist_next(curr);
         }
+        g_slist_free(contacts);
     } else {
         cons_show("No pending requests sent.");
     }
-
     cons_alert();
 }
 
@@ -420,15 +422,15 @@ void
 cons_show_room_list(GSList *rooms, const char * const conference_node)
 {
     ProfWin *console = wins_get_console();
-    if ((rooms != NULL) && (g_slist_length(rooms) > 0)) {
+    if (rooms && (g_slist_length(rooms) > 0)) {
         cons_show("Chat rooms at %s:", conference_node);
-        while (rooms != NULL) {
+        while (rooms) {
             DiscoItem *room = rooms->data;
-            win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "  %s", room->jid);
-            if (room->name != NULL) {
-                win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", (%s)", room->name);
+            win_vprint(console, '-', NULL, NO_EOL, 0, "", "  %s", room->jid);
+            if (room->name) {
+                win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", (%s)", room->name);
             }
-            win_save_newline(console);
+            win_newline(console);
             rooms = g_slist_next(rooms);
         }
     } else {
@@ -450,7 +452,7 @@ cons_show_bookmarks(const GList *list)
         cons_show("");
         cons_show("Bookmarks:");
 
-        while (list != NULL) {
+        while (list) {
             Bookmark *item = list->data;
 
             theme_item_t presence_colour = THEME_TEXT;
@@ -458,24 +460,24 @@ cons_show_bookmarks(const GList *list)
             if (muc_active(item->jid)) {
                 presence_colour = THEME_ONLINE;
             }
-            win_save_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "  %s", item->jid);
-            if (item->nick != NULL) {
-                win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "/%s", item->nick);
+            win_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "  %s", item->jid);
+            if (item->nick) {
+                win_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "/%s", item->nick);
             }
             if (item->autojoin) {
-                win_save_print(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (autojoin)");
+                win_print(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (autojoin)");
             }
-            if (item->password != NULL) {
-                win_save_print(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (private)");
+            if (item->password) {
+                win_print(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (private)");
             }
             if (muc_active(item->jid)) {
                 ProfWin *roomwin = (ProfWin*)wins_get_muc(item->jid);
-                if (roomwin != NULL) {
+                if (roomwin) {
                     int num = wins_get_num(roomwin);
-                    win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (%d)", num);
+                    win_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (%d)", num);
                 }
             }
-            win_save_newline(console);
+            win_newline(console);
             list = g_list_next(list);
         }
     }
@@ -485,26 +487,26 @@ cons_show_bookmarks(const GList *list)
 void
 cons_show_disco_info(const char *jid, GSList *identities, GSList *features)
 {
-    if (((identities != NULL) && (g_slist_length(identities) > 0)) ||
-        ((features != NULL) && (g_slist_length(features) > 0))) {
+    if ((identities && (g_slist_length(identities) > 0)) ||
+        (features && (g_slist_length(features) > 0))) {
         cons_show("");
         cons_show("Service disovery info for %s", jid);
 
-        if (identities != NULL) {
+        if (identities) {
             cons_show("  Identities");
         }
-        while (identities != NULL) {
+        while (identities) {
             DiscoIdentity *identity = identities->data;  // anme trpe, cat
             GString *identity_str = g_string_new("    ");
-            if (identity->name != NULL) {
+            if (identity->name) {
                 identity_str = g_string_append(identity_str, identity->name);
                 identity_str = g_string_append(identity_str, " ");
             }
-            if (identity->type != NULL) {
+            if (identity->type) {
                 identity_str = g_string_append(identity_str, identity->type);
                 identity_str = g_string_append(identity_str, " ");
             }
-            if (identity->category != NULL) {
+            if (identity->category) {
                 identity_str = g_string_append(identity_str, identity->category);
             }
             cons_show(identity_str->str);
@@ -512,10 +514,10 @@ cons_show_disco_info(const char *jid, GSList *identities, GSList *features)
             identities = g_slist_next(identities);
         }
 
-        if (features != NULL) {
+        if (features) {
             cons_show("  Features:");
         }
-        while (features != NULL) {
+        while (features) {
             cons_show("    %s", features->data);
             features = g_slist_next(features);
         }
@@ -528,16 +530,16 @@ void
 cons_show_disco_items(GSList *items, const char * const jid)
 {
     ProfWin *console = wins_get_console();
-    if ((items != NULL) && (g_slist_length(items) > 0)) {
+    if (items && (g_slist_length(items) > 0)) {
         cons_show("");
         cons_show("Service discovery items for %s:", jid);
-        while (items != NULL) {
+        while (items) {
             DiscoItem *item = items->data;
-            win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "  %s", item->jid);
-            if (item->name != NULL) {
-                win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", (%s)", item->name);
+            win_vprint(console, '-', NULL, NO_EOL, 0, "", "  %s", item->jid);
+            if (item->name) {
+                win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", (%s)", item->name);
             }
-            win_save_vprint(console, '-', NULL, NO_DATE, 0, "", "");
+            win_vprint(console, '-', NULL, NO_DATE, 0, "", "");
             items = g_slist_next(items);
         }
     } else {
@@ -554,7 +556,7 @@ cons_show_status(const char * const barejid)
     ProfWin *console = wins_get_console();
     PContact pcontact = roster_get_contact(barejid);
 
-    if (pcontact != NULL) {
+    if (pcontact) {
         win_show_contact(console, pcontact);
     } else {
         cons_show("No such contact \"%s\" in roster.", barejid);
@@ -569,8 +571,8 @@ cons_show_room_invite(const char * const invitor, const char * const room,
 {
     char *display_from = NULL;
     PContact contact = roster_get_contact(invitor);
-    if (contact != NULL) {
-        if (p_contact_name(contact) != NULL) {
+    if (contact) {
+        if (p_contact_name(contact)) {
             display_from = strdup(p_contact_name(contact));
         } else {
             display_from = strdup(invitor);
@@ -584,7 +586,7 @@ cons_show_room_invite(const char * const invitor, const char * const room,
     cons_show("  From   : %s", display_from);
     cons_show("  Room   : %s", room);
 
-    if (reason != NULL) {
+    if (reason) {
         cons_show("  Message: %s", reason);
     }
 
@@ -612,7 +614,7 @@ cons_show_account_list(gchar **accounts)
                     (g_strcmp0(jabber_get_account_name(), accounts[i]) == 0)) {
                 resource_presence_t presence = accounts_get_last_presence(accounts[i]);
                 theme_item_t presence_colour = theme_main_presence_attrs(string_from_resource_presence(presence));
-                win_save_vprint(console, '-', NULL, 0, presence_colour, "", "%s", accounts[i]);
+                win_vprint(console, '-', NULL, 0, presence_colour, "", "%s", accounts[i]);
             } else {
                 cons_show(accounts[i]);
             }
@@ -671,9 +673,9 @@ cons_show_account(ProfAccount *account)
     if (g_list_length(account->otr_manual) > 0) {
         GString *manual = g_string_new("OTR manual        : ");
         GList *curr = account->otr_manual;
-        while (curr != NULL) {
+        while (curr) {
             g_string_append(manual, curr->data);
-            if (curr->next != NULL) {
+            if (curr->next) {
                 g_string_append(manual, ", ");
             }
             curr = curr->next;
@@ -684,9 +686,9 @@ cons_show_account(ProfAccount *account)
     if (g_list_length(account->otr_opportunistic) > 0) {
         GString *opportunistic = g_string_new("OTR opportunistic : ");
         GList *curr = account->otr_opportunistic;
-        while (curr != NULL) {
+        while (curr) {
             g_string_append(opportunistic, curr->data);
-            if (curr->next != NULL) {
+            if (curr->next) {
                 g_string_append(opportunistic, ", ");
             }
             curr = curr->next;
@@ -697,9 +699,9 @@ cons_show_account(ProfAccount *account)
     if (g_list_length(account->otr_always) > 0) {
         GString *always = g_string_new("OTR always        : ");
         GList *curr = account->otr_always;
-        while (curr != NULL) {
+        while (curr) {
             g_string_append(always, curr->data);
-            if (curr->next != NULL) {
+            if (curr->next) {
                 g_string_append(always, ", ");
             }
             curr = curr->next;
@@ -717,76 +719,81 @@ cons_show_account(ProfAccount *account)
         GList *resources = jabber_get_available_resources();
         GList *ordered_resources = NULL;
 
-        if (resources != NULL) {
-            win_save_println(console, "Resources:");
+        GList *curr = resources;
+        if (curr) {
+            win_println(console, "Resources:");
 
-            // sort in order of availabiltiy
-            while (resources != NULL) {
-                Resource *resource = resources->data;
+            // sort in order of availability
+            while (curr) {
+                Resource *resource = curr->data;
                 ordered_resources = g_list_insert_sorted(ordered_resources,
                     resource, (GCompareFunc)resource_compare_availability);
-                resources = g_list_next(resources);
+                curr = g_list_next(curr);
             }
         }
 
-        while (ordered_resources != NULL) {
-            Resource *resource = ordered_resources->data;
+        g_list_free(resources);
+
+        curr = ordered_resources;
+        while (curr) {
+            Resource *resource = curr->data;
             const char *resource_presence = string_from_resource_presence(resource->presence);
             theme_item_t presence_colour = theme_main_presence_attrs(resource_presence);
-            win_save_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
+            win_vprint(console, '-', NULL, NO_EOL, presence_colour, "", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
 
-            if (resource->status != NULL) {
-                win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", resource->status);
+            if (resource->status) {
+                win_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", resource->status);
             }
-            win_save_vprint(console, '-', NULL, NO_DATE, 0, "", "");
+            win_vprint(console, '-', NULL, NO_DATE, 0, "", "");
             Jid *jidp = jid_create_from_bare_and_resource(account->jid, resource->name);
             Capabilities *caps = caps_lookup(jidp->fulljid);
             jid_destroy(jidp);
 
-            if (caps != NULL) {
+            if (caps) {
                 // show identity
-                if ((caps->category != NULL) || (caps->type != NULL) || (caps->name != NULL)) {
-                    win_save_print(console, '-', NULL, NO_EOL, 0, "", "    Identity: ");
-                    if (caps->name != NULL) {
-                        win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
-                        if ((caps->category != NULL) || (caps->type != NULL)) {
-                            win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+                if (caps->category || caps->type || caps->name) {
+                    win_print(console, '-', NULL, NO_EOL, 0, "", "    Identity: ");
+                    if (caps->name) {
+                        win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
+                        if (caps->category || caps->type) {
+                            win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                         }
                     }
-                    if (caps->type != NULL) {
-                        win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
-                        if (caps->category != NULL) {
-                            win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+                    if (caps->type) {
+                        win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
+                        if (caps->category) {
+                            win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                         }
                     }
-                    if (caps->category != NULL) {
-                        win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
+                    if (caps->category) {
+                        win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
                     }
-                    win_save_newline(console);
+                    win_newline(console);
                 }
-                if (caps->software != NULL) {
-                    win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "    Software: %s", caps->software);
+                if (caps->software) {
+                    win_vprint(console, '-', NULL, NO_EOL, 0, "", "    Software: %s", caps->software);
                 }
-                if (caps->software_version != NULL) {
-                    win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
+                if (caps->software_version) {
+                    win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
                 }
-                if ((caps->software != NULL) || (caps->software_version != NULL)) {
-                    win_save_newline(console);
+                if (caps->software || caps->software_version) {
+                    win_newline(console);
                 }
-                if (caps->os != NULL) {
-                    win_save_vprint(console, '-', NULL, NO_EOL, 0, "", "    OS: %s", caps->os);
+                if (caps->os) {
+                    win_vprint(console, '-', NULL, NO_EOL, 0, "", "    OS: %s", caps->os);
                 }
-                if (caps->os_version != NULL) {
-                    win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
+                if (caps->os_version) {
+                    win_vprint(console, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
                 }
-                if ((caps->os != NULL) || (caps->os_version != NULL)) {
-                    win_save_newline(console);
+                if (caps->os || caps->os_version) {
+                    win_newline(console);
                 }
                 caps_destroy(caps);
             }
 
-            ordered_resources = g_list_next(ordered_resources);
+            curr = g_list_next(curr);
         }
+        g_list_free(ordered_resources);
     }
 
     cons_alert();
@@ -801,10 +808,10 @@ cons_show_aliases(GList *aliases)
     }
 
     GList *curr = aliases;
-    if (curr != NULL) {
+    if (curr) {
         cons_show("Command aliases:");
     }
-    while (curr != NULL) {
+    while (curr) {
         ProfAlias *alias = curr->data;
         cons_show("  /%s -> %s", alias->name, alias->value);
         curr = g_list_next(curr);
@@ -850,9 +857,9 @@ cons_resource_setting(void)
     else
         cons_show("Resource title (/resource)    : OFF");
     if (prefs_get_boolean(PREF_RESOURCE_MESSAGE))
-        cons_show("Message title (/resource)     : ON");
+        cons_show("Resource message (/resource)  : ON");
     else
-        cons_show("Message title (/resource)     : OFF");
+        cons_show("Resource message (/resource)  : OFF");
 }
 
 void
@@ -899,6 +906,11 @@ cons_occupants_setting(void)
     else
         cons_show("Occupants (/occupants)        : hide");
 
+    if (prefs_get_boolean(PREF_OCCUPANTS_JID))
+        cons_show("Occupant jids (/occupants)    : show");
+    else
+        cons_show("Occupant jids (/occupants)    : hide");
+
     int size = prefs_get_occupants_size();
     cons_show("Occupants size (/occupants)   : %d", size);
 }
@@ -907,7 +919,7 @@ void
 cons_autoconnect_setting(void)
 {
     char *pref_connect_account = prefs_get_string(PREF_CONNECT_ACCOUNT);
-    if (pref_connect_account != NULL)
+    if (pref_connect_account)
         cons_show("Autoconnect (/autoconnect)      : %s", pref_connect_account);
     else
         cons_show("Autoconnect (/autoconnect)      : OFF");
@@ -925,6 +937,16 @@ cons_time_setting(void)
         cons_show("Time (/time)                  : %s", pref_time);
 
     prefs_free_string(pref_time);
+
+    char *pref_time_statusbar = prefs_get_string(PREF_TIME_STATUSBAR);
+    if (g_strcmp0(pref_time_statusbar, "minutes") == 0)
+        cons_show("Time statusbar (/time)        : minutes");
+    else if (g_strcmp0(pref_time_statusbar, "off") == 0)
+        cons_show("Time statusbar (/time)        : OFF");
+    else
+        cons_show("Time statusbar (/time)        : seconds");
+
+    prefs_free_string(pref_time_statusbar);
 }
 
 void
@@ -994,6 +1016,9 @@ cons_roster_setting(void)
     else
         cons_show("Roster resource (/roster)     : hide");
 
+    char *by = prefs_get_string(PREF_ROSTER_BY);
+    cons_show("Roster by (/roster)           : %s", by);
+
     int size = prefs_get_roster_size();
     cons_show("Roster size (/roster)         : %d", size);
 }
@@ -1120,27 +1145,27 @@ void
 cons_states_setting(void)
 {
     if (prefs_get_boolean(PREF_STATES))
-        cons_show("Send chat states (/states) : ON");
+        cons_show("Send chat states (/states)    : ON");
     else
-        cons_show("Send chat states (/states) : OFF");
+        cons_show("Send chat states (/states)    : OFF");
 }
 
 void
 cons_outtype_setting(void)
 {
     if (prefs_get_boolean(PREF_OUTTYPE))
-        cons_show("Send composing (/outtype)  : ON");
+        cons_show("Send composing (/outtype)     : ON");
     else
-        cons_show("Send composing (/outtype)  : OFF");
+        cons_show("Send composing (/outtype)     : OFF");
 }
 
 void
 cons_intype_setting(void)
 {
     if (prefs_get_boolean(PREF_INTYPE))
-        cons_show("Show typing (/intype)      : ON");
+        cons_show("Show typing (/intype)         : ON");
     else
-        cons_show("Show typing (/intype)      : OFF");
+        cons_show("Show typing (/intype)         : OFF");
 }
 
 void
@@ -1148,11 +1173,11 @@ cons_gone_setting(void)
 {
     gint gone_time = prefs_get_gone();
     if (gone_time == 0) {
-        cons_show("Leave conversation (/gone) : OFF");
+        cons_show("Leave conversation (/gone)    : OFF");
     } else if (gone_time == 1) {
-        cons_show("Leave conversation (/gone) : 1 minute");
+        cons_show("Leave conversation (/gone)    : 1 minute");
     } else {
-        cons_show("Leave conversation (/gone) : %d minutes", gone_time);
+        cons_show("Leave conversation (/gone)    : %d minutes", gone_time);
     }
 }
 
@@ -1160,9 +1185,33 @@ void
 cons_history_setting(void)
 {
     if (prefs_get_boolean(PREF_HISTORY))
-        cons_show("Chat history (/history)    : ON");
+        cons_show("Chat history (/history)       : ON");
     else
-        cons_show("Chat history (/history)    : OFF");
+        cons_show("Chat history (/history)       : OFF");
+}
+
+void
+cons_carbons_setting(void)
+{
+    if (prefs_get_boolean(PREF_CARBONS))
+        cons_show("Message carbons (/carbons)    : ON");
+    else
+        cons_show("Message carbons (/carbons)    : OFF");
+}
+
+void
+cons_receipts_setting(void)
+{
+    if (prefs_get_boolean(PREF_RECEIPTS_REQUEST))
+        cons_show("Request receipts (/receipts)  : ON");
+    else
+        cons_show("Request receipts (/receipts)  : OFF");
+
+    if (prefs_get_boolean(PREF_RECEIPTS_SEND))
+        cons_show("Send receipts (/receipts)     : ON");
+    else
+        cons_show("Send receipts (/receipts)     : OFF");
+
 }
 
 void
@@ -1175,6 +1224,8 @@ cons_show_chat_prefs(void)
     cons_intype_setting();
     cons_gone_setting();
     cons_history_setting();
+    cons_carbons_setting();
+    cons_receipts_setting();
 
     cons_alert();
 }
@@ -1359,7 +1410,7 @@ cons_show_themes(GSList *themes)
         cons_show("No available themes.");
     } else {
         cons_show("Available themes:");
-        while (themes != NULL) {
+        while (themes) {
             cons_show(themes->data);
             themes = g_slist_next(themes);
         }
@@ -1400,7 +1451,7 @@ cons_help(void)
     cons_show("/help basic      - List basic commands for getting started.");
     cons_show("/help chatting   - List chat commands.");
     cons_show("/help groupchat  - List groupchat commands.");
-    cons_show("/help presence   - List commands to change presence.");
+    cons_show("/help presences  - List commands to change presence.");
     cons_show("/help contacts   - List commands for manipulating your roster.");
     cons_show("/help service    - List service discovery commands.");
     cons_show("/help settings   - List commands for changing settings.");
@@ -1417,27 +1468,10 @@ cons_navigation_help(void)
     cons_show("");
     cons_show("Navigation:");
     cons_show("");
-    cons_show("Alt-1                            : This console window.");
-    cons_show("F1                               : This console window.");
-    cons_show("Alt-2..Alt-0                     : Chat windows.");
-    cons_show("F2..F10                          : Chat windows.");
+    cons_show("Alt-1..Alt-0, F1..F10            : Choose window.");
     cons_show("Alt-LEFT, Alt-RIGHT              : Previous/next chat window");
-    cons_show("UP, DOWN                         : Navigate input history.");
-    cons_show("Ctrl-n, Ctrl-p                   : Navigate input history.");
-    cons_show("LEFT, RIGHT, HOME, END           : Move cursor.");
-    cons_show("Ctrl-b, Ctrl-f, Ctrl-a, Ctrl-e   : Move cursor.");
-    cons_show("Ctrl-LEFT, Ctrl-RIGHT            : Jump word.");
-    cons_show("Ctrl-w                           : Delete previous word.");
-    cons_show("Alt-Backspace                    : Delete previous word.");
-    cons_show("Backspace                        : Delete previous character.");
-    cons_show("DEL                              : Delete next character.");
-    cons_show("Ctrl-d                           : Delete next character.");
-    cons_show("ESC                              : Clear current input.");
-    cons_show("Ctrl-u                           : Delete all previous characters.");
-    cons_show("TAB                              : Autocomplete.");
-    cons_show("PAGE UP, PAGE DOWN               : Page the main window.");
-    cons_show("Shift-UP, Shift-DOWN             : Page occupants/roster panel.");
-    cons_show("Ctrl-UP, Ctrl-DOWN               : Page occupants/roster panel.");
+    cons_show("PAGEUP, PAGEDOWN                 : Page the main window.");
+    cons_show("Alt-PAGEUP, Alt-PAGEDOWN         : Page occupants/roster panel.");
     cons_show("");
 
     cons_alert();
@@ -1448,7 +1482,7 @@ cons_show_roster_group(const char * const group, GSList *list)
 {
     cons_show("");
 
-    if (list != NULL) {
+    if (list) {
         cons_show("%s:", group);
     } else {
         cons_show("No group named %s exists.", group);
@@ -1537,22 +1571,22 @@ cons_theme_colours(void)
 
     ProfWin *console = wins_get_console();
     cons_show("Theme colours:");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_WHITE, "",         " white   ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_WHITE_BOLD, "",   " bold_white");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_GREEN, "",         " green   ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_GREEN_BOLD, "",   " bold_green");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_RED, "",           " red     ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_RED_BOLD, "",     " bold_red");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_YELLOW, "",        " yellow  ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_YELLOW_BOLD, "",  " bold_yellow");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_BLUE, "",          " blue    ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_BLUE_BOLD, "",    " bold_blue");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_CYAN, "",          " cyan    ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_CYAN_BOLD, "",    " bold_cyan");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_MAGENTA, "",       " magenta ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_MAGENTA_BOLD, "", " bold_magenta");
-    win_save_print(console, '-', NULL, NO_EOL, THEME_BLACK, "",         " black   ");
-    win_save_print(console, '-', NULL, NO_DATE, THEME_BLACK_BOLD, "",   " bold_black");
+    win_print(console, '-', NULL, NO_EOL, THEME_WHITE, "",         " white   ");
+    win_print(console, '-', NULL, NO_DATE, THEME_WHITE_BOLD, "",   " bold_white");
+    win_print(console, '-', NULL, NO_EOL, THEME_GREEN, "",         " green   ");
+    win_print(console, '-', NULL, NO_DATE, THEME_GREEN_BOLD, "",   " bold_green");
+    win_print(console, '-', NULL, NO_EOL, THEME_RED, "",           " red     ");
+    win_print(console, '-', NULL, NO_DATE, THEME_RED_BOLD, "",     " bold_red");
+    win_print(console, '-', NULL, NO_EOL, THEME_YELLOW, "",        " yellow  ");
+    win_print(console, '-', NULL, NO_DATE, THEME_YELLOW_BOLD, "",  " bold_yellow");
+    win_print(console, '-', NULL, NO_EOL, THEME_BLUE, "",          " blue    ");
+    win_print(console, '-', NULL, NO_DATE, THEME_BLUE_BOLD, "",    " bold_blue");
+    win_print(console, '-', NULL, NO_EOL, THEME_CYAN, "",          " cyan    ");
+    win_print(console, '-', NULL, NO_DATE, THEME_CYAN_BOLD, "",    " bold_cyan");
+    win_print(console, '-', NULL, NO_EOL, THEME_MAGENTA, "",       " magenta ");
+    win_print(console, '-', NULL, NO_DATE, THEME_MAGENTA_BOLD, "", " bold_magenta");
+    win_print(console, '-', NULL, NO_EOL, THEME_BLACK, "",         " black   ");
+    win_print(console, '-', NULL, NO_DATE, THEME_BLACK_BOLD, "",   " bold_black");
     cons_show("");
 }
 
@@ -1560,25 +1594,25 @@ static void
 _cons_splash_logo(void)
 {
     ProfWin *console = wins_get_console();
-    win_save_println(console, "Welcome to");
+    win_println(console, "Welcome to");
 
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "                   ___            _           ");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "                  / __)          (_)_         ");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", " ____   ____ ___ | |__ ____ ____  _| |_ _   _ ");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "|  _ \\ / ___) _ \\|  __) _  |  _ \\| |  _) | | |");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "| | | | |  | |_| | | ( ( | | | | | | |_| |_| |");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "| ||_/|_|   \\___/|_|  \\_||_|_| |_|_|\\___)__  |");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "|_|                                    (____/ ");
-    win_save_print(console, '-', NULL, 0, THEME_SPLASH, "", "");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "                   ___            _           ");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "                  / __)          (_)_         ");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", " ____   ____ ___ | |__ ____ ____  _| |_ _   _ ");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "|  _ \\ / ___) _ \\|  __) _  |  _ \\| |  _) | | |");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "| | | | |  | |_| | | ( ( | | | | | | |_| |_| |");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "| ||_/|_|   \\___/|_|  \\_||_|_| |_|_|\\___)__  |");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "|_|                                    (____/ ");
+    win_print(console, '-', NULL, 0, THEME_SPLASH, "", "");
 
     if (strcmp(PACKAGE_STATUS, "development") == 0) {
 #ifdef HAVE_GIT_VERSION
-        win_save_vprint(console, '-', NULL, 0, 0, "", "Version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
+        win_vprint(console, '-', NULL, 0, 0, "", "Version %sdev.%s.%s", PACKAGE_VERSION, PROF_GIT_BRANCH, PROF_GIT_REVISION);
 #else
-        win_save_vprint(console, '-', NULL, 0, 0, "", "Version %sdev", PACKAGE_VERSION);
+        win_vprint(console, '-', NULL, 0, 0, "", "Version %sdev", PACKAGE_VERSION);
 #endif
     } else {
-        win_save_vprint(console, '-', NULL, 0, 0, "", "Version %s", PACKAGE_VERSION);
+        win_vprint(console, '-', NULL, 0, 0, "", "Version %s", PACKAGE_VERSION);
     }
 }
 
@@ -1592,7 +1626,7 @@ _show_roster_contacts(GSList *list, gboolean show_groups)
         PContact contact = curr->data;
         GString *title = g_string_new("  ");
         title = g_string_append(title, p_contact_barejid(contact));
-        if (p_contact_name(contact) != NULL) {
+        if (p_contact_name(contact)) {
             title = g_string_append(title, " (");
             title = g_string_append(title, p_contact_name(contact));
             title = g_string_append(title, ")");
@@ -1605,11 +1639,11 @@ _show_roster_contacts(GSList *list, gboolean show_groups)
         } else {
             presence_colour = theme_main_presence_attrs("offline");
         }
-        win_save_vprint(console, '-', NULL, NO_EOL, presence_colour, "", title->str);
+        win_vprint(console, '-', NULL, NO_EOL, presence_colour, "", title->str);
 
         g_string_free(title, TRUE);
 
-        win_save_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " - ");
+        win_print(console, '-', NULL, NO_DATE | NO_EOL, 0, "", " - ");
         GString *sub = g_string_new("");
         sub = g_string_append(sub, p_contact_subscription(contact));
         if (p_contact_pending_out(contact)) {
@@ -1625,28 +1659,28 @@ _show_roster_contacts(GSList *list, gboolean show_groups)
         }
 
         if (show_groups) {
-            win_save_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", sub->str);
+            win_vprint(console, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", sub->str);
         } else {
-            win_save_vprint(console, '-', NULL, NO_DATE, presence_colour, "", "%s", sub->str);
+            win_vprint(console, '-', NULL, NO_DATE, presence_colour, "", "%s", sub->str);
         }
 
         g_string_free(sub, TRUE);
 
         if (show_groups) {
             GSList *groups = p_contact_groups(contact);
-            if (groups != NULL) {
+            if (groups) {
                 GString *groups_str = g_string_new(" - ");
-                while (groups != NULL) {
+                while (groups) {
                     g_string_append(groups_str, groups->data);
-                    if (g_slist_next(groups) != NULL) {
+                    if (g_slist_next(groups)) {
                         g_string_append(groups_str, ", ");
                     }
                     groups = g_slist_next(groups);
                 }
-                win_save_vprint(console, '-', NULL, NO_DATE, 0, "", "%s", groups_str->str);
+                win_vprint(console, '-', NULL, NO_DATE, 0, "", "%s", groups_str->str);
                 g_string_free(groups_str, TRUE);
             } else {
-                 win_save_print(console, '-', NULL, NO_DATE, 0, "", " ");
+                 win_print(console, '-', NULL, NO_DATE, 0, "", " ");
             }
         }
 
diff --git a/src/ui/core.c b/src/ui/core.c
index 85d5748a..e7059ef0 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -1,7 +1,7 @@
 /*
  * core.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -41,6 +41,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
 #ifdef HAVE_LIBXSS
 #include <X11/extensions/scrnsaver.h>
 #endif
@@ -71,19 +74,22 @@
 #include "ui/window.h"
 #include "ui/windows.h"
 #include "xmpp/xmpp.h"
+#include "event/ui_events.h"
 
 static char *win_title;
 
 static int inp_size;
 
+static gboolean perform_resize = FALSE;
+
 #ifdef HAVE_LIBXSS
 static Display *display;
 #endif
 
 static GTimer *ui_idle_time;
 
-static void _win_handle_switch(const wint_t ch);
-static void _win_show_history(int win_index, const char * const contact);
+//static void _win_handle_switch(const wint_t ch);
+static void _win_show_history(ProfChatWin *chatwin, const char * const contact);
 static void _ui_draw_term_title(void);
 
 void
@@ -91,7 +97,9 @@ ui_init(void)
 {
     log_info("Initialising UI");
     initscr();
-    raw();
+    nonl();
+    cbreak();
+    noecho();
     keypad(stdscr, TRUE);
     if (prefs_get_boolean(PREF_MOUSE)) {
         mousemask(ALL_MOUSE_EVENTS, NULL);
@@ -116,6 +124,12 @@ ui_init(void)
 }
 
 void
+ui_sigwinch_handler(int sig)
+{
+    perform_resize = TRUE;
+}
+
+void
 ui_update(void)
 {
     ProfWin *current = wins_get_current();
@@ -132,6 +146,13 @@ ui_update(void)
     status_bar_update_virtual();
     inp_put_back();
     doupdate();
+
+    if (perform_resize) {
+        signal(SIGWINCH, SIG_IGN);
+        ui_resize();
+        perform_resize = FALSE;
+        signal(SIGWINCH, ui_sigwinch_handler);
+    }
 }
 
 void
@@ -147,13 +168,13 @@ ui_get_idle_time(void)
 // if compiled with libxss, get the x sessions idle time
 #ifdef HAVE_LIBXSS
     XScreenSaverInfo *info = XScreenSaverAllocInfo();
-    if (info != NULL && display != NULL) {
+    if (info && display) {
         XScreenSaverQueryInfo(display, DefaultRootWindow(display), info);
         unsigned long result = info->idle;
         XFree(info);
         return result;
     }
-    if (info != NULL) {
+    if (info) {
         XFree(info);
     }
 // if no libxss or xss idle time failed, use profanity idle time
@@ -174,84 +195,66 @@ ui_close(void)
 {
     notifier_uninit();
     wins_destroy();
+    inp_close();
     endwin();
 }
 
-char*
+char *
 ui_readline(void)
 {
-    int key_type;
-    wint_t ch;
-
-    char *line = inp_read(&key_type, &ch);
-    _win_handle_switch(ch);
+    return inp_readline();
+}
 
+void
+ui_page_up(void)
+{
     ProfWin *current = wins_get_current();
-    win_handle_page(current, ch, key_type);
-
-    if (ch == KEY_RESIZE) {
-        ui_resize();
-    }
+    win_page_up(current);
+}
 
-    if (ch != ERR && key_type != ERR) {
-        ui_reset_idle_time();
-        ui_input_nonblocking(TRUE);
-    } else {
-        ui_input_nonblocking(FALSE);
-    }
+void
+ui_page_down(void)
+{
+    ProfWin *current = wins_get_current();
+    win_page_down(current);
+}
 
-    return line;
+void
+ui_subwin_page_up(void)
+{
+    ProfWin *current = wins_get_current();
+    win_sub_page_up(current);
 }
 
 void
-ui_inp_history_append(char *inp)
+ui_subwin_page_down(void)
 {
-    inp_history_append(inp);
+    ProfWin *current = wins_get_current();
+    win_sub_page_down(current);
 }
 
 void
 ui_input_clear(void)
 {
-    inp_win_reset();
+    inp_win_clear();
 }
 
 void
 ui_input_nonblocking(gboolean reset)
 {
-    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);
+    inp_nonblocking(reset);
 }
 
 void
 ui_resize(void)
 {
-    log_info("Resizing UI");
+    struct winsize w;
+    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
     erase();
+    resizeterm(w.ws_row, w.ws_col);
     refresh();
+
+    log_info("Resizing UI");
     title_bar_resize();
     wins_resize_all();
     status_bar_resize();
@@ -280,13 +283,6 @@ ui_load_colours(void)
 }
 
 gboolean
-ui_win_exists(int index)
-{
-    ProfWin *window = wins_get_by_num(index);
-    return (window != NULL);
-}
-
-gboolean
 ui_xmlconsole_exists(void)
 {
     ProfXMLWin *xmlwin = wins_get_xmlconsole();
@@ -305,13 +301,13 @@ ui_handle_stanza(const char * const msg)
         ProfWin *window = (ProfWin*) xmlconsole;
 
         if (g_str_has_prefix(msg, "SENT:")) {
-            win_save_print(window, '-', NULL, 0, 0, "", "SENT:");
-            win_save_print(window, '-', NULL, 0, THEME_ONLINE, "", &msg[6]);
-            win_save_print(window, '-', NULL, 0, THEME_ONLINE, "", "");
+            win_print(window, '-', NULL, 0, 0, "", "SENT:");
+            win_print(window, '-', NULL, 0, THEME_ONLINE, "", &msg[6]);
+            win_print(window, '-', NULL, 0, THEME_ONLINE, "", "");
         } else if (g_str_has_prefix(msg, "RECV:")) {
-            win_save_print(window, '-', NULL, 0, 0, "", "RECV:");
-            win_save_print(window, '-', NULL, 0, THEME_AWAY, "", &msg[6]);
-            win_save_print(window, '-', NULL, 0, THEME_AWAY, "", "");
+            win_print(window, '-', NULL, 0, 0, "", "RECV:");
+            win_print(window, '-', NULL, 0, THEME_AWAY, "", &msg[6]);
+            win_print(window, '-', NULL, 0, THEME_AWAY, "", "");
         }
     }
 }
@@ -324,6 +320,42 @@ ui_chat_win_exists(const char * const barejid)
 }
 
 void
+ui_contact_online(char *barejid, Resource *resource, GDateTime *last_activity)
+{
+    char *show_console = prefs_get_string(PREF_STATUSES_CONSOLE);
+    char *show_chat_win = prefs_get_string(PREF_STATUSES_CHAT);
+    PContact contact = roster_get_contact(barejid);
+
+    // show nothing
+    if (g_strcmp0(p_contact_subscription(contact), "none") == 0) {
+        free(show_console);
+        free(show_chat_win);
+        return;
+    }
+
+    // show in console if "all"
+    if (g_strcmp0(show_console, "all") == 0) {
+        cons_show_contact_online(contact, resource, last_activity);
+
+    // show in console of "online" and presence online
+    } else if (g_strcmp0(show_console, "online") == 0 && resource->presence == RESOURCE_ONLINE) {
+        cons_show_contact_online(contact, resource, last_activity);
+    }
+
+    // show in chat win if "all"
+    if (g_strcmp0(show_chat_win, "all") == 0) {
+        ui_chat_win_contact_online(contact, resource, last_activity);
+
+    // show in char win if "online" and presence online
+    } else if (g_strcmp0(show_chat_win, "online") == 0 && resource->presence == RESOURCE_ONLINE) {
+        ui_chat_win_contact_online(contact, resource, last_activity);
+    }
+
+    free(show_console);
+    free(show_chat_win);
+}
+
+void
 ui_contact_typing(const char * const barejid, const char * const resource)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
@@ -356,7 +388,7 @@ ui_contact_typing(const char * const barejid, const char * const resource)
         if ( !is_current || (is_current && prefs_get_boolean(PREF_NOTIFY_TYPING_CURRENT)) ) {
             PContact contact = roster_get_contact(barejid);
             char const *display_usr = NULL;
-            if (p_contact_name(contact) != NULL) {
+            if (p_contact_name(contact)) {
                 display_usr = p_contact_name(contact);
             } else {
                 display_usr = barejid;
@@ -380,53 +412,42 @@ ui_get_current_chat(void)
 }
 
 void
-ui_incoming_msg(const char * const barejid, const char * const resource, const char * const message, GTimeVal *tv_stamp)
+ui_message_receipt(const char * const barejid, const char * const id)
 {
-    gboolean win_created = FALSE;
-    GString *user = g_string_new("");
-
-    PContact contact = roster_get_contact(barejid);
-    if (contact != NULL) {
-        if (p_contact_name(contact) != NULL) {
-            g_string_append(user, p_contact_name(contact));
-        } else {
-            g_string_append(user, barejid);
-        }
-    } else {
-        g_string_append(user, barejid);
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    if (chatwin) {
+        ProfWin *win = (ProfWin*) chatwin;
+        win_mark_received(win, id);
     }
+}
 
-    if (resource && prefs_get_boolean(PREF_RESOURCE_MESSAGE)) {
-        g_string_append(user, "/");
-        g_string_append(user, resource);
-    }
+void
+ui_incoming_msg(const char * const barejid, const char * const resource, const char * const message, GTimeVal *tv_stamp)
+{
+    gboolean win_created = FALSE;
 
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin == NULL) {
         ProfWin *window = wins_new_chat(barejid);
         chatwin = (ProfChatWin*)window;
-#ifdef HAVE_LIBOTR
-        if (otr_is_secure(barejid)) {
-            chatwin->is_otr = TRUE;
-        }
-#endif
         win_created = TRUE;
     }
 
     ProfWin *window = (ProfWin*) chatwin;
-
     int num = wins_get_num(window);
 
+    char *display_name = roster_get_msg_display_name(barejid, resource);
+
     // currently viewing chat window with sender
     if (wins_is_current(window)) {
-        win_print_incoming_message(window, tv_stamp, user->str, message);
+        win_print_incoming_message(window, tv_stamp, display_name, 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(user->str, num);
+        cons_show_incoming_message(display_name, num);
 
         if (prefs_get_boolean(PREF_FLASH)) {
             flash();
@@ -434,23 +455,18 @@ ui_incoming_msg(const char * const barejid, const char * const resource, const c
 
         chatwin->unread++;
         if (prefs_get_boolean(PREF_CHLOG) && prefs_get_boolean(PREF_HISTORY)) {
-            _win_show_history(num, barejid);
+            _win_show_history(chatwin, barejid);
         }
 
         // show users status first, when receiving message via delayed delivery
-        if ((tv_stamp != NULL) && (win_created)) {
+        if (tv_stamp && (win_created)) {
             PContact pcontact = roster_get_contact(barejid);
-            if (pcontact != NULL) {
+            if (pcontact) {
                 win_show_contact(window, pcontact);
             }
         }
 
-        win_print_incoming_message(window, tv_stamp, user->str, message);
-    }
-
-    int ui_index = num;
-    if (ui_index == 10) {
-        ui_index = 0;
+        win_print_incoming_message(window, tv_stamp, display_name, message);
     }
 
     if (prefs_get_boolean(PREF_BEEP)) {
@@ -458,17 +474,10 @@ ui_incoming_msg(const char * const barejid, const char * const resource, const c
     }
 
     if (prefs_get_boolean(PREF_NOTIFY_MESSAGE)) {
-        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(user->str, ui_index, message);
-            } else {
-                notify_message(user->str, ui_index, NULL);
-            }
-        }
+        notify_message(window, display_name, message);
     }
 
-    g_string_free(user, TRUE);
+    free(display_name);
 }
 
 void
@@ -494,24 +503,14 @@ ui_incoming_private_msg(const char * const fulljid, const char * const message,
 
     // not currently viewing chat window with sender
     } else {
+        privatewin->unread++;
         status_bar_new(num);
         cons_show_incoming_message(display_from, num);
+        win_print_incoming_message(window, tv_stamp, display_from, message);
 
         if (prefs_get_boolean(PREF_FLASH)) {
             flash();
         }
-
-        privatewin->unread++;
-        if (prefs_get_boolean(PREF_CHLOG) && prefs_get_boolean(PREF_HISTORY)) {
-            _win_show_history(num, fulljid);
-        }
-
-        win_print_incoming_message(window, tv_stamp, display_from, message);
-    }
-
-    int ui_index = num;
-    if (ui_index == 10) {
-        ui_index = 0;
     }
 
     if (prefs_get_boolean(PREF_BEEP)) {
@@ -519,14 +518,7 @@ ui_incoming_private_msg(const char * const fulljid, const char * const message,
     }
 
     if (prefs_get_boolean(PREF_NOTIFY_MESSAGE)) {
-        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);
-            } else {
-                notify_message(display_from, ui_index, NULL);
-            }
-        }
+        notify_message(window, display_from, message);
     }
 
     free(display_from);
@@ -535,7 +527,7 @@ ui_incoming_private_msg(const char * const fulljid, const char * const message,
 void
 ui_roster_add(const char * const barejid, const char * const name)
 {
-    if (name != NULL) {
+    if (name) {
         cons_show("Roster item added: %s (%s)", barejid, name);
     } else {
         cons_show("Roster item added: %s", barejid);
@@ -582,7 +574,7 @@ void
 ui_auto_away(void)
 {
     char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
-    if (pref_autoaway_message != NULL) {
+    if (pref_autoaway_message) {
         int pri =
             accounts_get_priority_for_presence_type(jabber_get_account_name(),
                 RESOURCE_AWAY);
@@ -622,7 +614,12 @@ ui_handle_login_account_success(ProfAccount *account)
     contact_presence_t contact_presence = contact_presence_from_resource_presence(resource_presence);
     cons_show_login_success(account);
     title_bar_set_presence(contact_presence);
-    status_bar_print_message(account->jid);
+
+    GString *fulljid = g_string_new(account->jid);
+    g_string_append(fulljid, "/");
+    g_string_append(fulljid, account->resource);
+    status_bar_print_message(fulljid->str);
+    g_string_free(fulljid, TRUE);
     status_bar_update_virtual();
 }
 
@@ -633,7 +630,7 @@ ui_update_presence(const resource_presence_t resource_presence,
     contact_presence_t contact_presence = contact_presence_from_resource_presence(resource_presence);
     title_bar_set_presence(contact_presence);
     gint priority = accounts_get_priority_for_presence_type(jabber_get_account_name(), resource_presence);
-    if (message != NULL) {
+    if (message) {
         cons_show("Status set to %s (priority %d), \"%s\".", show, priority, message);
     } else {
         cons_show("Status set to %s (priority %d).", show, priority);
@@ -647,7 +644,7 @@ ui_handle_recipient_not_found(const char * const recipient, const char * const e
     ProfMucWin *mucwin = wins_get_muc(recipient);
     if (mucwin) {
         cons_show_error("Room %s not found: %s", recipient, err_msg);
-        win_save_vprint((ProfWin*) mucwin, '!', NULL, 0, THEME_ERROR, "", "Room %s not found: %s", recipient, err_msg);
+        win_vprint((ProfWin*) mucwin, '!', NULL, 0, THEME_ERROR, "", "Room %s not found: %s", recipient, err_msg);
         return;
     }
 }
@@ -660,19 +657,19 @@ ui_handle_recipient_error(const char * const recipient, const char * const err_m
 
     ProfChatWin *chatwin = wins_get_chat(recipient);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
         return;
     }
 
     ProfMucWin *mucwin = wins_get_muc(recipient);
     if (mucwin) {
-        win_save_vprint((ProfWin*)mucwin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
+        win_vprint((ProfWin*)mucwin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
         return;
     }
 
     ProfPrivateWin *privatewin = wins_get_private(recipient);
     if (privatewin) {
-        win_save_vprint((ProfWin*)privatewin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
+        win_vprint((ProfWin*)privatewin, '!', NULL, 0, THEME_ERROR, "", "Error from %s: %s", recipient, err_msg);
         return;
     }
 }
@@ -691,7 +688,7 @@ ui_handle_error(const char * const err_msg)
 void
 ui_invalid_command_usage(const char * const usage, void (*setting_func)(void))
 {
-    if (setting_func != NULL) {
+    if (setting_func) {
         cons_show("");
         (*setting_func)();
         cons_show("Usage: %s", usage);
@@ -729,7 +726,7 @@ ui_close_connected_win(int index)
             ProfChatWin *chatwin = (ProfChatWin*) window;
             assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
 #ifdef HAVE_LIBOTR
-            if (chatwin->is_otr) {
+            if (chatwin->enc_mode == PROF_ENC_OTR) {
                 otr_end_session(chatwin->barejid);
             }
 #endif
@@ -748,7 +745,7 @@ ui_close_all_wins(void)
     GList *win_nums = wins_get_nums();
     GList *curr = win_nums;
 
-    while (curr != NULL) {
+    while (curr) {
         int num = GPOINTER_TO_INT(curr->data);
         if ((num != 1) && (!ui_win_has_unsaved_form(num))) {
             if (conn_status == JABBER_CONNECTED) {
@@ -775,7 +772,7 @@ ui_close_read_wins(void)
     GList *win_nums = wins_get_nums();
     GList *curr = win_nums;
 
-    while (curr != NULL) {
+    while (curr) {
         int num = GPOINTER_TO_INT(curr->data);
         if ((num != 1) && (ui_win_unread(num) == 0) && (!ui_win_has_unsaved_form(num))) {
             if (conn_status == JABBER_CONNECTED) {
@@ -799,7 +796,7 @@ ui_redraw_all_room_rosters(void)
     GList *win_nums = wins_get_nums();
     GList *curr = win_nums;
 
-    while (curr != NULL) {
+    while (curr) {
         int num = GPOINTER_TO_INT(curr->data);
         ProfWin *window = wins_get_by_num(num);
         if (window->type == WIN_MUC && win_has_active_subwin(window)) {
@@ -820,7 +817,7 @@ ui_hide_all_room_rosters(void)
     GList *win_nums = wins_get_nums();
     GList *curr = win_nums;
 
-    while (curr != NULL) {
+    while (curr) {
         int num = GPOINTER_TO_INT(curr->data);
         ProfWin *window = wins_get_by_num(num);
         if (window->type == WIN_MUC && win_has_active_subwin(window)) {
@@ -841,7 +838,7 @@ ui_show_all_room_rosters(void)
     GList *win_nums = wins_get_nums();
     GList *curr = win_nums;
 
-    while (curr != NULL) {
+    while (curr) {
         int num = GPOINTER_TO_INT(curr->data);
         ProfWin *window = wins_get_by_num(num);
         if (window->type == WIN_MUC && !win_has_active_subwin(window)) {
@@ -870,95 +867,32 @@ ui_win_has_unsaved_form(int num)
     }
 }
 
-gboolean
-ui_switch_win(const int i)
-{
-    if (ui_win_exists(i)) {
-        ProfWin *old_current = wins_get_current();
-        if (old_current->type == WIN_MUC_CONFIG) {
-            ProfMucConfWin *confwin = (ProfMucConfWin*)old_current;
-            cmd_autocomplete_remove_form_fields(confwin->form);
-        }
-
-        ProfWin *new_current = wins_get_by_num(i);
-        if (new_current->type == WIN_MUC_CONFIG) {
-            ProfMucConfWin *confwin = (ProfMucConfWin*)new_current;
-            cmd_autocomplete_add_form_fields(confwin->form);
-        }
-
-        wins_set_current_by_num(i);
-
-        if (i == 1) {
-            title_bar_console();
-            status_bar_current(1);
-            status_bar_active(1);
-        } else {
-            title_bar_switch();
-            status_bar_current(i);
-            status_bar_active(i);
-        }
-        return TRUE;
-    } else {
-        return FALSE;
-    }
-}
-
 void
-ui_previous_win(void)
+ui_switch_win(ProfWin *window)
 {
-    ProfWin *old_current = wins_get_current();
-    if (old_current->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)old_current;
-        cmd_autocomplete_remove_form_fields(confwin->form);
-    }
-
-    ProfWin *new_current = wins_get_previous();
-    if (new_current->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)new_current;
-        cmd_autocomplete_add_form_fields(confwin->form);
-    }
+    assert(window != NULL);
 
-    int i = wins_get_num(new_current);
-    wins_set_current_by_num(i);
-
-    if (i == 1) {
-        title_bar_console();
-        status_bar_current(1);
-        status_bar_active(1);
-    } else {
-        title_bar_switch();
-        status_bar_current(i);
-        status_bar_active(i);
-    }
-}
-
-void
-ui_next_win(void)
-{
     ProfWin *old_current = wins_get_current();
     if (old_current->type == WIN_MUC_CONFIG) {
         ProfMucConfWin *confwin = (ProfMucConfWin*)old_current;
         cmd_autocomplete_remove_form_fields(confwin->form);
     }
 
-    ProfWin *new_current = wins_get_next();
-    if (new_current->type == WIN_MUC_CONFIG) {
-        ProfMucConfWin *confwin = (ProfMucConfWin*)new_current;
+    if (window->type == WIN_MUC_CONFIG) {
+        ProfMucConfWin *confwin = (ProfMucConfWin*)window;
         cmd_autocomplete_add_form_fields(confwin->form);
     }
 
-    int i = wins_get_num(new_current);
+    int i = wins_get_num(window);
     wins_set_current_by_num(i);
 
     if (i == 1) {
         title_bar_console();
-        status_bar_current(1);
-        status_bar_active(1);
     } else {
         title_bar_switch();
-        status_bar_current(i);
-        status_bar_active(i);
     }
+    status_bar_current(i);
+    status_bar_active(i);
 }
 
 void
@@ -974,12 +908,12 @@ ui_gone_secure(const char * const barejid, gboolean trusted)
         chatwin = (ProfChatWin*)window;
     }
 
-    chatwin->is_otr = TRUE;
+    chatwin->enc_mode = PROF_ENC_OTR;
     chatwin->is_trusted = trusted;
     if (trusted) {
-        win_save_print(window, '!', NULL, 0, THEME_OTR_STARTED_TRUSTED, "", "OTR session started (trusted).");
+        win_print(window, '!', NULL, 0, THEME_OTR_STARTED_TRUSTED, "", "OTR session started (trusted).");
     } else {
-        win_save_print(window, '!', NULL, 0, THEME_OTR_STARTED_UNTRUSTED, "", "OTR session started (untrusted).");
+        win_print(window, '!', NULL, 0, THEME_OTR_STARTED_UNTRUSTED, "", "OTR session started (untrusted).");
     }
 
     if (wins_is_current(window)) {
@@ -1002,11 +936,11 @@ ui_gone_insecure(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        chatwin->is_otr = FALSE;
+        chatwin->enc_mode = PROF_ENC_NONE;
         chatwin->is_trusted = FALSE;
 
         ProfWin *window = (ProfWin*)chatwin;
-        win_save_print(window, '!', NULL, 0, THEME_OTR_ENDED, "", "OTR session ended.");
+        win_print(window, '!', NULL, 0, THEME_OTR_ENDED, "", "OTR session ended.");
         if (wins_is_current(window)) {
             title_bar_switch();
         }
@@ -1018,7 +952,7 @@ ui_smp_recipient_initiated(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s wants to authenticate your identity, use '/otr secret <secret>'.", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s wants to authenticate your identity, use '/otr secret <secret>'.", barejid);
     }
 }
 
@@ -1027,9 +961,9 @@ ui_smp_recipient_initiated_q(const char * const barejid, const char *question)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s wants to authenticate your identity with the following question:", barejid);
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "  %s", question);
-        win_save_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "use '/otr answer <answer>'.");
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s wants to authenticate your identity with the following question:", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "  %s", question);
+        win_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "use '/otr answer <answer>'.");
     }
 }
 
@@ -1038,7 +972,7 @@ ui_smp_unsuccessful_sender(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication failed, the secret you entered does not match the secret entered by %s.", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication failed, the secret you entered does not match the secret entered by %s.", barejid);
     }
 }
 
@@ -1047,7 +981,7 @@ ui_smp_unsuccessful_receiver(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication failed, the secret entered by %s does not match yours.", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication failed, the secret entered by %s does not match yours.", barejid);
     }
 }
 
@@ -1056,7 +990,7 @@ ui_smp_aborted(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "SMP session aborted.");
+        win_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "SMP session aborted.");
     }
 }
 
@@ -1065,7 +999,7 @@ ui_smp_successful(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication successful.");
+        win_print((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authentication successful.");
     }
 }
 
@@ -1074,7 +1008,7 @@ ui_smp_answer_success(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s successfully authenticated you.", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s successfully authenticated you.", barejid);
     }
 }
 
@@ -1083,7 +1017,7 @@ ui_smp_answer_failure(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s failed to authenticate you.", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "%s failed to authenticate you.", barejid);
     }
 }
 
@@ -1092,7 +1026,7 @@ ui_otr_authenticating(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authenticating %s...", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Authenticating %s...", barejid);
     }
 }
 
@@ -1101,7 +1035,18 @@ ui_otr_authetication_waiting(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Awaiting authentication from %s...", barejid);
+        win_vprint((ProfWin*)chatwin, '!', NULL, 0, 0, "", "Awaiting authentication from %s...", barejid);
+    }
+}
+
+void
+ui_handle_otr_error(const char * const barejid, const char * const message)
+{
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    if (chatwin) {
+        win_print((ProfWin*)chatwin, '!', NULL, 0, THEME_ERROR, "", message);
+    } else {
+        cons_show_error("%s - %s", barejid, message);
     }
 }
 
@@ -1110,11 +1055,11 @@ ui_trust(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        chatwin->is_otr = TRUE;
+        chatwin->enc_mode = PROF_ENC_OTR;
         chatwin->is_trusted = TRUE;
 
         ProfWin *window = (ProfWin*)chatwin;
-        win_save_print(window, '!', NULL, 0, THEME_OTR_TRUSTED, "", "OTR session trusted.");
+        win_print(window, '!', NULL, 0, THEME_OTR_TRUSTED, "", "OTR session trusted.");
         if (wins_is_current(window)) {
             title_bar_switch();
         }
@@ -1126,11 +1071,11 @@ ui_untrust(const char * const barejid)
 {
     ProfChatWin *chatwin = wins_get_chat(barejid);
     if (chatwin) {
-        chatwin->is_otr = TRUE;
+        chatwin->enc_mode = PROF_ENC_OTR;
         chatwin->is_trusted = FALSE;
 
         ProfWin *window = (ProfWin*)chatwin;
-        win_save_print(window, '!', NULL, 0, THEME_OTR_UNTRUSTED, "", "OTR session untrusted.");
+        win_print(window, '!', NULL, 0, THEME_OTR_UNTRUSTED, "", "OTR session untrusted.");
         if (wins_is_current(window)) {
             title_bar_switch();
         }
@@ -1179,12 +1124,12 @@ ui_prune_wins(void)
     gboolean pruned = FALSE;
 
     GSList *wins = wins_get_prune_wins();
-    if (wins != NULL) {
+    if (wins) {
         pruned = TRUE;
     }
 
     GSList *curr = wins;
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_CHAT) {
             if (conn_status == JABBER_CONNECTED) {
@@ -1199,7 +1144,7 @@ ui_prune_wins(void)
         curr = g_slist_next(curr);
     }
 
-    if (wins != NULL) {
+    if (wins) {
         g_slist_free(wins);
     }
 
@@ -1231,18 +1176,12 @@ ui_current_win_is_otr(void)
     if (current->type == WIN_CHAT) {
         ProfChatWin *chatwin = (ProfChatWin*)current;
         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-        return chatwin->is_otr;
+        return chatwin->enc_mode == PROF_ENC_OTR;
     } else {
         return FALSE;
     }
 }
 
-int
-ui_current_win_index(void)
-{
-    return wins_get_current_num();
-}
-
 win_type_t
 ui_win_type(int index)
 {
@@ -1258,7 +1197,7 @@ ui_current_print_line(const char * const msg, ...)
     va_start(arg, msg);
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, msg, arg);
-    win_save_println(window, fmt_msg->str);
+    win_println(window, fmt_msg->str);
     va_end(arg);
     g_string_free(fmt_msg, TRUE);
 }
@@ -1271,16 +1210,22 @@ ui_current_print_formatted_line(const char show_char, int attrs, const char * co
     va_start(arg, msg);
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, msg, arg);
-    win_save_print(current, show_char, NULL, 0, attrs, "", fmt_msg->str);
+    win_print(current, show_char, NULL, 0, attrs, "", fmt_msg->str);
     va_end(arg);
     g_string_free(fmt_msg, TRUE);
 }
 
 void
+ui_win_error_line(ProfWin *window, const char * const msg)
+{
+    win_print(window, '-', NULL, 0, THEME_ERROR, "", msg);
+}
+
+void
 ui_current_error_line(const char * const msg)
 {
     ProfWin *current = wins_get_current();
-    win_save_print(current, '-', NULL, 0, THEME_ERROR, "", msg);
+    win_print(current, '-', NULL, 0, THEME_ERROR, "", msg);
 }
 
 void
@@ -1293,7 +1238,7 @@ ui_print_system_msg_from_recipient(const char * const barejid, const char *messa
     if (window == NULL) {
         int num = 0;
         window = wins_new_chat(barejid);
-        if (window != NULL) {
+        if (window) {
             num = wins_get_num(window);
             status_bar_active(num);
         } else {
@@ -1303,7 +1248,7 @@ ui_print_system_msg_from_recipient(const char * const barejid, const char *messa
         }
     }
 
-    win_save_vprint(window, '-', NULL, 0, 0, "", "*%s %s", barejid, message);
+    win_vprint(window, '-', NULL, 0, 0, "", "*%s %s", barejid, message);
 }
 
 void
@@ -1325,8 +1270,8 @@ ui_recipient_gone(const char * const barejid, const char * const resource)
         if (show_message) {
             const char * display_usr = NULL;
             PContact contact = roster_get_contact(barejid);
-            if (contact != NULL) {
-                if (p_contact_name(contact) != NULL) {
+            if (contact) {
+                if (p_contact_name(contact)) {
                     display_usr = p_contact_name(contact);
                 } else {
                     display_usr = barejid;
@@ -1335,175 +1280,128 @@ ui_recipient_gone(const char * const barejid, const char * const resource)
                 display_usr = barejid;
             }
 
-            win_save_vprint((ProfWin*)chatwin, '!', NULL, 0, THEME_GONE, "", "<- %s has left the conversation.", display_usr);
+            win_vprint((ProfWin*)chatwin, '!', NULL, 0, THEME_GONE, "", "<- %s has left the conversation.", display_usr);
         }
     }
 }
 
-void
+ProfPrivateWin*
 ui_new_private_win(const char * const fulljid)
 {
-    ProfWin *window = (ProfWin*)wins_get_private(fulljid);
-    int num = 0;
-
-    // create new window
-    if (window == NULL) {
-        window = wins_new_private(fulljid);
-        num = wins_get_num(window);
-    } else {
-        num = wins_get_num(window);
-    }
-
-    ui_switch_win(num);
-}
-
-void
-ui_new_chat_win(const char * const barejid)
-{
-    ProfWin *window = (ProfWin*)wins_get_chat(barejid);
-    int num = 0;
-
-    // create new window
-    if (window == NULL) {
-        window = wins_new_chat(barejid);
-
-        num = wins_get_num(window);
-
-        if (prefs_get_boolean(PREF_CHLOG) && prefs_get_boolean(PREF_HISTORY)) {
-            _win_show_history(num, barejid);
-        }
-
-        // if the contact is offline, show a message
-        PContact contact = roster_get_contact(barejid);
-        if (contact != NULL) {
-            if (strcmp(p_contact_presence(contact), "offline") == 0) {
-                const char * const show = p_contact_presence(contact);
-                const char * const status = p_contact_status(contact);
-                win_show_status_string(window, barejid, show, status, NULL, "--", "offline");
-            }
-        }
-    } else {
-        num = wins_get_num(window);
-    }
-
-    ui_switch_win(num);
+    ProfWin *window = wins_new_private(fulljid);
+    return (ProfPrivateWin*)window;
 }
 
 void
 ui_create_xmlconsole_win(void)
 {
     ProfWin *window = wins_new_xmlconsole();
-    int num = wins_get_num(window);
-    ui_switch_win(num);
+    ui_ev_focus_win(window);
 }
 
 void
 ui_open_xmlconsole_win(void)
 {
     ProfXMLWin *xmlwin = wins_get_xmlconsole();
-    if (xmlwin != NULL) {
-        int num = wins_get_num((ProfWin*)xmlwin);
-        ui_switch_win(num);
+    if (xmlwin) {
+        ui_ev_focus_win((ProfWin*)xmlwin);
     }
 }
 
-void
-ui_outgoing_chat_msg(const char * const from, const char * const barejid,
-    const char * const message)
+ProfChatWin*
+ui_new_chat_win(const char * const barejid)
 {
-    PContact contact = roster_get_contact(barejid);
-    ProfWin *window = (ProfWin*)wins_get_chat(barejid);
-    int num = 0;
+    ProfWin *window = wins_new_chat(barejid);
+    ProfChatWin *chatwin = (ProfChatWin *)window;
 
-    // create new window
-    if (window == NULL) {
-        window = wins_new_chat(barejid);
 #ifdef HAVE_LIBOTR
-        ProfChatWin *chatwin = (ProfChatWin*)window;
-        if (otr_is_secure(barejid)) {
-            chatwin->is_otr = TRUE;
-        }
+    if (otr_is_secure(barejid)) {
+        chatwin->enc_mode = PROF_ENC_OTR;
+    }
 #endif
-        num = wins_get_num(window);
 
-        if (prefs_get_boolean(PREF_CHLOG) && prefs_get_boolean(PREF_HISTORY)) {
-            _win_show_history(num, barejid);
-        }
+    if (prefs_get_boolean(PREF_CHLOG) && prefs_get_boolean(PREF_HISTORY)) {
+        _win_show_history(chatwin, barejid);
+    }
 
-        if (contact != NULL) {
-            if (strcmp(p_contact_presence(contact), "offline") == 0) {
-                const char *show = p_contact_presence(contact);
-                const char *status = p_contact_status(contact);
-                win_show_status_string(window, barejid, show, status, NULL, "--", "offline");
-            }
+    // if the contact is offline, show a message
+    PContact contact = roster_get_contact(barejid);
+    if (contact) {
+        if (strcmp(p_contact_presence(contact), "offline") == 0) {
+            const char * const show = p_contact_presence(contact);
+            const char * const status = p_contact_status(contact);
+            win_show_status_string(window, barejid, show, status, NULL, "--", "offline");
         }
+    }
+
+    return chatwin;
+}
 
-    // use existing window
+void
+ui_outgoing_chat_msg(ProfChatWin *chatwin, const char * const message, char *id)
+{
+    if (prefs_get_boolean(PREF_RECEIPTS_REQUEST) && id) {
+        win_print_with_receipt((ProfWin*)chatwin, '-', NULL, 0, THEME_TEXT_ME, "me", message, id);
     } else {
-        num = wins_get_num(window);
+        win_print((ProfWin*)chatwin, '-', NULL, 0, THEME_TEXT_ME, "me", message);
     }
-    ProfChatWin *chatwin = (ProfChatWin*)window;
-    chat_state_active(chatwin->state);
-
-    win_save_print(window, '-', NULL, 0, THEME_TEXT_ME, from, message);
-    ui_switch_win(num);
 }
 
 void
-ui_outgoing_private_msg(const char * const from, const char * const fulljid,
-    const char * const message)
+ui_outgoing_chat_msg_carbon(const char * const barejid, const char * const message)
 {
-    ProfWin *window = (ProfWin*)wins_get_private(fulljid);
-    int num = 0;
+    ProfChatWin *chatwin = wins_get_chat(barejid);
 
     // create new window
-    if (window == NULL) {
-        window = wins_new_private(fulljid);
-        num = wins_get_num(window);
-
-    // use existing window
-    } else {
-        num = wins_get_num(window);
+    if (!chatwin) {
+        chatwin = ui_new_chat_win(barejid);
     }
 
-    win_save_print(window, '-', NULL, 0, THEME_TEXT_ME, from, message);
-    ui_switch_win(num);
+    chat_state_active(chatwin->state);
+
+    win_print((ProfWin*)chatwin, '-', NULL, 0, THEME_TEXT_ME, "me", message);
+
+    int num = wins_get_num((ProfWin*)chatwin);
+    status_bar_active(num);
+}
+
+void
+ui_outgoing_private_msg(ProfPrivateWin *privwin, const char * const message)
+{
+    win_print((ProfWin*)privwin, '-', NULL, 0, THEME_TEXT_ME, "me", message);
 }
 
 void
 ui_room_join(const char * const roomjid, gboolean focus)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    int num = 0;
-
-    // create new window
-    if (window == NULL) {
+    if (!window) {
         window = wins_new_muc(roomjid);
     }
 
     char *nick = muc_nick(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "-> You have joined the room as %s", nick);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "-> You have joined the room as %s", nick);
     if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
         char *role = muc_role_str(roomjid);
         char *affiliation = muc_affiliation_str(roomjid);
         if (role) {
-            win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", role: %s", role);
+            win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", role: %s", role);
         }
         if (affiliation) {
-            win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", affiliation: %s", affiliation);
+            win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", affiliation: %s", affiliation);
         }
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 
-    num = wins_get_num(window);
 
     if (focus) {
-        ui_switch_win(num);
+        ui_ev_focus_win(window);
     } else {
+        int num = wins_get_num(window);
         status_bar_active(num);
         ProfWin *console = wins_get_console();
         char *nick = muc_nick(roomjid);
-        win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "-> Autojoined %s as %s (%d).", roomjid, nick, num);
+        win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "-> Autojoined %s as %s (%d).", roomjid, nick, num);
     }
 }
 
@@ -1511,9 +1409,7 @@ void
 ui_switch_to_room(const char * const roomjid)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    int num = wins_get_num(window);
-    num = wins_get_num(window);
-    ui_switch_win(num);
+    ui_ev_focus_win(window);
 }
 
 void
@@ -1521,14 +1417,14 @@ ui_room_role_change(const char * const roomjid, const char * const role, const c
     const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your role has been changed to: %s", role);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your role has been changed to: %s", role);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 void
@@ -1536,14 +1432,14 @@ ui_room_affiliation_change(const char * const roomjid, const char * const affili
     const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your affiliation has been changed to: %s", affiliation);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your affiliation has been changed to: %s", affiliation);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 void
@@ -1551,14 +1447,14 @@ ui_room_role_and_affiliation_change(const char * const roomjid, const char * con
     const char * const actor, const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your role and affiliation have been changed, role: %s, affiliation: %s", role, affiliation);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Your role and affiliation have been changed, role: %s, affiliation: %s", role, affiliation);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 
@@ -1567,14 +1463,14 @@ ui_room_occupant_role_change(const char * const roomjid, const char * const nick
     const char * const actor, const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's role has been changed to: %s", nick, role);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's role has been changed to: %s", nick, role);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 void
@@ -1582,14 +1478,14 @@ ui_room_occupant_affiliation_change(const char * const roomjid, const char * con
     const char * const actor, const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's affiliation has been changed to: %s", nick, affiliation);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's affiliation has been changed to: %s", nick, affiliation);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 void
@@ -1597,14 +1493,14 @@ ui_room_occupant_role_and_affiliation_change(const char * const roomjid, const c
     const char * const affiliation, const char * const actor, const char * const reason)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's role and affiliation have been changed, role: %s, affiliation: %s", nick, role, affiliation);
+    win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%s's role and affiliation have been changed, role: %s, affiliation: %s", nick, role, affiliation);
     if (actor) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", by: %s", actor);
     }
     if (reason) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ROOMINFO, "", ", reason: %s", reason);
     }
-    win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+    win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
 }
 
 void
@@ -1612,8 +1508,8 @@ ui_handle_room_info_error(const char * const roomjid, const char * const error)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        win_save_vprint(window, '!', NULL, 0, 0, "", "Room info request failed: %s", error);
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_vprint(window, '!', NULL, 0, 0, "", "Room info request failed: %s", error);
+        win_print(window, '-', NULL, 0, 0, "", "");
     }
 }
 
@@ -1622,38 +1518,38 @@ ui_show_room_disco_info(const char * const roomjid, GSList *identities, GSList *
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        if (((identities != NULL) && (g_slist_length(identities) > 0)) ||
-            ((features != NULL) && (g_slist_length(features) > 0))) {
-            if (identities != NULL) {
-                win_save_print(window, '!', NULL, 0, 0, "", "Identities:");
+        if ((identities && (g_slist_length(identities) > 0)) ||
+            (features && (g_slist_length(features) > 0))) {
+            if (identities) {
+                win_print(window, '!', NULL, 0, 0, "", "Identities:");
             }
-            while (identities != NULL) {
+            while (identities) {
                 DiscoIdentity *identity = identities->data;  // anme trpe, cat
                 GString *identity_str = g_string_new("  ");
-                if (identity->name != NULL) {
+                if (identity->name) {
                     identity_str = g_string_append(identity_str, identity->name);
                     identity_str = g_string_append(identity_str, " ");
                 }
-                if (identity->type != NULL) {
+                if (identity->type) {
                     identity_str = g_string_append(identity_str, identity->type);
                     identity_str = g_string_append(identity_str, " ");
                 }
-                if (identity->category != NULL) {
+                if (identity->category) {
                     identity_str = g_string_append(identity_str, identity->category);
                 }
-                win_save_print(window, '!', NULL, 0, 0, "", identity_str->str);
+                win_print(window, '!', NULL, 0, 0, "", identity_str->str);
                 g_string_free(identity_str, TRUE);
                 identities = g_slist_next(identities);
             }
 
-            if (features != NULL) {
-                win_save_print(window, '!', NULL, 0, 0, "", "Features:");
+            if (features) {
+                win_print(window, '!', NULL, 0, 0, "", "Features:");
             }
-            while (features != NULL) {
-                win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", features->data);
+            while (features) {
+                win_vprint(window, '!', NULL, 0, 0, "", "  %s", features->data);
                 features = g_slist_next(features);
             }
-            win_save_print(window, '-', NULL, 0, 0, "", "");
+            win_print(window, '-', NULL, 0, 0, "", "");
         }
     }
 }
@@ -1667,32 +1563,32 @@ ui_room_roster(const char * const roomjid, GList *roster, const char * const pre
     } else {
         if ((roster == NULL) || (g_list_length(roster) == 0)) {
             if (presence == NULL) {
-                win_save_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room is empty.");
+                win_print(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room is empty.");
             } else {
-                win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "No occupants %s.", presence);
+                win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "No occupants %s.", presence);
             }
         } else {
             int length = g_list_length(roster);
             if (presence == NULL) {
-                win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%d occupants: ", length);
+                win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%d occupants: ", length);
             } else {
-                win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%d %s: ", length, presence);
+                win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "%d %s: ", length, presence);
             }
 
-            while (roster != NULL) {
+            while (roster) {
                 Occupant *occupant = roster->data;
                 const char *presence_str = string_from_resource_presence(occupant->presence);
 
                 theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
-                win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", occupant->nick);
+                win_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", "%s", occupant->nick);
 
-                if (roster->next != NULL) {
-                    win_save_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", ");
+                if (roster->next) {
+                    win_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", ");
                 }
 
                 roster = g_list_next(roster);
             }
-            win_save_print(window, '!', NULL, NO_DATE, THEME_ONLINE, "", "");
+            win_print(window, '!', NULL, NO_DATE, THEME_ONLINE, "", "");
 
         }
     }
@@ -1711,7 +1607,7 @@ ui_room_member_offline(const char * const roomjid, const char * const nick)
     if (window == NULL) {
         log_error("Received offline presence for room participant %s, but no window open for %s.", nick, roomjid);
     } else {
-        win_save_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s has left the room.", nick);
+        win_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s has left the room.", nick);
     }
 }
 
@@ -1734,7 +1630,7 @@ ui_room_member_kicked(const char * const roomjid, const char * const nick, const
             g_string_append(message, reason);
         }
 
-        win_save_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s", message->str);
+        win_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s", message->str);
         g_string_free(message, TRUE);
     }
 }
@@ -1758,7 +1654,7 @@ ui_room_member_banned(const char * const roomjid, const char * const nick, const
             g_string_append(message, reason);
         }
 
-        win_save_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s", message->str);
+        win_vprint(window, '!', NULL, 0, THEME_OFFLINE, "", "<- %s", message->str);
         g_string_free(message, TRUE);
     }
 }
@@ -1771,16 +1667,16 @@ ui_room_member_online(const char * const roomjid, const char * const nick, const
     if (window == NULL) {
         log_error("Received online presence for room participant %s, but no window open for %s.", nick, roomjid);
     } else {
-        win_save_vprint(window, '!', NULL, NO_EOL, THEME_ONLINE, "", "-> %s has joined the room", nick);
+        win_vprint(window, '!', NULL, NO_EOL, THEME_ONLINE, "", "-> %s has joined the room", nick);
         if (prefs_get_boolean(PREF_MUC_PRIVILEGES)) {
             if (role) {
-                win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", ", role: %s", role);
+                win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", ", role: %s", role);
             }
             if (affiliation) {
-                win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", ", affiliation: %s", affiliation);
+                win_vprint(window, '!', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", ", affiliation: %s", affiliation);
             }
         }
-        win_save_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
+        win_print(window, '!', NULL, NO_DATE, THEME_ROOMINFO, "", "");
     }
 }
 
@@ -1804,7 +1700,7 @@ ui_room_member_nick_change(const char * const roomjid,
     if (window == NULL) {
         log_error("Received nick change for room participant %s, but no window open for %s.", old_nick, roomjid);
     } else {
-        win_save_vprint(window, '!', NULL, 0, THEME_THEM, "", "** %s is now known as %s", old_nick, nick);
+        win_vprint(window, '!', NULL, 0, THEME_THEM, "", "** %s is now known as %s", old_nick, nick);
     }
 }
 
@@ -1815,7 +1711,7 @@ ui_room_nick_change(const char * const roomjid, const char * const nick)
     if (window == NULL) {
         log_error("Received self nick change %s, but no window open for %s.", nick, roomjid);
     } else {
-        win_save_vprint(window, '!', NULL, 0, THEME_ME, "", "** You are now known as %s", nick);
+        win_vprint(window, '!', NULL, 0, THEME_ME, "", "** You are now known as %s", nick);
     }
 }
 
@@ -1840,7 +1736,7 @@ ui_room_history(const char * const roomjid, const char * const nick,
             g_string_append(line, message);
         }
 
-        win_save_print(window, '-', &tv_stamp, NO_COLOUR_DATE, 0, "", line->str);
+        win_print(window, '-', &tv_stamp, NO_COLOUR_DATE, 0, "", line->str);
         g_string_free(line, TRUE);
     }
 }
@@ -1852,77 +1748,79 @@ ui_room_message(const char * const roomjid, const char * const nick,
     ProfMucWin *mucwin = wins_get_muc(roomjid);
     if (mucwin == NULL) {
         log_error("Room message received from %s, but no window open for %s", nick, roomjid);
-    } else {
-        ProfWin *window = (ProfWin*) mucwin;
-        int num = wins_get_num(window);
-        char *my_nick = muc_nick(roomjid);
+        return;
+    }
 
-        if (g_strcmp0(nick, my_nick) != 0) {
-            if (g_strrstr(message, my_nick) != NULL) {
-                win_save_print(window, '-', NULL, NO_ME, THEME_ROOMMENTION, nick, message);
-            } else {
-                win_save_print(window, '-', NULL, NO_ME, THEME_TEXT_THEM, nick, message);
-            }
+    ProfWin *window = (ProfWin*) mucwin;
+    int num = wins_get_num(window);
+    char *my_nick = muc_nick(roomjid);
+
+    if (g_strcmp0(nick, my_nick) != 0) {
+        if (g_strrstr(message, my_nick)) {
+            win_print(window, '-', NULL, NO_ME, THEME_ROOMMENTION, nick, message);
         } else {
-            win_save_print(window, '-', NULL, 0, THEME_TEXT_ME, nick, message);
+            win_print(window, '-', NULL, NO_ME, THEME_TEXT_THEM, nick, message);
         }
+    } else {
+        win_print(window, '-', NULL, 0, THEME_TEXT_ME, nick, message);
+    }
 
-        // currently in groupchat window
-        if (wins_is_current(window)) {
-            status_bar_active(num);
-
-        // not currenlty on groupchat window
-        } else {
-            status_bar_new(num);
-            cons_show_incoming_message(nick, num);
+    // currently in groupchat window
+    if (wins_is_current(window)) {
+        status_bar_active(num);
 
-            if (strcmp(nick, my_nick) != 0) {
-                if (prefs_get_boolean(PREF_FLASH)) {
-                    flash();
-                }
-            }
+    // not currently on groupchat window
+    } else {
+        status_bar_new(num);
+        cons_show_incoming_message(nick, num);
 
-            mucwin->unread++;
+        if (prefs_get_boolean(PREF_FLASH) && (strcmp(nick, my_nick) != 0)) {
+            flash();
         }
 
-        int ui_index = num;
-        if (ui_index == 10) {
-            ui_index = 0;
-        }
+        mucwin->unread++;
+    }
 
-        if (strcmp(nick, muc_nick(roomjid)) != 0) {
-            if (prefs_get_boolean(PREF_BEEP)) {
-                beep();
-            }
+    int ui_index = num;
+    if (ui_index == 10) {
+        ui_index = 0;
+    }
 
-            gboolean notify = FALSE;
-            char *room_setting = prefs_get_string(PREF_NOTIFY_ROOM);
-            if (g_strcmp0(room_setting, "on") == 0) {
-                notify = TRUE;
-            }
-            if (g_strcmp0(room_setting, "mention") == 0) {
-                char *message_lower = g_utf8_strdown(message, -1);
-                char *nick_lower = g_utf8_strdown(nick, -1);
-                if (g_strrstr(message_lower, nick_lower) != NULL) {
-                    notify = TRUE;
-                }
-                g_free(message_lower);
-                g_free(nick_lower);
-            }
-            prefs_free_string(room_setting);
-
-            if (notify) {
-                gboolean is_current = wins_is_current(window);
-                if ( !is_current || (is_current && prefs_get_boolean(PREF_NOTIFY_ROOM_CURRENT)) ) {
-                    Jid *jidp = jid_create(roomjid);
-                    if (prefs_get_boolean(PREF_NOTIFY_ROOM_TEXT)) {
-                        notify_room_message(nick, jidp->localpart, ui_index, message);
-                    } else {
-                        notify_room_message(nick, jidp->localpart, ui_index, NULL);
-                    }
-                    jid_destroy(jidp);
-                }
+    // don't notify self messages
+    if (strcmp(nick, my_nick) == 0) {
+        return;
+    }
+
+    if (prefs_get_boolean(PREF_BEEP)) {
+        beep();
+    }
+
+    gboolean notify = FALSE;
+    char *room_setting = prefs_get_string(PREF_NOTIFY_ROOM);
+    if (g_strcmp0(room_setting, "on") == 0) {
+        notify = TRUE;
+    }
+    if (g_strcmp0(room_setting, "mention") == 0) {
+        char *message_lower = g_utf8_strdown(message, -1);
+        char *nick_lower = g_utf8_strdown(nick, -1);
+        if (g_strrstr(message_lower, nick_lower)) {
+            notify = TRUE;
+        }
+        g_free(message_lower);
+        g_free(nick_lower);
+    }
+    prefs_free_string(room_setting);
+
+    if (notify) {
+        gboolean is_current = wins_is_current(window);
+        if ( !is_current || (is_current && prefs_get_boolean(PREF_NOTIFY_ROOM_CURRENT)) ) {
+            Jid *jidp = jid_create(roomjid);
+            if (prefs_get_boolean(PREF_NOTIFY_ROOM_TEXT)) {
+                notify_room_message(nick, jidp->localpart, ui_index, message);
+            } else {
+                notify_room_message(nick, jidp->localpart, ui_index, NULL);
             }
+            jid_destroy(jidp);
         }
     }
 }
@@ -1940,22 +1838,22 @@ ui_room_requires_config(const char * const roomjid)
             ui_index = 0;
         }
 
-        win_save_print(window, '-', NULL, 0, 0, "", "");
-        win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
+        win_print(window, '-', NULL, 0, 0, "", "");
+        win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
             "Room locked, requires configuration.");
-        win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
+        win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
             "Use '/room accept' to accept the defaults");
-        win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
+        win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
             "Use '/room destroy' to cancel and destroy the room");
-        win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
+        win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "",
             "Use '/room config' to edit the room configuration");
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "");
 
         // currently in groupchat window
         if (wins_is_current(window)) {
             status_bar_active(num);
 
-        // not currenlty on groupchat window
+        // not currently on groupchat window
         } else {
             status_bar_new(num);
         }
@@ -1998,16 +1896,16 @@ ui_room_destroyed(const char * const roomjid, const char * const reason, const c
         ProfWin *console = wins_get_console();
 
         if (reason) {
-            win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- Room destroyed: %s, reason: %s", roomjid, reason);
+            win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- Room destroyed: %s, reason: %s", roomjid, reason);
         } else {
-            win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- Room destroyed: %s", roomjid);
+            win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- Room destroyed: %s", roomjid);
         }
 
         if (new_jid) {
             if (password) {
-                win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "Replacement room: %s, password: %s", new_jid, password);
+                win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "Replacement room: %s, password: %s", new_jid, password);
             } else {
-                win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "Replacement room: %s", new_jid);
+                win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "Replacement room: %s", new_jid);
             }
         }
     }
@@ -2035,7 +1933,7 @@ ui_room_kicked(const char * const roomjid, const char * const actor, const char
         }
 
         ProfWin *console = wins_get_console();
-        win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- %s", message->str);
+        win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- %s", message->str);
         g_string_free(message, TRUE);
     }
 }
@@ -2062,7 +1960,7 @@ ui_room_banned(const char * const roomjid, const char * const actor, const char
         }
 
         ProfWin *console = wins_get_console();
-        win_save_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- %s", message->str);
+        win_vprint(console, '!', NULL, 0, THEME_TYPING, "", "<- %s", message->str);
         g_string_free(message, TRUE);
     }
 }
@@ -2078,17 +1976,17 @@ ui_room_subject(const char * const roomjid, const char * const nick, const char
 
         if (subject) {
             if (nick) {
-                win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "*%s has set the room subject: ", nick);
-                win_save_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
+                win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "*%s has set the room subject: ", nick);
+                win_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
             } else {
-                win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room subject: ");
-                win_save_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
+                win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room subject: ");
+                win_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", subject);
             }
         } else {
             if (nick) {
-                win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "*%s has cleared the room subject: ", nick);
+                win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "*%s has cleared the room subject: ", nick);
             } else {
-                win_save_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room subject cleared");
+                win_vprint(window, '!', NULL, 0, THEME_ROOMINFO, "", "Room subject cleared");
             }
         }
 
@@ -2096,7 +1994,7 @@ ui_room_subject(const char * const roomjid, const char * const nick, const char
         if (wins_is_current(window)) {
             status_bar_active(num);
 
-        // not currenlty on groupchat window
+        // not currently on groupchat window
         } else {
             status_bar_active(num);
         }
@@ -2110,7 +2008,7 @@ ui_handle_room_kick_error(const char * const roomjid, const char * const nick, c
     if (window == NULL) {
         log_error("Kick error received for %s, but no window open for %s.", nick, roomjid);
     } else {
-        win_save_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error kicking %s: %s", nick, error);
+        win_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error kicking %s: %s", nick, error);
     }
 }
 
@@ -2123,14 +2021,14 @@ ui_room_broadcast(const char * const roomjid, const char * const message)
     } else {
         int num = wins_get_num(window);
 
-        win_save_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room message: ");
-        win_save_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", message);
+        win_vprint(window, '!', NULL, NO_EOL, THEME_ROOMINFO, "", "Room message: ");
+        win_vprint(window, '!', NULL, NO_DATE, 0, "", "%s", message);
 
         // currently in groupchat window
         if (wins_is_current(window)) {
             status_bar_active(num);
 
-        // not currenlty on groupchat window
+        // not currently on groupchat window
         } else {
             status_bar_new(num);
         }
@@ -2143,7 +2041,7 @@ ui_handle_room_affiliation_list_error(const char * const roomjid, const char * c
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        win_save_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error retrieving %s list: %s", affiliation, error);
+        win_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error retrieving %s list: %s", affiliation, error);
     }
 }
 
@@ -2153,17 +2051,17 @@ ui_handle_room_affiliation_list(const char * const roomjid, const char * const a
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
         if (jids) {
-            win_save_vprint(window, '!', NULL, 0, 0, "", "Affiliation: %s", affiliation);
+            win_vprint(window, '!', NULL, 0, 0, "", "Affiliation: %s", affiliation);
             GSList *curr_jid = jids;
             while (curr_jid) {
                 char *jid = curr_jid->data;
-                win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", jid);
+                win_vprint(window, '!', NULL, 0, 0, "", "  %s", jid);
                 curr_jid = g_slist_next(curr_jid);
             }
-            win_save_print(window, '!', NULL, 0, 0, "", "");
+            win_print(window, '!', NULL, 0, 0, "", "");
         } else {
-            win_save_vprint(window, '!', NULL, 0, 0, "", "No users found with affiliation: %s", affiliation);
-            win_save_print(window, '!', NULL, 0, 0, "", "");
+            win_vprint(window, '!', NULL, 0, 0, "", "No users found with affiliation: %s", affiliation);
+            win_print(window, '!', NULL, 0, 0, "", "");
         }
     }
 }
@@ -2173,7 +2071,7 @@ ui_handle_room_role_list_error(const char * const roomjid, const char * const ro
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        win_save_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error retrieving %s list: %s", role, error);
+        win_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error retrieving %s list: %s", role, error);
     }
 }
 
@@ -2183,26 +2081,26 @@ ui_handle_room_role_list(const char * const roomjid, const char * const role, GS
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
         if (nicks) {
-            win_save_vprint(window, '!', NULL, 0, 0, "", "Role: %s", role);
+            win_vprint(window, '!', NULL, 0, 0, "", "Role: %s", role);
             GSList *curr_nick = nicks;
             while (curr_nick) {
                 char *nick = curr_nick->data;
                 Occupant *occupant = muc_roster_item(roomjid, nick);
                 if (occupant) {
                     if (occupant->jid) {
-                        win_save_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", nick, occupant->jid);
+                        win_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", nick, occupant->jid);
                     } else {
-                        win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", nick);
+                        win_vprint(window, '!', NULL, 0, 0, "", "  %s", nick);
                     }
                 } else {
-                    win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", nick);
+                    win_vprint(window, '!', NULL, 0, 0, "", "  %s", nick);
                 }
                 curr_nick = g_slist_next(curr_nick);
             }
-            win_save_print(window, '!', NULL, 0, 0, "", "");
+            win_print(window, '!', NULL, 0, 0, "", "");
         } else {
-            win_save_vprint(window, '!', NULL, 0, 0, "", "No occupants found with role: %s", role);
-            win_save_print(window, '!', NULL, 0, 0, "", "");
+            win_vprint(window, '!', NULL, 0, 0, "", "No occupants found with role: %s", role);
+            win_print(window, '!', NULL, 0, 0, "", "");
         }
     }
 }
@@ -2213,7 +2111,7 @@ ui_handle_room_affiliation_set_error(const char * const roomjid, const char * co
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        win_save_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error setting %s affiliation for %s: %s", affiliation, jid, error);
+        win_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error setting %s affiliation for %s: %s", affiliation, jid, error);
     }
 }
 
@@ -2223,7 +2121,7 @@ ui_handle_room_role_set_error(const char * const roomjid, const char * const nic
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
     if (window) {
-        win_save_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error setting %s role for %s: %s", role, nick, error);
+        win_vprint(window, '!', NULL, 0, THEME_ERROR, "", "Error setting %s role for %s: %s", role, nick, error);
     }
 }
 
@@ -2237,7 +2135,7 @@ int
 ui_win_unread(int index)
 {
     ProfWin *window = wins_get_by_num(index);
-    if (window != NULL) {
+    if (window) {
         return win_unread(window);
     } else {
         return 0;
@@ -2247,14 +2145,9 @@ ui_win_unread(int index)
 char *
 ui_ask_password(void)
 {
-  char *passwd = malloc(sizeof(char) * (MAX_PASSWORD_SIZE + 1));
   status_bar_get_password();
   status_bar_update_virtual();
-  inp_block();
-  inp_get_password(passwd);
-  inp_non_block(prefs_get_inpblock());
-
-  return passwd;
+  return inp_get_password();
 }
 
 void
@@ -2265,7 +2158,7 @@ ui_chat_win_contact_online(PContact contact, Resource *resource, GDateTime *last
     const char *barejid = p_contact_barejid(contact);
 
     ProfWin *window = (ProfWin*)wins_get_chat(barejid);
-    if (window != NULL) {
+    if (window) {
         win_show_status_string(window, display_str, show, resource->status,
             last_activity, "++", "online");
 
@@ -2281,7 +2174,7 @@ ui_chat_win_contact_offline(PContact contact, char *resource, char *status)
     const char *barejid = p_contact_barejid(contact);
 
     ProfWin *window = (ProfWin*)wins_get_chat(barejid);
-    if (window != NULL) {
+    if (window) {
         win_show_status_string(window, display_str, "offline", status, NULL, "--",
             "offline");
     }
@@ -2296,7 +2189,7 @@ ui_contact_offline(char *barejid, char *resource, char *status)
     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 (p_contact_subscription(contact)) {
         if (strcmp(p_contact_subscription(contact), "none") != 0) {
 
             // show in console if "all"
@@ -2377,7 +2270,7 @@ _ui_draw_term_title(void)
         if (res == -1) {
             log_error("Error writing terminal window title.");
         }
-        if (win_title != NULL) {
+        if (win_title) {
             free(win_title);
         }
         win_title = strdup(new_win_title);
@@ -2391,10 +2284,10 @@ ui_show_room_info(ProfMucWin *mucwin)
     char *affiliation = muc_affiliation_str(mucwin->roomjid);
 
     ProfWin *window = (ProfWin*) mucwin;
-    win_save_vprint(window, '!', NULL, 0, 0, "", "Room: %s", mucwin->roomjid);
-    win_save_vprint(window, '!', NULL, 0, 0, "", "Affiliation: %s", affiliation);
-    win_save_vprint(window, '!', NULL, 0, 0, "", "Role: %s", role);
-    win_save_print(window, '-', NULL, 0, 0, "", "");
+    win_vprint(window, '!', NULL, 0, 0, "", "Room: %s", mucwin->roomjid);
+    win_vprint(window, '!', NULL, 0, 0, "", "Affiliation: %s", affiliation);
+    win_vprint(window, '!', NULL, 0, 0, "", "Role: %s", role);
+    win_print(window, '-', NULL, 0, 0, "", "");
 }
 
 void
@@ -2406,28 +2299,28 @@ ui_show_room_role_list(ProfMucWin *mucwin, muc_role_t role)
     if (!occupants) {
         switch (role) {
             case MUC_ROLE_MODERATOR:
-                win_save_print(window, '!', NULL, 0, 0, "", "No moderators found.");
+                win_print(window, '!', NULL, 0, 0, "", "No moderators found.");
                 break;
             case MUC_ROLE_PARTICIPANT:
-                win_save_print(window, '!', NULL, 0, 0, "", "No participants found.");
+                win_print(window, '!', NULL, 0, 0, "", "No participants found.");
                 break;
             case MUC_ROLE_VISITOR:
-                win_save_print(window, '!', NULL, 0, 0, "", "No visitors found.");
+                win_print(window, '!', NULL, 0, 0, "", "No visitors found.");
                 break;
             default:
                 break;
         }
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "");
     } else {
         switch (role) {
             case MUC_ROLE_MODERATOR:
-                win_save_print(window, '!', NULL, 0, 0, "", "Moderators:");
+                win_print(window, '!', NULL, 0, 0, "", "Moderators:");
                 break;
             case MUC_ROLE_PARTICIPANT:
-                win_save_print(window, '!', NULL, 0, 0, "", "Participants:");
+                win_print(window, '!', NULL, 0, 0, "", "Participants:");
                 break;
             case MUC_ROLE_VISITOR:
-                win_save_print(window, '!', NULL, 0, 0, "", "Visitors:");
+                win_print(window, '!', NULL, 0, 0, "", "Visitors:");
                 break;
             default:
                 break;
@@ -2438,16 +2331,16 @@ ui_show_room_role_list(ProfMucWin *mucwin, muc_role_t role)
             Occupant *occupant = curr_occupant->data;
             if (occupant->role == role) {
                 if (occupant->jid) {
-                    win_save_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", occupant->nick, occupant->jid);
+                    win_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", occupant->nick, occupant->jid);
                 } else {
-                    win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", occupant->nick);
+                    win_vprint(window, '!', NULL, 0, 0, "", "  %s", occupant->nick);
                 }
             }
 
             curr_occupant = g_slist_next(curr_occupant);
         }
 
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "");
     }
 }
 
@@ -2460,34 +2353,34 @@ ui_show_room_affiliation_list(ProfMucWin *mucwin, muc_affiliation_t affiliation)
     if (!occupants) {
         switch (affiliation) {
             case MUC_AFFILIATION_OWNER:
-                win_save_print(window, '!', NULL, 0, 0, "", "No owners found.");
+                win_print(window, '!', NULL, 0, 0, "", "No owners found.");
                 break;
             case MUC_AFFILIATION_ADMIN:
-                win_save_print(window, '!', NULL, 0, 0, "", "No admins found.");
+                win_print(window, '!', NULL, 0, 0, "", "No admins found.");
                 break;
             case MUC_AFFILIATION_MEMBER:
-                win_save_print(window, '!', NULL, 0, 0, "", "No members found.");
+                win_print(window, '!', NULL, 0, 0, "", "No members found.");
                 break;
             case MUC_AFFILIATION_OUTCAST:
-                win_save_print(window, '!', NULL, 0, 0, "", "No outcasts found.");
+                win_print(window, '!', NULL, 0, 0, "", "No outcasts found.");
                 break;
             default:
                 break;
         }
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "");
     } else {
         switch (affiliation) {
             case MUC_AFFILIATION_OWNER:
-                win_save_print(window, '!', NULL, 0, 0, "", "Owners:");
+                win_print(window, '!', NULL, 0, 0, "", "Owners:");
                 break;
             case MUC_AFFILIATION_ADMIN:
-                win_save_print(window, '!', NULL, 0, 0, "", "Admins:");
+                win_print(window, '!', NULL, 0, 0, "", "Admins:");
                 break;
             case MUC_AFFILIATION_MEMBER:
-                win_save_print(window, '!', NULL, 0, 0, "", "Members:");
+                win_print(window, '!', NULL, 0, 0, "", "Members:");
                 break;
             case MUC_AFFILIATION_OUTCAST:
-                win_save_print(window, '!', NULL, 0, 0, "", "Outcasts:");
+                win_print(window, '!', NULL, 0, 0, "", "Outcasts:");
                 break;
             default:
                 break;
@@ -2498,28 +2391,28 @@ ui_show_room_affiliation_list(ProfMucWin *mucwin, muc_affiliation_t affiliation)
             Occupant *occupant = curr_occupant->data;
             if (occupant->affiliation == affiliation) {
                 if (occupant->jid) {
-                    win_save_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", occupant->nick, occupant->jid);
+                    win_vprint(window, '!', NULL, 0, 0, "", "  %s (%s)", occupant->nick, occupant->jid);
                 } else {
-                    win_save_vprint(window, '!', NULL, 0, 0, "", "  %s", occupant->nick);
+                    win_vprint(window, '!', NULL, 0, 0, "", "  %s", occupant->nick);
                 }
             }
 
             curr_occupant = g_slist_next(curr_occupant);
         }
 
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "");
     }
 }
 
 static void
 _ui_handle_form_field(ProfWin *window, char *tag, FormField *field)
 {
-    win_save_vprint(window, '-', NULL, NO_EOL, THEME_AWAY, "", "[%s] ", tag);
-    win_save_vprint(window, '-', NULL, NO_EOL | NO_DATE, 0, "", "%s", field->label);
+    win_vprint(window, '-', NULL, NO_EOL, THEME_AWAY, "", "[%s] ", tag);
+    win_vprint(window, '-', NULL, NO_EOL | NO_DATE, 0, "", "%s", field->label);
     if (field->required) {
-        win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " (required): ");
+        win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " (required): ");
     } else {
-        win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ": ");
+        win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ": ");
     }
 
     GSList *values = field->values;
@@ -2529,113 +2422,113 @@ _ui_handle_form_field(ProfWin *window, char *tag, FormField *field)
     case FIELD_HIDDEN:
         break;
     case FIELD_TEXT_SINGLE:
-        if (curr_value != NULL) {
+        if (curr_value) {
             char *value = curr_value->data;
-            if (value != NULL) {
+            if (value) {
                 if (g_strcmp0(field->var, "muc#roomconfig_roomsecret") == 0) {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", "[hidden]");
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", "[hidden]");
                 } else {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", value);
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", value);
                 }
             }
         }
-        win_save_newline(window);
+        win_newline(window);
         break;
     case FIELD_TEXT_PRIVATE:
-        if (curr_value != NULL) {
+        if (curr_value) {
             char *value = curr_value->data;
-            if (value != NULL) {
-                win_save_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", "[hidden]");
+            if (value) {
+                win_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", "[hidden]");
             }
         }
-        win_save_newline(window);
+        win_newline(window);
         break;
     case FIELD_TEXT_MULTI:
-        win_save_newline(window);
+        win_newline(window);
         int index = 1;
-        while (curr_value != NULL) {
+        while (curr_value) {
             char *value = curr_value->data;
             GString *val_tag = g_string_new("");
             g_string_printf(val_tag, "val%d", index++);
-            win_save_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", val_tag->str, value);
+            win_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", val_tag->str, value);
             g_string_free(val_tag, TRUE);
             curr_value = g_slist_next(curr_value);
         }
         break;
     case FIELD_BOOLEAN:
         if (curr_value == NULL) {
-            win_save_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
+            win_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
         } else {
             char *value = curr_value->data;
             if (value == NULL) {
-                win_save_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
+                win_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
             } else {
                 if (g_strcmp0(value, "0") == 0) {
-                    win_save_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
+                    win_print(window, '-', NULL, NO_DATE, THEME_OFFLINE, "", "FALSE");
                 } else {
-                    win_save_print(window, '-', NULL, NO_DATE, THEME_ONLINE, "", "TRUE");
+                    win_print(window, '-', NULL, NO_DATE, THEME_ONLINE, "", "TRUE");
                 }
             }
         }
         break;
     case FIELD_LIST_SINGLE:
-        if (curr_value != NULL) {
-            win_save_newline(window);
+        if (curr_value) {
+            win_newline(window);
             char *value = curr_value->data;
             GSList *options = field->options;
             GSList *curr_option = options;
-            while (curr_option != NULL) {
+            while (curr_option) {
                 FormOption *option = curr_option->data;
                 if (g_strcmp0(option->value, value) == 0) {
-                    win_save_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", option->value, option->label);
+                    win_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", option->value, option->label);
                 } else {
-                    win_save_vprint(window, '-', NULL, 0, THEME_OFFLINE, "", "  [%s] %s", option->value, option->label);
+                    win_vprint(window, '-', NULL, 0, THEME_OFFLINE, "", "  [%s] %s", option->value, option->label);
                 }
                 curr_option = g_slist_next(curr_option);
             }
         }
         break;
     case FIELD_LIST_MULTI:
-        if (curr_value != NULL) {
-            win_save_newline(window);
+        if (curr_value) {
+            win_newline(window);
             GSList *options = field->options;
             GSList *curr_option = options;
-            while (curr_option != NULL) {
+            while (curr_option) {
                 FormOption *option = curr_option->data;
-                if (g_slist_find_custom(curr_value, option->value, (GCompareFunc)g_strcmp0) != NULL) {
-                    win_save_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", option->value, option->label);
+                if (g_slist_find_custom(curr_value, option->value, (GCompareFunc)g_strcmp0)) {
+                    win_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  [%s] %s", option->value, option->label);
                 } else {
-                    win_save_vprint(window, '-', NULL, 0, THEME_OFFLINE, "", "  [%s] %s", option->value, option->label);
+                    win_vprint(window, '-', NULL, 0, THEME_OFFLINE, "", "  [%s] %s", option->value, option->label);
                 }
                 curr_option = g_slist_next(curr_option);
             }
         }
         break;
     case FIELD_JID_SINGLE:
-        if (curr_value != NULL) {
+        if (curr_value) {
             char *value = curr_value->data;
-            if (value != NULL) {
-                win_save_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", value);
+            if (value) {
+                win_print(window, '-', NULL, NO_DATE | NO_EOL, THEME_ONLINE, "", value);
             }
         }
-        win_save_newline(window);
+        win_newline(window);
         break;
     case FIELD_JID_MULTI:
-        win_save_newline(window);
-        while (curr_value != NULL) {
+        win_newline(window);
+        while (curr_value) {
             char *value = curr_value->data;
-            win_save_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  %s", value);
+            win_vprint(window, '-', NULL, 0, THEME_ONLINE, "", "  %s", value);
             curr_value = g_slist_next(curr_value);
         }
         break;
     case FIELD_FIXED:
-        if (curr_value != NULL) {
+        if (curr_value) {
             char *value = curr_value->data;
-            if (value != NULL) {
-                win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", value);
+            if (value) {
+                win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", value);
             }
         }
-        win_save_newline(window);
+        win_newline(window);
         break;
     default:
         break;
@@ -2646,25 +2539,25 @@ void
 ui_show_form(ProfMucConfWin *confwin)
 {
     ProfWin *window = (ProfWin*) confwin;
-    if (confwin->form->title != NULL) {
-        win_save_print(window, '-', NULL, NO_EOL, 0, "", "Form title: ");
-        win_save_print(window, '-', NULL, NO_DATE, 0, "", confwin->form->title);
+    if (confwin->form->title) {
+        win_print(window, '-', NULL, NO_EOL, 0, "", "Form title: ");
+        win_print(window, '-', NULL, NO_DATE, 0, "", confwin->form->title);
     } else {
-        win_save_vprint(window, '-', NULL, 0, 0, "", "Configuration for room %s.", confwin->roomjid);
+        win_vprint(window, '-', NULL, 0, 0, "", "Configuration for room %s.", confwin->roomjid);
     }
-    win_save_print(window, '-', NULL, 0, 0, "", "");
+    win_print(window, '-', NULL, 0, 0, "", "");
 
     ui_show_form_help(confwin);
 
     GSList *fields = confwin->form->fields;
     GSList *curr_field = fields;
-    while (curr_field != NULL) {
+    while (curr_field) {
         FormField *field = curr_field->data;
 
         if ((g_strcmp0(field->type, "fixed") == 0) && field->values) {
             if (field->values) {
                 char *value = field->values->data;
-                win_save_print(window, '-', NULL, 0, 0, "", value);
+                win_print(window, '-', NULL, 0, 0, "", value);
             }
         } else if (g_strcmp0(field->type, "hidden") != 0 && field->var) {
             char *tag = g_hash_table_lookup(confwin->form->var_to_tag, field->var);
@@ -2680,7 +2573,7 @@ ui_show_form_field(ProfWin *window, DataForm *form, char *tag)
 {
     FormField *field = form_get_field_by_tag(form, tag);
     _ui_handle_form_field(window, tag, field);
-    win_save_println(window, "");
+    win_println(window, "");
 }
 
 void
@@ -2690,16 +2583,14 @@ ui_handle_room_configuration(const char * const roomjid, DataForm *form)
     ProfMucConfWin *confwin = (ProfMucConfWin*)window;
     assert(confwin->memcheck == PROFCONFWIN_MEMCHECK);
 
-    int num = wins_get_num(window);
-    ui_switch_win(num);
-
+    ui_ev_focus_win(window);
     ui_show_form(confwin);
 
-    win_save_print(window, '-', NULL, 0, 0, "", "");
-    win_save_print(window, '-', NULL, 0, 0, "", "Use '/form submit' to save changes.");
-    win_save_print(window, '-', NULL, 0, 0, "", "Use '/form cancel' to cancel changes.");
-    win_save_print(window, '-', NULL, 0, 0, "", "See '/form help' for more information.");
-    win_save_print(window, '-', NULL, 0, 0, "", "");
+    win_print(window, '-', NULL, 0, 0, "", "");
+    win_print(window, '-', NULL, 0, 0, "", "Use '/form submit' to save changes.");
+    win_print(window, '-', NULL, 0, 0, "", "Use '/form cancel' to cancel changes.");
+    win_print(window, '-', NULL, 0, 0, "", "See '/form help' for more information.");
+    win_print(window, '-', NULL, 0, 0, "", "");
 }
 
 void
@@ -2721,7 +2612,7 @@ ui_handle_room_configuration_form_error(const char * const roomjid, const char *
         g_string_append(message_str, message);
     }
 
-    win_save_print(window, '-', NULL, 0, THEME_ERROR, "", message_str->str);
+    win_print(window, '-', NULL, 0, THEME_ERROR, "", message_str->str);
 
     g_string_free(message_str, TRUE);
 }
@@ -2747,11 +2638,11 @@ ui_handle_room_config_submit_result(const char * const roomjid)
         }
 
         if (muc_window) {
-            int num = wins_get_num(muc_window);
-            ui_switch_win(num);
-            win_save_print(muc_window, '!', NULL, 0, THEME_ROOMINFO, "", "Room configuration successful");
+            ui_ev_focus_win((ProfWin*)muc_window);
+            win_print(muc_window, '!', NULL, 0, THEME_ROOMINFO, "", "Room configuration successful");
         } else {
-            ui_switch_win(1);
+            ProfWin *console = wins_get_console();
+            ui_ev_focus_win(console);
             cons_show("Room configuration successful: %s", roomjid);
         }
     } else {
@@ -2776,25 +2667,25 @@ ui_handle_room_config_submit_result_error(const char * const roomjid, const char
 
         if (form_window) {
             if (message) {
-                win_save_vprint(form_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error: %s", message);
+                win_vprint(form_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error: %s", message);
             } else {
-                win_save_print(form_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
+                win_print(form_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
             }
         } else if (muc_window) {
             if (message) {
-                win_save_vprint(muc_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error: %s", message);
+                win_vprint(muc_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error: %s", message);
             } else {
-                win_save_print(muc_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
+                win_print(muc_window, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
             }
         } else {
             if (message) {
-                win_save_vprint(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error for %s: %s", roomjid, message);
+                win_vprint(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error for %s: %s", roomjid, message);
             } else {
-                win_save_vprint(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error for %s", roomjid);
+                win_vprint(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error for %s", roomjid);
             }
         }
     } else {
-        win_save_print(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
+        win_print(console, '!', NULL, 0, THEME_ERROR, "", "Configuration error");
     }
 }
 
@@ -2803,17 +2694,17 @@ ui_show_form_field_help(ProfMucConfWin *confwin, char *tag)
 {
     ProfWin *window = (ProfWin*) confwin;
     FormField *field = form_get_field_by_tag(confwin->form, tag);
-    if (field != NULL) {
-        win_save_print(window, '-', NULL, NO_EOL, 0, "", field->label);
+    if (field) {
+        win_print(window, '-', NULL, NO_EOL, 0, "", field->label);
         if (field->required) {
-            win_save_print(window, '-', NULL, NO_DATE, 0, "", " (Required):");
+            win_print(window, '-', NULL, NO_DATE, 0, "", " (Required):");
         } else {
-            win_save_print(window, '-', NULL, NO_DATE, 0, "", ":");
+            win_print(window, '-', NULL, NO_DATE, 0, "", ":");
         }
-        if (field->description != NULL) {
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Description : %s", field->description);
+        if (field->description) {
+            win_vprint(window, '-', NULL, 0, 0, "", "  Description : %s", field->description);
         }
-        win_save_vprint(window, '-', NULL, 0, 0, "", "  Type        : %s", field->type);
+        win_vprint(window, '-', NULL, 0, 0, "", "  Type        : %s", field->type);
 
         int num_values = 0;
         GSList *curr_option = NULL;
@@ -2822,51 +2713,51 @@ ui_show_form_field_help(ProfMucConfWin *confwin, char *tag)
         switch (field->type_t) {
         case FIELD_TEXT_SINGLE:
         case FIELD_TEXT_PRIVATE:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is any text");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is any text");
             break;
         case FIELD_TEXT_MULTI:
             num_values = form_get_value_count(confwin->form, tag);
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is any text");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is any text");
             if (num_values > 0) {
-                win_save_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
-                win_save_vprint(window, '-', NULL, 0, 0, "", "  Where       : <value> between 'val1' and 'val%d'", num_values);
+                win_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
+                win_vprint(window, '-', NULL, 0, 0, "", "  Where       : <value> between 'val1' and 'val%d'", num_values);
             }
             break;
         case FIELD_BOOLEAN:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is either 'on' or 'off'");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is either 'on' or 'off'");
             break;
         case FIELD_LIST_SINGLE:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is one of");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is one of");
             curr_option = field->options;
-            while (curr_option != NULL) {
+            while (curr_option) {
                 option = curr_option->data;
-                win_save_vprint(window, '-', NULL, 0, 0, "", "                  %s", option->value);
+                win_vprint(window, '-', NULL, 0, 0, "", "                  %s", option->value);
                 curr_option = g_slist_next(curr_option);
             }
             break;
         case FIELD_LIST_MULTI:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is one of");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
+            win_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is one of");
             curr_option = field->options;
-            while (curr_option != NULL) {
+            while (curr_option) {
                 option = curr_option->data;
-                win_save_vprint(window, '-', NULL, 0, 0, "", "                  %s", option->value);
+                win_vprint(window, '-', NULL, 0, 0, "", "                  %s", option->value);
                 curr_option = g_slist_next(curr_option);
             }
             break;
         case FIELD_JID_SINGLE:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is a valid Jabber ID");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Set         : /%s <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is a valid Jabber ID");
             break;
         case FIELD_JID_MULTI:
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
-            win_save_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
-            win_save_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is a valid Jabber ID");
+            win_vprint(window, '-', NULL, 0, 0, "", "  Add         : /%s add <value>", tag);
+            win_vprint(window, '-', NULL, 0, 0, "", "  Remove      : /%s remove <value>", tag);
+            win_print(window, '-', NULL, 0, 0, "", "  Where       : <value> is a valid Jabber ID");
             break;
         case FIELD_FIXED:
         case FIELD_UNKNOWN:
@@ -2875,33 +2766,42 @@ ui_show_form_field_help(ProfMucConfWin *confwin, char *tag)
             break;
         }
     } else {
-        win_save_vprint(window, '-', NULL, 0, 0, "", "No such field %s", tag);
+        win_vprint(window, '-', NULL, 0, 0, "", "No such field %s", tag);
     }
 }
 
 void
 ui_show_form_help(ProfMucConfWin *confwin)
 {
-    if (confwin->form->instructions != NULL) {
+    if (confwin->form->instructions) {
         ProfWin *window = (ProfWin*) confwin;
-        win_save_print(window, '-', NULL, 0, 0, "", "Supplied instructions:");
-        win_save_print(window, '-', NULL, 0, 0, "", confwin->form->instructions);
-        win_save_print(window, '-', NULL, 0, 0, "", "");
+        win_print(window, '-', NULL, 0, 0, "", "Supplied instructions:");
+        win_print(window, '-', NULL, 0, 0, "", confwin->form->instructions);
+        win_print(window, '-', NULL, 0, 0, "", "");
     }
 }
 
 void
 ui_show_lines(ProfWin *window, const gchar** lines)
 {
-    if (lines != NULL) {
+    if (lines) {
         int i;
         for (i = 0; lines[i] != NULL; i++) {
-            win_save_print(window, '-', NULL, 0, 0, "", lines[i]);
+            win_print(window, '-', NULL, 0, 0, "", lines[i]);
         }
     }
 }
 
 void
+ui_room_update_occupants(const char * const roomjid)
+{
+    ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
+    if (window && win_has_active_subwin(window)) {
+        occupantswin_occupants(roomjid);
+    }
+}
+
+void
 ui_room_show_occupants(const char * const roomjid)
 {
     ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
@@ -2940,65 +2840,34 @@ ui_hide_roster(void)
 }
 
 static void
-_win_handle_switch(const wint_t ch)
-{
-    if (ch == KEY_F(1)) {
-        ui_switch_win(1);
-    } else if (ch == KEY_F(2)) {
-        ui_switch_win(2);
-    } else if (ch == KEY_F(3)) {
-        ui_switch_win(3);
-    } else if (ch == KEY_F(4)) {
-        ui_switch_win(4);
-    } else if (ch == KEY_F(5)) {
-        ui_switch_win(5);
-    } else if (ch == KEY_F(6)) {
-        ui_switch_win(6);
-    } else if (ch == KEY_F(7)) {
-        ui_switch_win(7);
-    } else if (ch == KEY_F(8)) {
-        ui_switch_win(8);
-    } else if (ch == KEY_F(9)) {
-        ui_switch_win(9);
-    } else if (ch == KEY_F(10)) {
-        ui_switch_win(0);
-    }
-}
-
-static void
-_win_show_history(int win_index, const char * const contact)
-{
-    ProfWin *window = wins_get_by_num(win_index);
-    if (window->type == WIN_CHAT) {
-        ProfChatWin *chatwin = (ProfChatWin*) window;
-        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
-        if (!chatwin->history_shown) {
-            Jid *jid = jid_create(jabber_get_fulljid());
-            GSList *history = chat_log_get_previous(jid->barejid, contact);
-            jid_destroy(jid);
-            GSList *curr = history;
-            while (curr != NULL) {
-                char *line = curr->data;
-                // entry
-                if (line[2] == ':') {
-                    char hh[3]; memcpy(hh, &line[0], 2); hh[2] = '\0'; int ihh = atoi(hh);
-                    char mm[3]; memcpy(mm, &line[3], 2); mm[2] = '\0'; int imm = atoi(mm);
-                    char ss[3]; memcpy(ss, &line[6], 2); ss[2] = '\0'; int iss = atoi(ss);
-                    GDateTime *time = g_date_time_new_local(2000, 1, 1, ihh, imm, iss);
-                    GTimeVal tv;
-                    g_date_time_to_timeval(time, &tv);
-                    win_save_print(window, '-', &tv, NO_COLOUR_DATE, 0, "", curr->data+11);
-                    g_date_time_unref(time);
-                // header
-                } else {
-                    win_save_print(window, '-', NULL, 0, 0, "", curr->data);
-                }
-                curr = g_slist_next(curr);
+_win_show_history(ProfChatWin *chatwin, const char * const contact)
+{
+    if (!chatwin->history_shown) {
+        Jid *jid = jid_create(jabber_get_fulljid());
+        GSList *history = chat_log_get_previous(jid->barejid, contact);
+        jid_destroy(jid);
+        GSList *curr = history;
+        while (curr) {
+            char *line = curr->data;
+            // entry
+            if (line[2] == ':') {
+                char hh[3]; memcpy(hh, &line[0], 2); hh[2] = '\0'; int ihh = atoi(hh);
+                char mm[3]; memcpy(mm, &line[3], 2); mm[2] = '\0'; int imm = atoi(mm);
+                char ss[3]; memcpy(ss, &line[6], 2); ss[2] = '\0'; int iss = atoi(ss);
+                GDateTime *time = g_date_time_new_local(2000, 1, 1, ihh, imm, iss);
+                GTimeVal tv;
+                g_date_time_to_timeval(time, &tv);
+                win_print((ProfWin*)chatwin, '-', &tv, NO_COLOUR_DATE, 0, "", curr->data+11);
+                g_date_time_unref(time);
+            // header
+            } else {
+                win_print((ProfWin*)chatwin, '-', NULL, 0, 0, "", curr->data);
             }
-            chatwin->history_shown = TRUE;
-
-            g_slist_free_full(history, free);
+            curr = g_slist_next(curr);
         }
+        chatwin->history_shown = TRUE;
+
+        g_slist_free_full(history, free);
     }
 }
 
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index 85ddc79a..caea8ea9 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -1,7 +1,7 @@
 /*
  * inputwin.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -38,6 +38,11 @@
 #include <stdlib.h>
 #include <string.h>
 #include <wchar.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
 
 #ifdef HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
@@ -50,7 +55,6 @@
 #include "config/accounts.h"
 #include "config/preferences.h"
 #include "config/theme.h"
-#include "tools/history.h"
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
@@ -59,39 +63,48 @@
 #include "ui/statusbar.h"
 #include "ui/inputwin.h"
 #include "ui/windows.h"
+#include "event/ui_events.h"
 #include "xmpp/xmpp.h"
 
-#define _inp_win_update_virtual() pnoutrefresh(inp_win, 0, pad_start, rows-1, 0, rows-1, cols-1)
-
-#define KEY_CTRL_A 0001
-#define KEY_CTRL_B 0002
-#define KEY_CTRL_D 0004
-#define KEY_CTRL_E 0005
-#define KEY_CTRL_F 0006
-#define KEY_CTRL_N 0016
-#define KEY_CTRL_P 0020
-#define KEY_CTRL_U 0025
-#define KEY_CTRL_W 0027
-
-#define MAX_HISTORY 100
-#define INP_WIN_MAX 1000
-
 static WINDOW *inp_win;
-static History history;
-
-static char input[INP_WIN_MAX];
-static int input_len_bytes;
-
 static int pad_start = 0;
-static int rows, cols;
 
-static int _handle_edit(int key_type, const wint_t ch);
-static int _handle_alt_key(int key);
-static void _handle_backspace(void);
-static int _printable(const wint_t ch);
-static void _clear_input(void);
-static void _go_to_end(void);
-static void _delete_previous_word(void);
+static struct timeval p_rl_timeout;
+static gint inp_timeout = 0;
+static gint no_input_count = 0;
+
+static fd_set fds;
+static int r;
+static char *inp_line = NULL;
+static gboolean get_password = FALSE;
+
+static void _inp_win_update_virtual(void);
+static int _inp_printable(const wint_t ch);
+static void _inp_win_handle_scroll(void);
+static int _inp_offset_to_col(char *str, int offset);
+static void _inp_write(char *line, int offset);
+
+static int _inp_rl_getc(FILE *stream);
+static void _inp_rl_linehandler(char *line);
+static int _inp_rl_tab_handler(int count, int key);
+static int _inp_rl_clear_handler(int count, int key);
+static int _inp_rl_win1_handler(int count, int key);
+static int _inp_rl_win2_handler(int count, int key);
+static int _inp_rl_win3_handler(int count, int key);
+static int _inp_rl_win4_handler(int count, int key);
+static int _inp_rl_win5_handler(int count, int key);
+static int _inp_rl_win6_handler(int count, int key);
+static int _inp_rl_win7_handler(int count, int key);
+static int _inp_rl_win8_handler(int count, int key);
+static int _inp_rl_win9_handler(int count, int key);
+static int _inp_rl_win0_handler(int count, int key);
+static int _inp_rl_altleft_handler(int count, int key);
+static int _inp_rl_altright_handler(int count, int key);
+static int _inp_rl_pageup_handler(int count, int key);
+static int _inp_rl_pagedown_handler(int count, int key);
+static int _inp_rl_altpageup_handler(int count, int key);
+static int _inp_rl_altpagedown_handler(int count, int key);
+static int _inp_rl_startup_hook(void);
 
 void
 create_input_window(void)
@@ -101,25 +114,84 @@ create_input_window(void)
 #else
     ESCDELAY = 25;
 #endif
-    getmaxyx(stdscr, rows, cols);
+    if (inp_timeout == 1000) {
+        p_rl_timeout.tv_sec = 1;
+        p_rl_timeout.tv_usec = 0;
+    } else {
+        p_rl_timeout.tv_sec = 0;
+        p_rl_timeout.tv_usec = inp_timeout * 1000;
+    }
+
+    rl_readline_name = "profanity";
+    rl_getc_function = _inp_rl_getc;
+    rl_startup_hook = _inp_rl_startup_hook;
+    rl_callback_handler_install(NULL, _inp_rl_linehandler);
+
     inp_win = newpad(1, INP_WIN_MAX);
     wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));;
     keypad(inp_win, TRUE);
     wmove(inp_win, 0, 0);
+
     _inp_win_update_virtual();
-    history = history_new(MAX_HISTORY);
+}
+
+char *
+inp_readline(void)
+{
+    free(inp_line);
+    inp_line = NULL;
+    FD_ZERO(&fds);
+    FD_SET(fileno(rl_instream), &fds);
+    errno = 0;
+    r = select(FD_SETSIZE, &fds, NULL, NULL, &p_rl_timeout);
+    if (r < 0) {
+        char *err_msg = strerror(errno);
+        log_error("Readline failed: %s", err_msg);
+        return NULL;
+    }
+
+    if (FD_ISSET(fileno(rl_instream), &fds)) {
+        rl_callback_read_char();
+
+        if (rl_line_buffer &&
+                rl_line_buffer[0] != '/' &&
+                rl_line_buffer[0] != '\0' &&
+                rl_line_buffer[0] != '\n') {
+            prof_handle_activity();
+        }
+
+        ui_reset_idle_time();
+        _inp_write(rl_line_buffer, rl_point);
+        inp_nonblocking(TRUE);
+    } else {
+        inp_nonblocking(FALSE);
+        prof_handle_idle();
+    }
+
+    if (inp_timeout == 1000) {
+        p_rl_timeout.tv_sec = 1;
+        p_rl_timeout.tv_usec = 0;
+    } else {
+        p_rl_timeout.tv_sec = 0;
+        p_rl_timeout.tv_usec = inp_timeout * 1000;
+    }
+
+    if (inp_line) {
+        return strdup(inp_line);
+    } else {
+        return NULL;
+    }
 }
 
 void
 inp_win_resize(void)
 {
-    int inp_x;
-    getmaxyx(stdscr, rows, cols);
-    inp_x = getcurx(inp_win);
+    int col = getcurx(inp_win);
+    int wcols = getmaxx(stdscr);
 
     // if lost cursor off screen, move contents to show it
-    if (inp_x >= pad_start + cols) {
-        pad_start = inp_x - (cols / 2);
+    if (col >= pad_start + wcols) {
+        pad_start = col - (wcols / 2);
         if (pad_start < 0) {
             pad_start = 0;
         }
@@ -130,127 +202,60 @@ inp_win_resize(void)
 }
 
 void
-inp_non_block(gint timeout)
-{
-    wtimeout(inp_win, timeout);
-}
-
-void
-inp_block(void)
+inp_nonblocking(gboolean reset)
 {
-    wtimeout(inp_win, -1);
-}
-
-char *
-inp_read(int *key_type, wint_t *ch)
-{
-    int display_size = utf8_display_len(input);
-
-    // echo off, and get some more input
-    noecho();
-    *key_type = wget_wch(inp_win, ch);
-
-    gboolean in_command = FALSE;
-    if ((display_size > 0 && input[0] == '/') ||
-            (display_size == 0 && *ch == '/')) {
-        in_command = TRUE;
+    if (! prefs_get_boolean(PREF_INPBLOCK_DYNAMIC)) {
+        inp_timeout = prefs_get_inpblock();
+        return;
     }
 
-    if (*key_type == ERR) {
-        prof_handle_idle();
-    }
-    if ((*key_type != ERR) && (*key_type != KEY_CODE_YES) && !in_command && _printable(*ch)) {
-        prof_handle_activity();
+    if (reset) {
+        inp_timeout = 0;
+        no_input_count = 0;
     }
 
-    // if it wasn't an arrow key etc
-    if (!_handle_edit(*key_type, *ch)) {
-        if (_printable(*ch) && *key_type != KEY_CODE_YES) {
-            if (input_len_bytes >= INP_WIN_MAX) {
-                *ch = ERR;
-                return NULL;
-            }
+    if (inp_timeout < prefs_get_inpblock()) {
+        no_input_count++;
 
-            int inp_x = getcurx(inp_win);
-
-            // handle insert if not at end of input
-            if (inp_x < display_size) {
-                char bytes[MB_CUR_MAX];
-                size_t utf_len = wcrtomb(bytes, *ch, NULL);
-
-                char *next_ch = g_utf8_offset_to_pointer(input, inp_x);
-                char *offset;
-                for (offset = &input[input_len_bytes - 1]; offset >= next_ch; offset--) {
-                    *(offset + utf_len) = *offset;
-                }
-                int i;
-                for (i = 0; i < utf_len; i++) {
-                     *(next_ch + i) = bytes[i];
-                }
-
-                input_len_bytes += utf_len;
-                input[input_len_bytes] = '\0';
-                waddstr(inp_win, next_ch);
-                wmove(inp_win, 0, inp_x + 1);
-
-                if (inp_x - pad_start > cols-3) {
-                    pad_start++;
-                    _inp_win_update_virtual();
-                }
-
-            // otherwise just append
-            } else {
-                char bytes[MB_CUR_MAX+1];
-                size_t utf_len = wcrtomb(bytes, *ch, NULL);
-
-                // wcrtomb can return (size_t) -1
-                if (utf_len < MB_CUR_MAX) {
-                    int i;
-                    for (i = 0 ; i < utf_len; i++) {
-                        input[input_len_bytes++] = bytes[i];
-                    }
-                    input[input_len_bytes] = '\0';
-
-                    bytes[utf_len] = '\0';
-                    waddstr(inp_win, bytes);
-                    display_size++;
-
-                    // if gone over screen size follow input
-                    int rows, cols;
-                    getmaxyx(stdscr, rows, cols);
-                    if (display_size - pad_start > cols-2) {
-                        pad_start++;
-                        _inp_win_update_virtual();
-                    }
-                }
-            }
+        if (no_input_count % 10 == 0) {
+            inp_timeout += no_input_count;
 
-            cmd_reset_autocomplete();
+            if (inp_timeout > prefs_get_inpblock()) {
+                inp_timeout = prefs_get_inpblock();
+            }
         }
     }
-
-    echo();
-
-    if (*ch == '\n') {
-        input[input_len_bytes] = '\0';
-        input_len_bytes = 0;
-        return strdup(input);
-    } else {
-        return NULL;
-    }
 }
 
 void
-inp_get_password(char *passwd)
+inp_close(void)
+{
+    rl_callback_handler_remove();
+}
+
+char*
+inp_get_password(void)
 {
-    _clear_input();
+    werase(inp_win);
+    wmove(inp_win, 0, 0);
+    pad_start = 0;
     _inp_win_update_virtual();
     doupdate();
-    noecho();
-    mvwgetnstr(inp_win, 0, 1, passwd, MAX_PASSWORD_SIZE);
-    wmove(inp_win, 0, 0);
-    echo();
+    char *password = NULL;
+    get_password = TRUE;
+    while (!password) {
+        password = inp_readline();
+        ui_update();
+        werase(inp_win);
+        wmove(inp_win, 0, 0);
+        pad_start = 0;
+        _inp_win_update_virtual();
+        doupdate();
+    }
+    get_password = FALSE;
+
     status_bar_clear();
+    return password;
 }
 
 void
@@ -260,525 +265,314 @@ inp_put_back(void)
 }
 
 void
-inp_replace_input(const char * const new_input)
-{
-    strncpy(input, new_input, INP_WIN_MAX);
-    input_len_bytes = strlen(input);
-    inp_win_reset();
-    input[input_len_bytes] = '\0';
-    waddstr(inp_win, input);
-    _go_to_end();
-}
-
-void
-inp_win_reset(void)
+inp_win_clear(void)
 {
-    _clear_input();
+    werase(inp_win);
+    wmove(inp_win, 0, 0);
     pad_start = 0;
     _inp_win_update_virtual();
 }
 
-void
-inp_history_append(char *inp)
+static void
+_inp_win_update_virtual(void)
 {
-    history_append(history, inp);
+    int wrows, wcols;
+    getmaxyx(stdscr, wrows, wcols);
+    pnoutrefresh(inp_win, 0, pad_start, wrows-1, 0, wrows-1, wcols-2);
 }
 
 static void
-_clear_input(void)
+_inp_write(char *line, int offset)
 {
+    int col = _inp_offset_to_col(line, offset);
     werase(inp_win);
-    wmove(inp_win, 0, 0);
+    waddstr(inp_win, line);
+    wmove(inp_win, 0, col);
+    _inp_win_handle_scroll();
+
+    _inp_win_update_virtual();
 }
 
-/*
- * Deal with command editing, return 1 if ch was an edit
- * key press: up, down, left, right or backspace
- * return 0 if it wasn't
- */
 static int
-_handle_edit(int key_type, const wint_t ch)
-{
-    char *prev = NULL;
-    char *next = NULL;
-    int inp_x = getcurx(inp_win);
-    int next_ch;
-    int display_size = utf8_display_len(input);
-
-    // CTRL-LEFT
-    if ((key_type == KEY_CODE_YES) && (ch == 547 || ch == 545 || ch == 544 || ch == 540 || ch == 539) && (inp_x > 0)) {
-        input[input_len_bytes] = '\0';
-        gchar *curr_ch = g_utf8_offset_to_pointer(input, inp_x);
-        curr_ch = g_utf8_find_prev_char(input, curr_ch);
-        gchar *prev_ch;
-        gunichar curr_uni;
-        gunichar prev_uni;
-
-        while (curr_ch != NULL) {
-            curr_uni = g_utf8_get_char(curr_ch);
-
-            if (g_unichar_isspace(curr_uni)) {
-                curr_ch = g_utf8_find_prev_char(input, curr_ch);
-            } else {
-                prev_ch = g_utf8_find_prev_char(input, curr_ch);
-                if (prev_ch == NULL) {
-                    curr_ch = NULL;
-                    break;
-                } else {
-                    prev_uni = g_utf8_get_char(prev_ch);
-                    if (g_unichar_isspace(prev_uni)) {
-                        break;
-                    } else {
-                        curr_ch = prev_ch;
-                    }
-                }
-            }
-        }
+_inp_printable(const wint_t ch)
+{
+    char bytes[MB_CUR_MAX+1];
+    size_t utf_len = wcrtomb(bytes, ch, NULL);
+    bytes[utf_len] = '\0';
+    gunichar unichar = g_utf8_get_char(bytes);
 
-        if (curr_ch == NULL) {
-            inp_x = 0;
-            wmove(inp_win, 0, inp_x);
-        } else {
-            glong offset = g_utf8_pointer_to_offset(input, curr_ch);
-            inp_x = offset;
-            wmove(inp_win, 0, inp_x);
+    return g_unichar_isprint(unichar) && (ch != KEY_MOUSE);
+}
+
+static int
+_inp_offset_to_col(char *str, int offset)
+{
+    int i = 0;
+    int col = 0;
+
+    while (i < offset && str[i] != '\0') {
+        gunichar uni = g_utf8_get_char(&str[i]);
+        size_t ch_len = mbrlen(&str[i], 4, NULL);
+        i += ch_len;
+        col++;
+        if (g_unichar_iswide(uni)) {
+            col++;
         }
+    }
 
-        // if gone off screen to left, jump left (half a screen worth)
-        if (inp_x <= pad_start) {
-            pad_start = pad_start - (cols / 2);
-            if (pad_start < 0) {
-                pad_start = 0;
-            }
+    return col;
+}
 
-            _inp_win_update_virtual();
-        }
-        return 1;
-
-    // CTRL-RIGHT
-    } else if ((key_type == KEY_CODE_YES) && (ch == 562 || ch == 560 || ch == 555 || ch == 559 || ch == 554) && (inp_x < display_size)) {
-        input[input_len_bytes] = '\0';
-        gchar *curr_ch = g_utf8_offset_to_pointer(input, inp_x);
-        gchar *next_ch = g_utf8_find_next_char(curr_ch, NULL);
-        gunichar curr_uni;
-        gunichar next_uni;
-        gboolean moved = FALSE;
-
-        while (g_utf8_pointer_to_offset(input, next_ch) < display_size) {
-            curr_uni = g_utf8_get_char(curr_ch);
-            next_uni = g_utf8_get_char(next_ch);
-            curr_ch = next_ch;
-            next_ch = g_utf8_find_next_char(next_ch, NULL);
-
-            if (!g_unichar_isspace(curr_uni) && g_unichar_isspace(next_uni) && moved) {
-                break;
-            } else {
-                moved = TRUE;
-            }
-        }
+static void
+_inp_win_handle_scroll(void)
+{
+    int col = getcurx(inp_win);
+    int wcols = getmaxx(stdscr);
 
-        if (next_ch == NULL) {
-            inp_x = display_size;
-            wmove(inp_win, 0, inp_x);
-        } else {
-            glong offset = g_utf8_pointer_to_offset(input, curr_ch);
-            if (offset == display_size - 1) {
-                inp_x = offset + 1;
-            } else {
-                inp_x = offset;
-            }
-            wmove(inp_win, 0, inp_x);
+    if (col == 0) {
+        pad_start = 0;
+    } else if (col >= pad_start + (wcols -2)) {
+        pad_start = col - (wcols / 2);
+        if (pad_start < 0) {
+            pad_start = 0;
         }
-
-        // if gone off screen to right, jump right (half a screen worth)
-        if (inp_x > pad_start + cols) {
-            pad_start = pad_start + (cols / 2);
-            _inp_win_update_virtual();
+    } else if (col <= pad_start) {
+        pad_start = pad_start - (wcols / 2);
+        if (pad_start < 0) {
+            pad_start = 0;
         }
+    }
+}
 
-        return 1;
-
-    // ALT-LEFT
-    } else if ((key_type == KEY_CODE_YES) && (ch == 537 || ch == 542)) {
-        ui_previous_win();
-        return 1;
-
-    // ALT-RIGHT
-    } else if ((key_type == KEY_CODE_YES) && (ch == 552 || ch == 557)) {
-        ui_next_win();
-        return 1;
-
-    // other editing keys
-    } else {
-        switch(ch) {
-
-        case 27: // ESC
-            // check for ALT-key
-            next_ch = wgetch(inp_win);
-            if (next_ch != ERR) {
-                return _handle_alt_key(next_ch);
-            } else {
-                input_len_bytes = 0;
-                inp_win_reset();
-                return 1;
-            }
-
-        case 127:
-            _handle_backspace();
-            return 1;
-        case KEY_BACKSPACE:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-            _handle_backspace();
-            return 1;
-
-        case KEY_DC: // DEL
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_D:
-            if (inp_x == display_size-1) {
-                gchar *start = g_utf8_substring(input, 0, inp_x);
-                for (input_len_bytes = 0; input_len_bytes < strlen(start); input_len_bytes++) {
-                    input[input_len_bytes] = start[input_len_bytes];
-                }
-                input[input_len_bytes] = '\0';
-
-                g_free(start);
-
-                _clear_input();
-                waddstr(inp_win, input);
-            } else if (inp_x < display_size-1) {
-                gchar *start = g_utf8_substring(input, 0, inp_x);
-                gchar *end = g_utf8_substring(input, inp_x+1, input_len_bytes);
-                GString *new = g_string_new(start);
-                g_string_append(new, end);
-
-                for (input_len_bytes = 0; input_len_bytes < strlen(new->str); input_len_bytes++) {
-                    input[input_len_bytes] = new->str[input_len_bytes];
-                }
-                input[input_len_bytes] = '\0';
-
-                g_free(start);
-                g_free(end);
-                g_string_free(new, FALSE);
-
-                _clear_input();
-                waddstr(inp_win, input);
-                wmove(inp_win, 0, inp_x);
-            }
-            return 1;
-
-        case KEY_LEFT:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_B:
-            if (inp_x > 0) {
-                wmove(inp_win, 0, inp_x-1);
-
-                // current position off screen to left
-                if (inp_x - 1 < pad_start) {
-                    pad_start--;
-                    _inp_win_update_virtual();
-                }
-            }
-            return 1;
-
-        case KEY_RIGHT:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_F:
-            if (inp_x < display_size) {
-                wmove(inp_win, 0, inp_x+1);
-
-                // current position off screen to right
-                if ((inp_x + 1 - pad_start) >= cols) {
-                    pad_start++;
-                    _inp_win_update_virtual();
-                }
-            }
-            return 1;
-
-        case KEY_UP:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_P:
-            input[input_len_bytes] = '\0';
-            prev = history_previous(history, input);
-            if (prev) {
-                inp_replace_input(prev);
-            }
-            return 1;
+// Readline callbacks
 
-        case KEY_DOWN:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_N:
-            input[input_len_bytes] = '\0';
-            next = history_next(history, input);
-            if (next) {
-                inp_replace_input(next);
-            } else if (input_len_bytes != 0) {
-                input[input_len_bytes] = '\0';
-                history_append(history, input);
-                inp_replace_input("");
-            }
-            return 1;
+static int
+_inp_rl_startup_hook(void)
+{
+    rl_bind_keyseq("\\e1", _inp_rl_win1_handler);
+    rl_bind_keyseq("\\e2", _inp_rl_win2_handler);
+    rl_bind_keyseq("\\e3", _inp_rl_win3_handler);
+    rl_bind_keyseq("\\e4", _inp_rl_win4_handler);
+    rl_bind_keyseq("\\e5", _inp_rl_win5_handler);
+    rl_bind_keyseq("\\e6", _inp_rl_win6_handler);
+    rl_bind_keyseq("\\e7", _inp_rl_win7_handler);
+    rl_bind_keyseq("\\e8", _inp_rl_win8_handler);
+    rl_bind_keyseq("\\e9", _inp_rl_win9_handler);
+    rl_bind_keyseq("\\e0", _inp_rl_win0_handler);
+
+    rl_bind_keyseq("\\eOP", _inp_rl_win1_handler);
+    rl_bind_keyseq("\\eOQ", _inp_rl_win2_handler);
+    rl_bind_keyseq("\\eOR", _inp_rl_win3_handler);
+    rl_bind_keyseq("\\eOS", _inp_rl_win4_handler);
+    rl_bind_keyseq("\\e[15~", _inp_rl_win5_handler);
+    rl_bind_keyseq("\\e[17~", _inp_rl_win6_handler);
+    rl_bind_keyseq("\\e[18~", _inp_rl_win7_handler);
+    rl_bind_keyseq("\\e[19~", _inp_rl_win8_handler);
+    rl_bind_keyseq("\\e[20~", _inp_rl_win9_handler);
+    rl_bind_keyseq("\\e[21~", _inp_rl_win0_handler);
+
+    rl_bind_keyseq("\\e[1;9D", _inp_rl_altleft_handler);
+    rl_bind_keyseq("\\e[1;3D", _inp_rl_altleft_handler);
+    rl_bind_keyseq("\\e\\e[D", _inp_rl_altleft_handler);
+
+    rl_bind_keyseq("\\e[1;9C", _inp_rl_altright_handler);
+    rl_bind_keyseq("\\e[1;3C", _inp_rl_altright_handler);
+    rl_bind_keyseq("\\e\\e[C", _inp_rl_altright_handler);
+
+    rl_bind_keyseq("\\e\\e[5~", _inp_rl_altpageup_handler);
+    rl_bind_keyseq("\\e[5;3~", _inp_rl_altpageup_handler);
+    rl_bind_keyseq("\\e\\eOy", _inp_rl_altpageup_handler);
+
+    rl_bind_keyseq("\\e\\e[6~", _inp_rl_altpagedown_handler);
+    rl_bind_keyseq("\\e[6;3~", _inp_rl_altpagedown_handler);
+    rl_bind_keyseq("\\e\\eOs", _inp_rl_altpagedown_handler);
+
+    rl_bind_keyseq("\\e[5~", _inp_rl_pageup_handler);
+    rl_bind_keyseq("\\eOy", _inp_rl_pageup_handler);
+    rl_bind_keyseq("\\e[6~", _inp_rl_pagedown_handler);
+    rl_bind_keyseq("\\eOs", _inp_rl_pagedown_handler);
+
+    rl_bind_key('\t', _inp_rl_tab_handler);
+    rl_bind_key(CTRL('L'), _inp_rl_clear_handler);
+
+    return 0;
+}
 
-        case KEY_HOME:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_A:
-            wmove(inp_win, 0, 0);
-            pad_start = 0;
-            _inp_win_update_virtual();
-            return 1;
+static void
+_inp_rl_linehandler(char *line)
+{
+    if (line && *line) {
+        if (!get_password) {
+            add_history(line);
+        }
+    }
+    inp_line = line;
+}
 
-        case KEY_END:
-            if (key_type != KEY_CODE_YES) {
-                return 0;
-            }
-        case KEY_CTRL_E:
-            _go_to_end();
-            return 1;
-
-        case 9: // tab
-            if (input_len_bytes != 0) {
-                input[input_len_bytes] = '\0';
-                if ((strncmp(input, "/", 1) != 0) && (ui_current_win_type() == WIN_MUC)) {
-                    char *result = muc_autocomplete(input);
-                    if (result) {
-                        inp_replace_input(result);
-                        free(result);
-                    }
-                } else if (strncmp(input, "/", 1) == 0) {
-                    char *result = cmd_autocomplete(input);
-                    if (result) {
-                        inp_replace_input(result);
-                        free(result);
-                    }
-                }
-            }
-            return 1;
+static int
+_inp_rl_getc(FILE *stream)
+{
+    int ch = rl_getc(stream);
+    if (_inp_printable(ch)) {
+        cmd_reset_autocomplete();
+    }
+    return ch;
+}
 
-        case KEY_CTRL_W:
-            _delete_previous_word();
-            return 1;
-            break;
+static int
+_inp_rl_clear_handler(int count, int key)
+{
+    ui_clear_current();
+    return 0;
+}
 
-        case KEY_CTRL_U:
-            while (getcurx(inp_win) > 0) {
-                _delete_previous_word();
-            }
-            return 1;
-            break;
+static int
+_inp_rl_tab_handler(int count, int key)
+{
+    if (rl_point != rl_end || !rl_line_buffer) {
+        return 0;
+    }
 
-        default:
-            return 0;
+    if ((strncmp(rl_line_buffer, "/", 1) != 0) && (ui_current_win_type() == WIN_MUC)) {
+        char *result = muc_autocomplete(rl_line_buffer);
+        if (result) {
+            rl_replace_line(result, 0);
+            rl_point = rl_end;
+        }
+    } else if (strncmp(rl_line_buffer, "/", 1) == 0) {
+        char *result = cmd_autocomplete(rl_line_buffer);
+        if (result) {
+            rl_replace_line(result, 0);
+            rl_point = rl_end;
         }
     }
+
+    return 0;
 }
 
 static void
-_handle_backspace(void)
-{
-    int inp_x = getcurx(inp_win);
-    int display_size = utf8_display_len(input);
-    roster_reset_search_attempts();
-    if (display_size > 0) {
-
-        // if at end, delete last char
-        if (inp_x >= display_size) {
-            gchar *start = g_utf8_substring(input, 0, inp_x-1);
-            for (input_len_bytes = 0; input_len_bytes < strlen(start); input_len_bytes++) {
-                input[input_len_bytes] = start[input_len_bytes];
-            }
-            input[input_len_bytes] = '\0';
-
-            g_free(start);
+_go_to_win(int i)
+{
+    ProfWin *window = wins_get_by_num(i);
+    if (window) {
+        ui_ev_focus_win(window);
+    }
+}
 
-            _clear_input();
-            waddstr(inp_win, input);
-            wmove(inp_win, 0, inp_x -1);
+static int
+_inp_rl_win1_handler(int count, int key)
+{
+    _go_to_win(1);
+    return 0;
+}
 
-        // if in middle, delete and shift chars left
-        } else if (inp_x > 0 && inp_x < display_size) {
-            gchar *start = g_utf8_substring(input, 0, inp_x - 1);
-            gchar *end = g_utf8_substring(input, inp_x, input_len_bytes);
-            GString *new = g_string_new(start);
-            g_string_append(new, end);
+static int
+_inp_rl_win2_handler(int count, int key)
+{
+    _go_to_win(2);
+    return 0;
+}
 
-            for (input_len_bytes = 0; input_len_bytes < strlen(new->str); input_len_bytes++) {
-                input[input_len_bytes] = new->str[input_len_bytes];
-            }
-            input[input_len_bytes] = '\0';
+static int
+_inp_rl_win3_handler(int count, int key)
+{
+    _go_to_win(3);
+    return 0;
+}
 
-            g_free(start);
-            g_free(end);
-            g_string_free(new, FALSE);
+static int
+_inp_rl_win4_handler(int count, int key)
+{
+    _go_to_win(4);
+    return 0;
+}
 
-            _clear_input();
-            waddstr(inp_win, input);
-            wmove(inp_win, 0, inp_x -1);
-        }
+static int
+_inp_rl_win5_handler(int count, int key)
+{
+    _go_to_win(5);
+    return 0;
+}
 
-        // if gone off screen to left, jump left (half a screen worth)
-        if (inp_x <= pad_start) {
-            pad_start = pad_start - (cols / 2);
-            if (pad_start < 0) {
-                pad_start = 0;
-            }
+static int
+_inp_rl_win6_handler(int count, int key)
+{
+    _go_to_win(6);
+    return 0;
+}
 
-            _inp_win_update_virtual();
-        }
-    }
+static int
+_inp_rl_win7_handler(int count, int key)
+{
+    _go_to_win(7);
+    return 0;
+}
 
+static int
+_inp_rl_win8_handler(int count, int key)
+{
+    _go_to_win(8);
+    return 0;
 }
 
 static int
-_handle_alt_key(int key)
-{
-    switch (key)
-    {
-        case '1':
-            ui_switch_win(1);
-            break;
-        case '2':
-            ui_switch_win(2);
-            break;
-        case '3':
-            ui_switch_win(3);
-            break;
-        case '4':
-            ui_switch_win(4);
-            break;
-        case '5':
-            ui_switch_win(5);
-            break;
-        case '6':
-            ui_switch_win(6);
-            break;
-        case '7':
-            ui_switch_win(7);
-            break;
-        case '8':
-            ui_switch_win(8);
-            break;
-        case '9':
-            ui_switch_win(9);
-            break;
-        case '0':
-            ui_switch_win(0);
-            break;
-        case KEY_LEFT:
-            ui_previous_win();
-            break;
-        case KEY_RIGHT:
-            ui_next_win();
-            break;
-        case 263:
-        case 127:
-            _delete_previous_word();
-            break;
-        default:
-            break;
-    }
-    return 1;
+_inp_rl_win9_handler(int count, int key)
+{
+    _go_to_win(9);
+    return 0;
 }
 
-static void
-_delete_previous_word(void)
-{
-    int end_del = getcurx(inp_win);
-    int start_del = end_del;
-
-    input[input_len_bytes] = '\0';
-    gchar *curr_ch = g_utf8_offset_to_pointer(input, end_del);
-    curr_ch = g_utf8_find_prev_char(input, curr_ch);
-    gchar *prev_ch;
-    gunichar curr_uni;
-    gunichar prev_uni;
-
-    while (curr_ch != NULL) {
-        curr_uni = g_utf8_get_char(curr_ch);
-
-        if (g_unichar_isspace(curr_uni)) {
-            curr_ch = g_utf8_find_prev_char(input, curr_ch);
-        } else {
-            prev_ch = g_utf8_find_prev_char(input, curr_ch);
-            if (prev_ch == NULL) {
-                curr_ch = NULL;
-                break;
-            } else {
-                prev_uni = g_utf8_get_char(prev_ch);
-                if (g_unichar_isspace(prev_uni)) {
-                    break;
-                } else {
-                    curr_ch = prev_ch;
-                }
-            }
-        }
-    }
+static int
+_inp_rl_win0_handler(int count, int key)
+{
+    _go_to_win(0);
+    return 0;
+}
 
-    if (curr_ch == NULL) {
-        start_del = 0;
-    } else {
-        start_del = g_utf8_pointer_to_offset(input, curr_ch);
+static int
+_inp_rl_altleft_handler(int count, int key)
+{
+    ProfWin *window = wins_get_previous();
+    if (window) {
+        ui_ev_focus_win(window);
     }
+    return 0;
+}
 
-    gint len = g_utf8_strlen(input, -1);
-    gchar *start_string = g_utf8_substring(input, 0, start_del);
-    gchar *end_string = g_utf8_substring(input, end_del, len);
-
-    int i;
-    for (i = 0; i < strlen(start_string); i++) {
-        input[i] = start_string[i];
-    }
-    for (i = 0; i < strlen(end_string); i++) {
-        input[strlen(start_string)+i] = end_string[i];
+static int
+_inp_rl_altright_handler(int count, int key)
+{
+    ProfWin *window = wins_get_next();
+    if (window) {
+        ui_ev_focus_win(window);
     }
+    return 0;
+}
 
-    input_len_bytes = strlen(start_string)+i;
-    input[input_len_bytes] = '\0';
-
-    _clear_input();
-    waddstr(inp_win, input);
-    wmove(inp_win, 0, start_del);
-
-    // if gone off screen to left, jump left (half a screen worth)
-    if (start_del <= pad_start) {
-        pad_start = pad_start - (cols / 2);
-        if (pad_start < 0) {
-            pad_start = 0;
-        }
+static int
+_inp_rl_pageup_handler(int count, int key)
+{
+    ui_page_up();
+    return 0;
+}
 
-        _inp_win_update_virtual();
-    }
+static int
+_inp_rl_pagedown_handler(int count, int key)
+{
+    ui_page_down();
+    return 0;
 }
 
-static void
-_go_to_end(void)
+static int
+_inp_rl_altpageup_handler(int count, int key)
 {
-    int display_size = utf8_display_len(input);
-    wmove(inp_win, 0, display_size);
-    if (display_size > cols-2) {
-        pad_start = display_size - cols + 1;
-        _inp_win_update_virtual();
-    }
+    ui_subwin_page_up();
+    return 0;
 }
 
 static int
-_printable(const wint_t ch)
+_inp_rl_altpagedown_handler(int count, int key)
 {
-    char bytes[MB_CUR_MAX+1];
-    size_t utf_len = wcrtomb(bytes, ch, NULL);
-    bytes[utf_len] = '\0';
-    gunichar unichar = g_utf8_get_char(bytes);
-    return g_unichar_isprint(unichar) && (ch != KEY_MOUSE);
+    ui_subwin_page_down();
+    return 0;
 }
diff --git a/src/ui/inputwin.h b/src/ui/inputwin.h
index 39fde720..f49a6a76 100644
--- a/src/ui/inputwin.h
+++ b/src/ui/inputwin.h
@@ -1,7 +1,7 @@
 /*
  * inputwin.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -35,15 +35,17 @@
 #ifndef UI_INPUTWIN_H
 #define UI_INPUTWIN_H
 
+#include <glib.h>
+
+#define INP_WIN_MAX 1000
+
 void create_input_window(void);
-char* inp_read(int *key_type, wint_t *ch);
-void inp_win_reset(void);
+char* inp_readline(void);
+void inp_nonblocking(gboolean reset);
+void inp_close(void);
+void inp_win_clear(void);
 void inp_win_resize(void);
 void inp_put_back(void);
-void inp_non_block(gint);
-void inp_block(void);
-void inp_get_password(char *passwd);
-void inp_replace_input(const char * const new_input);
-void inp_history_append(char *inp);
+char* inp_get_password(void);
 
 #endif
diff --git a/src/ui/notifier.c b/src/ui/notifier.c
index 7ca8f705..76290daf 100644
--- a/src/ui/notifier.c
+++ b/src/ui/notifier.c
@@ -1,7 +1,7 @@
 /*
  * notifier.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -48,10 +48,10 @@
 #include "log.h"
 #include "muc.h"
 #include "ui/ui.h"
+#include "ui/windows.h"
 #include "config/preferences.h"
 
-static void _notify(const char * const message, int timeout,
-    const char * const category);
+static void _notify(const char * const message, int timeout, const char * const category);
 
 static GTimer *remind_timer;
 
@@ -89,7 +89,7 @@ notify_invite(const char * const from, const char * const room,
     g_string_append(message, from);
     g_string_append(message, "\nto: ");
     g_string_append(message, room);
-    if (reason != NULL) {
+    if (reason) {
         g_string_append_printf(message, "\n\"%s\"", reason);
     }
 
@@ -99,17 +99,25 @@ notify_invite(const char * const from, const char * const room,
 }
 
 void
-notify_message(const char * const handle, int win, const char * const text)
+notify_message(ProfWin *window, const char * const name, const char * const text)
 {
-    GString *message = g_string_new("");
-    g_string_append_printf(message, "%s (win %d)", handle, win);
-    if (text != NULL) {
-        g_string_append_printf(message, "\n%s", text);
+    int num = wins_get_num(window);
+    if (num == 10) {
+        num = 0;
     }
 
-    _notify(message->str, 10000, "incoming message");
+    gboolean is_current = wins_is_current(window);
+    if (!is_current || (is_current && prefs_get_boolean(PREF_NOTIFY_MESSAGE_CURRENT)) ) {
+        GString *message = g_string_new("");
+        g_string_append_printf(message, "%s (win %d)", name, num);
 
-    g_string_free(message, TRUE);
+        if (prefs_get_boolean(PREF_NOTIFY_MESSAGE_TEXT) && text) {
+            g_string_append_printf(message, "\n%s", text);
+        }
+
+        _notify(message->str, 10000, "incoming message");
+        g_string_free(message, TRUE);
+    }
 }
 
 void
@@ -117,7 +125,7 @@ notify_room_message(const char * const handle, const char * const room, int win,
 {
     GString *message = g_string_new("");
     g_string_append_printf(message, "%s in %s (win %d)", handle, room, win);
-    if (text != NULL) {
+    if (text) {
         g_string_append_printf(message, "\n%s", text);
     }
 
@@ -243,10 +251,9 @@ _notify(const char * const message, int timeout,
     Shell_NotifyIcon(NIM_MODIFY, &nid);
 #endif
 #ifdef HAVE_OSXNOTIFY
-    GString *notify_command = g_string_new("terminal-notifier -title \"Profanity\" -message \"");
+    GString *notify_command = g_string_new("terminal-notifier -title \"Profanity\" -message '");
 
-    char *escaped_double = str_replace(message, "\"", "\\\"");
-    char *escaped_single = str_replace(escaped_double, "`", "\\`");
+    char *escaped_single = str_replace(message, "'", "'\\''");
 
     if (escaped_single[0] == '<') {
         g_string_append(notify_command, "\\<");
@@ -264,8 +271,7 @@ _notify(const char * const message, int timeout,
         g_string_append(notify_command, escaped_single);
     }
 
-    g_string_append(notify_command, "\"");
-    free(escaped_double);
+    g_string_append(notify_command, "'");
     free(escaped_single);
 
     char *term_name = getenv("TERM_PROGRAM");
@@ -276,7 +282,7 @@ _notify(const char * const message, int timeout,
         app_id = "com.googlecode.iterm2";
     }
 
-    if (app_id != NULL) {
+    if (app_id) {
         g_string_append(notify_command, " -sender ");
         g_string_append(notify_command, app_id);
     }
@@ -288,4 +294,4 @@ _notify(const char * const message, int timeout,
 
     g_string_free(notify_command, TRUE);
 #endif
-}
\ No newline at end of file
+}
diff --git a/src/ui/occupantswin.c b/src/ui/occupantswin.c
index 429c2fdf..bba9d0b9 100644
--- a/src/ui/occupantswin.c
+++ b/src/ui/occupantswin.c
@@ -1,7 +1,7 @@
 /*
  * occupantswin.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -40,7 +40,7 @@
 #include "config/preferences.h"
 
 static void
-_occuptantswin_occupant(ProfLayoutSplit *layout, Occupant *occupant)
+_occuptantswin_occupant(ProfLayoutSplit *layout, Occupant *occupant, gboolean showjid)
 {
     const char *presence_str = string_from_resource_presence(occupant->presence);
     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
@@ -51,6 +51,13 @@ _occuptantswin_occupant(ProfLayoutSplit *layout, Occupant *occupant)
     win_printline_nowrap(layout->subwin, msg->str);
     g_string_free(msg, TRUE);
 
+    if (showjid && occupant->jid) {
+        GString *msg = g_string_new("     ");
+        g_string_append(msg, occupant->jid);
+        win_printline_nowrap(layout->subwin, msg->str);
+        g_string_free(msg, TRUE);
+    }
+
     wattroff(layout->subwin, theme_attrs(presence_colour));
 }
 
@@ -74,7 +81,7 @@ occupantswin_occupants(const char * const roomjid)
                 while (roster_curr) {
                     Occupant *occupant = roster_curr->data;
                     if (occupant->role == MUC_ROLE_MODERATOR) {
-                        _occuptantswin_occupant(layout, occupant);
+                        _occuptantswin_occupant(layout, occupant, mucwin->showjid);
                     }
                     roster_curr = g_list_next(roster_curr);
                 }
@@ -86,7 +93,7 @@ occupantswin_occupants(const char * const roomjid)
                 while (roster_curr) {
                     Occupant *occupant = roster_curr->data;
                     if (occupant->role == MUC_ROLE_PARTICIPANT) {
-                        _occuptantswin_occupant(layout, occupant);
+                        _occuptantswin_occupant(layout, occupant, mucwin->showjid);
                     }
                     roster_curr = g_list_next(roster_curr);
                 }
@@ -98,7 +105,7 @@ occupantswin_occupants(const char * const roomjid)
                 while (roster_curr) {
                     Occupant *occupant = roster_curr->data;
                     if (occupant->role == MUC_ROLE_VISITOR) {
-                        _occuptantswin_occupant(layout, occupant);
+                        _occuptantswin_occupant(layout, occupant, mucwin->showjid);
                     }
                     roster_curr = g_list_next(roster_curr);
                 }
@@ -109,7 +116,7 @@ occupantswin_occupants(const char * const roomjid)
                 GList *roster_curr = occupants;
                 while (roster_curr) {
                     Occupant *occupant = roster_curr->data;
-                    _occuptantswin_occupant(layout, occupant);
+                    _occuptantswin_occupant(layout, occupant, mucwin->showjid);
                     roster_curr = g_list_next(roster_curr);
                 }
             }
diff --git a/src/ui/rosterwin.c b/src/ui/rosterwin.c
index 828ed429..763490c3 100644
--- a/src/ui/rosterwin.c
+++ b/src/ui/rosterwin.c
@@ -1,7 +1,7 @@
 /*
  * rosterwin.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c
index 2ef20913..581e63df 100644
--- a/src/ui/statusbar.c
+++ b/src/ui/statusbar.c
@@ -1,7 +1,7 @@
 /*
  * statusbar.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -48,6 +48,7 @@
 #include "ui/ui.h"
 #include "ui/statusbar.h"
 #include "ui/inputwin.h"
+#include "config/preferences.h"
 
 #define TIME_CHECK 60000000
 
@@ -94,7 +95,7 @@ create_status_bar(void)
     mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
     wattroff(status_bar, bracket_attrs);
 
-    if (last_time != NULL) {
+    if (last_time) {
         g_date_time_unref(last_time);
     }
     last_time = g_date_time_new_now_local();
@@ -126,10 +127,17 @@ status_bar_resize(void)
     mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
     wattroff(status_bar, bracket_attrs);
 
-    if (message != NULL) {
-        mvwprintw(status_bar, 0, 10, message);
+    if (message) {
+        char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
+        if (g_strcmp0(time_pref, "minutes") == 0) {
+            mvwprintw(status_bar, 0, 10, message);
+        } else if (g_strcmp0(time_pref, "seconds") == 0) {
+            mvwprintw(status_bar, 0, 13, message);
+        } else {
+            mvwprintw(status_bar, 0, 1, message);
+        }
     }
-    if (last_time != NULL) {
+    if (last_time) {
         g_date_time_unref(last_time);
     }
     last_time = g_date_time_new_now_local();
@@ -192,7 +200,7 @@ status_bar_inactive(const int win)
             is_new[11] = TRUE;
             _mark_new(11);
 
-        // still have active winsows
+        // still have active windows
         } else if (g_hash_table_size(remaining_active) != 0) {
             is_active[11] = TRUE;
             is_new[11] = FALSE;
@@ -241,7 +249,7 @@ status_bar_active(const int win)
             _mark_active(11);
         }
 
-    // visible winsow indicators
+    // visible window indicators
     } else {
         is_active[true_win] = TRUE;
         is_new[true_win] = FALSE;
@@ -289,11 +297,19 @@ status_bar_print_message(const char * const msg)
 {
     werase(status_bar);
 
-    if (message != NULL) {
+    if (message) {
         free(message);
     }
     message = strdup(msg);
-    mvwprintw(status_bar, 0, 10, message);
+
+    char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
+    if (g_strcmp0(time_pref, "minutes") == 0) {
+        mvwprintw(status_bar, 0, 10, message);
+    } else if (g_strcmp0(time_pref, "seconds") == 0) {
+        mvwprintw(status_bar, 0, 13, message);
+    } else {
+        mvwprintw(status_bar, 0, 1, message);
+    }
 
     int cols = getmaxx(stdscr);
     int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
@@ -309,7 +325,7 @@ status_bar_print_message(const char * const msg)
 void
 status_bar_clear(void)
 {
-    if (message != NULL) {
+    if (message) {
         free(message);
         message = NULL;
     }
@@ -330,7 +346,7 @@ status_bar_clear(void)
 void
 status_bar_clear_message(void)
 {
-    if (message != NULL) {
+    if (message) {
         free(message);
         message = NULL;
     }
@@ -412,23 +428,37 @@ _mark_inactive(int num)
 static void
 _status_bar_draw(void)
 {
-    if (last_time != NULL) {
+    if (last_time) {
         g_date_time_unref(last_time);
     }
     last_time = g_date_time_new_now_local();
-    gchar *date_fmt = g_date_time_format(last_time, "%H:%M");
-    assert(date_fmt != NULL);
 
     int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
 
-    wattron(status_bar, bracket_attrs);
-    mvwaddch(status_bar, 0, 1, '[');
-    wattroff(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, 2, date_fmt);
-    wattron(status_bar, bracket_attrs);
-    mvwaddch(status_bar, 0, 7, ']');
-    wattroff(status_bar, bracket_attrs);
-    g_free(date_fmt);
+    char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
+    if (g_strcmp0(time_pref, "minutes") == 0) {
+        gchar *date_fmt = g_date_time_format(last_time, "%H:%M");
+        assert(date_fmt != NULL);
+        wattron(status_bar, bracket_attrs);
+        mvwaddch(status_bar, 0, 1, '[');
+        wattroff(status_bar, bracket_attrs);
+        mvwprintw(status_bar, 0, 2, date_fmt);
+        wattron(status_bar, bracket_attrs);
+        mvwaddch(status_bar, 0, 7, ']');
+        wattroff(status_bar, bracket_attrs);
+        g_free(date_fmt);
+    } else if (g_strcmp0(time_pref, "seconds") == 0) {
+        gchar *date_fmt = g_date_time_format(last_time, "%H:%M:%S");
+        assert(date_fmt != NULL);
+        wattron(status_bar, bracket_attrs);
+        mvwaddch(status_bar, 0, 1, '[');
+        wattroff(status_bar, bracket_attrs);
+        mvwprintw(status_bar, 0, 2, date_fmt);
+        wattron(status_bar, bracket_attrs);
+        mvwaddch(status_bar, 0, 10, ']');
+        wattroff(status_bar, bracket_attrs);
+        g_free(date_fmt);
+    }
 
     _update_win_statuses();
     wnoutrefresh(status_bar);
diff --git a/src/ui/statusbar.h b/src/ui/statusbar.h
index 5c37867b..7d2c5ea0 100644
--- a/src/ui/statusbar.h
+++ b/src/ui/statusbar.h
@@ -1,7 +1,7 @@
 /*
  * statusbar.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index 326dbf8b..9bb84f9d 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -1,7 +1,7 @@
 /*
  * titlebar.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -80,7 +80,7 @@ title_bar_update_virtual(void)
 {
     ProfWin *window = wins_get_current();
     if (window->type != WIN_CONSOLE) {
-        if (typing_elapsed != NULL) {
+        if (typing_elapsed) {
             gdouble seconds = g_timer_elapsed(typing_elapsed, NULL);
 
             if (seconds >= 10) {
@@ -109,8 +109,11 @@ void
 title_bar_console(void)
 {
     werase(win);
-    typing = FALSE;
+    if (typing_elapsed) {
+        g_timer_destroy(typing_elapsed);
+    }
     typing_elapsed = NULL;
+    typing = FALSE;
 
     _title_bar_draw();
 }
@@ -125,7 +128,7 @@ title_bar_set_presence(contact_presence_t presence)
 void
 title_bar_switch(void)
 {
-    if (typing_elapsed != NULL) {
+    if (typing_elapsed) {
         g_timer_destroy(typing_elapsed);
         typing_elapsed = NULL;
         typing = FALSE;
@@ -138,7 +141,7 @@ void
 title_bar_set_typing(gboolean is_typing)
 {
     if (is_typing) {
-        if (typing_elapsed != NULL) {
+        if (typing_elapsed) {
             g_timer_start(typing_elapsed);
         } else {
             typing_elapsed = g_timer_new();
@@ -249,7 +252,7 @@ _show_privacy(ProfChatWin *chatwin)
 {
     int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
 
-    if (!chatwin->is_otr) {
+    if (chatwin->enc_mode == PROF_ENC_NONE) {
         if (prefs_get_boolean(PREF_OTR_WARN)) {
             int unencrypted_attrs = theme_attrs(THEME_TITLE_UNENCRYPTED);
             wprintw(win, " ");
diff --git a/src/ui/titlebar.h b/src/ui/titlebar.h
index 08a56514..66237f86 100644
--- a/src/ui/titlebar.h
+++ b/src/ui/titlebar.h
@@ -1,7 +1,7 @@
 /*
  * titlebar.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 99e73b4a..0ee21be4 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -1,7 +1,7 @@
 /*
  * ui.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -59,9 +59,8 @@ void ui_close(void);
 void ui_redraw(void);
 void ui_resize(void);
 GSList* ui_get_chat_recipients(void);
-gboolean ui_switch_win(const int i);
-void ui_next_win(void);
-void ui_previous_win(void);
+void ui_switch_win(ProfWin *window);
+void ui_sigwinch_handler(int sig);
 
 void ui_gone_secure(const char * const barejid, gboolean trusted);
 void ui_gone_insecure(const char * const barejid);
@@ -81,10 +80,12 @@ void ui_smp_answer_failure(const char * const barejid);
 void ui_otr_authenticating(const char * const barejid);
 void ui_otr_authetication_waiting(const char * const recipient);
 
+void ui_handle_otr_error(const char * const barejid, const char * const message);
+
 unsigned long ui_get_idle_time(void);
 void ui_reset_idle_time(void);
-void ui_new_chat_win(const char * const barejid);
-void ui_new_private_win(const char * const fulljid);
+ProfPrivateWin* ui_new_private_win(const char * const fulljid);
+ProfChatWin* ui_new_chat_win(const char * const barejid);
 void ui_print_system_msg_from_recipient(const char * const barejid, const char *message);
 gint ui_unread(void);
 void ui_close_connected_win(int index);
@@ -94,7 +95,6 @@ int ui_close_read_wins(void);
 // current window actions
 void ui_clear_current(void);
 win_type_t ui_current_win_type(void);
-int ui_current_win_index(void);
 gboolean ui_current_win_is_otr(void);
 
 ProfChatWin *ui_get_current_chat(void);
@@ -102,27 +102,28 @@ ProfChatWin *ui_get_current_chat(void);
 void ui_current_print_line(const char * const msg, ...);
 void ui_current_print_formatted_line(const char show_char, int attrs, const char * const msg, ...);
 void ui_current_error_line(const char * const msg);
+void ui_win_error_line(ProfWin *window, const char * const msg);
 
 win_type_t ui_win_type(int index);
 void ui_close_win(int index);
-gboolean ui_win_exists(int index);
 int ui_win_unread(int index);
 char * ui_ask_password(void);
 
 void ui_handle_stanza(const char * const msg);
 
 // ui events
+void ui_contact_online(char *barejid, Resource *resource, GDateTime *last_activity);
 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_message_receipt(const char * const barejid, const char * const id);
 
 void ui_disconnected(void);
 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);
-void ui_outgoing_private_msg(const char * const from, const char * const fulljid,
-    const char * const message);
+void ui_outgoing_chat_msg(ProfChatWin *chatwin, const char * const message, char *id);
+void ui_outgoing_chat_msg_carbon(const char * const barejid, const char * const message);
+void ui_outgoing_private_msg(ProfPrivateWin *privwin, const char * const message);
 
 void ui_room_join(const char * const roomjid, gboolean focus);
 void ui_switch_to_room(const char * const roomjid);
@@ -171,6 +172,7 @@ void ui_room_member_nick_change(const char * const roomjid,
 void ui_room_nick_change(const char * const roomjid, const char * const nick);
 void ui_room_member_presence(const char * const roomjid,
     const char * const nick, const char * const show, const char * const status);
+void ui_room_update_occupants(const char * const roomjid);
 void ui_room_show_occupants(const char * const roomjid);
 void ui_room_hide_occupants(const char * const roomjid);
 void ui_show_roster(void);
@@ -218,6 +220,11 @@ void ui_tidy_wins(void);
 void ui_prune_wins(void);
 gboolean ui_swap_wins(int source_win, int target_win);
 
+void ui_page_up(void);
+void ui_page_down(void);
+void ui_subwin_page_up(void);
+void ui_subwin_page_down(void);
+
 void ui_auto_away(void);
 void ui_end_auto_away(void);
 void ui_titlebar_presence(contact_presence_t presence);
@@ -227,9 +234,10 @@ void ui_update_presence(const resource_presence_t resource_presence,
 void ui_about(void);
 void ui_statusbar_new(const int win);
 
-char * ui_readline(void);
+char* ui_readline(void);
 void ui_input_clear(void);
 void ui_input_nonblocking(gboolean);
+void ui_write(char *line, int offset);
 
 void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void));
 
@@ -308,6 +316,8 @@ void cons_outtype_setting(void);
 void cons_intype_setting(void);
 void cons_gone_setting(void);
 void cons_history_setting(void);
+void cons_carbons_setting(void);
+void cons_receipts_setting(void);
 void cons_log_setting(void);
 void cons_chlog_setting(void);
 void cons_grlog_setting(void);
@@ -332,7 +342,7 @@ void notifier_initialise(void);
 void notifier_uninit(void);
 
 void notify_typing(const char * const handle);
-void notify_message(const char * const handle, int win, const char * const text);
+void notify_message(ProfWin *window, const char * const name, const char * const text);
 void notify_room_message(const char * const handle, const char * const room,
     int win, const char * const text);
 void notify_remind(void);
diff --git a/src/ui/window.c b/src/ui/window.c
index 7757fe39..d2462a61 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1,7 +1,7 @@
 /*
  * window.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -38,6 +38,7 @@
 #include <string.h>
 #include <time.h>
 #include <assert.h>
+#include <wchar.h>
 
 #include <glib.h>
 #ifdef HAVE_NCURSESW_NCURSES_H
@@ -59,7 +60,7 @@
 #define CEILING(X) (X-(int)(X) > 0 ? (int)(X+1) : (int)(X))
 
 static void _win_print(ProfWin *window, const char show_char, GDateTime *time,
-    int flags, theme_item_t theme_item, const char * const from, const char * const message);
+    int flags, theme_item_t theme_item, const char * const from, const char * const message, DeliveryReceipt *receipt);
 static void _win_print_wrapped(WINDOW *win, const char * const message);
 
 int
@@ -134,7 +135,7 @@ win_create_chat(const char * const barejid)
 
     new_win->barejid = strdup(barejid);
     new_win->resource_override = NULL;
-    new_win->is_otr = FALSE;
+    new_win->enc_mode = PROF_ENC_NONE;
     new_win->is_trusted = FALSE;
     new_win->history_shown = FALSE;
     new_win->unread = 0;
@@ -177,6 +178,11 @@ win_create_muc(const char * const roomjid)
 
     new_win->roomjid = strdup(roomjid);
     new_win->unread = 0;
+    if (prefs_get_boolean(PREF_OCCUPANTS_JID)) {
+        new_win->showjid = TRUE;
+    } else {
+        new_win->showjid = FALSE;
+    }
 
     new_win->memcheck = PROFMUCWIN_MEMCHECK;
 
@@ -331,12 +337,13 @@ win_free(ProfWin* window)
         buffer_free(window->layout->buffer);
         delwin(window->layout->win);
     }
+    free(window->layout);
 
     if (window->type == WIN_CHAT) {
         ProfChatWin *chatwin = (ProfChatWin*)window;
         free(chatwin->barejid);
         free(chatwin->resource_override);
-        free(chatwin->state);
+        chat_state_free(chatwin->state);
     }
 
     if (window->type == WIN_MUC) {
@@ -359,7 +366,101 @@ win_free(ProfWin* window)
 }
 
 void
-win_handle_page(ProfWin *window, const wint_t ch, const int result)
+win_page_up(ProfWin *window)
+{
+    int rows = getmaxy(stdscr);
+    int y = getcury(window->layout->win);
+    int page_space = rows - 4;
+    int *page_start = &(window->layout->y_pos);
+
+    *page_start -= page_space;
+
+    // went past beginning, show first page
+    if (*page_start < 0)
+        *page_start = 0;
+
+    window->layout->paged = 1;
+    win_update_virtual(window);
+
+    // switch off page if last line and space line visible
+    if ((y) - *page_start == page_space) {
+        window->layout->paged = 0;
+    }
+}
+
+void
+win_page_down(ProfWin *window)
+{
+    int rows = getmaxy(stdscr);
+    int y = getcury(window->layout->win);
+    int page_space = rows - 4;
+    int *page_start = &(window->layout->y_pos);
+
+    *page_start += page_space;
+
+    // only got half a screen, show full screen
+    if ((y - (*page_start)) < page_space)
+        *page_start = y - page_space;
+
+    // went past end, show full screen
+    else if (*page_start >= y)
+        *page_start = y - page_space - 1;
+
+    window->layout->paged = 1;
+    win_update_virtual(window);
+
+    // switch off page if last line and space line visible
+    if ((y) - *page_start == page_space) {
+        window->layout->paged = 0;
+    }
+}
+
+void
+win_sub_page_down(ProfWin *window)
+{
+
+    if (window->layout->type == LAYOUT_SPLIT) {
+        int rows = getmaxy(stdscr);
+        int page_space = rows - 4;
+        ProfLayoutSplit *split_layout = (ProfLayoutSplit*)window->layout;
+        int sub_y = getcury(split_layout->subwin);
+        int *sub_y_pos = &(split_layout->sub_y_pos);
+
+        *sub_y_pos += page_space;
+
+        // only got half a screen, show full screen
+        if ((sub_y- (*sub_y_pos)) < page_space)
+            *sub_y_pos = sub_y - page_space;
+
+        // went past end, show full screen
+        else if (*sub_y_pos >= sub_y)
+            *sub_y_pos = sub_y - page_space - 1;
+
+        win_update_virtual(window);
+    }
+}
+
+void
+win_sub_page_up(ProfWin *window)
+{
+    if (window->layout->type == LAYOUT_SPLIT) {
+        int rows = getmaxy(stdscr);
+        int page_space = rows - 4;
+        ProfLayoutSplit *split_layout = (ProfLayoutSplit*)window->layout;
+        int *sub_y_pos = &(split_layout->sub_y_pos);
+
+        *sub_y_pos -= page_space;
+
+        // went past beginning, show first page
+        if (*sub_y_pos < 0)
+            *sub_y_pos = 0;
+
+        win_update_virtual(window);
+    }
+}
+
+void
+win_mouse(ProfWin *window, const wint_t ch, const int result)
 {
     int rows = getmaxy(stdscr);
     int y = getcury(window->layout->win);
@@ -403,69 +504,6 @@ win_handle_page(ProfWin *window, const wint_t ch, const int result)
             }
         }
     }
-
-    // page up
-    if (ch == KEY_PPAGE) {
-        *page_start -= page_space;
-
-        // went past beginning, show first page
-        if (*page_start < 0)
-            *page_start = 0;
-
-        window->layout->paged = 1;
-        win_update_virtual(window);
-
-    // page down
-    } else if (ch == KEY_NPAGE) {
-        *page_start += page_space;
-
-        // only got half a screen, show full screen
-        if ((y - (*page_start)) < page_space)
-            *page_start = y - page_space;
-
-        // went past end, show full screen
-        else if (*page_start >= y)
-            *page_start = y - page_space - 1;
-
-        window->layout->paged = 1;
-        win_update_virtual(window);
-    }
-
-    // switch off page if last line and space line visible
-    if ((y) - *page_start == page_space) {
-        window->layout->paged = 0;
-    }
-
-    if (window->layout->type == LAYOUT_SPLIT) {
-        ProfLayoutSplit *split_layout = (ProfLayoutSplit*)window->layout;
-        int sub_y = getcury(split_layout->subwin);
-        int *sub_y_pos = &(split_layout->sub_y_pos);
-
-        // alt up arrow
-        if ((result == KEY_CODE_YES) && ((ch == 565) || (ch == 337))) {
-            *sub_y_pos -= page_space;
-
-            // went past beginning, show first page
-            if (*sub_y_pos < 0)
-                *sub_y_pos = 0;
-
-            win_update_virtual(window);
-
-        // alt down arrow
-        } else if ((result == KEY_CODE_YES) && ((ch == 524) || (ch == 336))) {
-            *sub_y_pos += page_space;
-
-            // only got half a screen, show full screen
-            if ((sub_y- (*sub_y_pos)) < page_space)
-                *sub_y_pos = sub_y - page_space;
-
-            // went past end, show full screen
-            else if (*sub_y_pos >= sub_y)
-                *sub_y_pos = sub_y - page_space - 1;
-
-            win_update_virtual(window);
-        }
-    }
 }
 
 void
@@ -515,14 +553,14 @@ win_show_occupant(ProfWin *window, Occupant *occupant)
 
     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
 
-    win_save_print(window, '-', NULL, NO_EOL, presence_colour, "", occupant->nick);
-    win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence_str);
+    win_print(window, '-', NULL, NO_EOL, presence_colour, "", occupant->nick);
+    win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence_str);
 
     if (occupant->status) {
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", occupant->status);
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", occupant->status);
     }
 
-    win_save_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
+    win_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
 }
 
 void
@@ -536,15 +574,15 @@ win_show_contact(ProfWin *window, PContact contact)
 
     theme_item_t presence_colour = theme_main_presence_attrs(presence);
 
-    if (name != NULL) {
-        win_save_print(window, '-', NULL, NO_EOL, presence_colour, "", name);
+    if (name) {
+        win_print(window, '-', NULL, NO_EOL, presence_colour, "", name);
     } else {
-        win_save_print(window, '-', NULL, NO_EOL, presence_colour, "", barejid);
+        win_print(window, '-', NULL, NO_EOL, presence_colour, "", barejid);
     }
 
-    win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence);
+    win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence);
 
-    if (last_activity != NULL) {
+    if (last_activity) {
         GDateTime *now = g_date_time_new_now_local();
         GTimeSpan span = g_date_time_difference(now, last_activity);
 
@@ -555,18 +593,18 @@ win_show_contact(ProfWin *window, PContact contact)
         int seconds = span / G_TIME_SPAN_SECOND;
 
         if (hours > 0) {
-          win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dh%dm%ds", hours, minutes, seconds);
+          win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dh%dm%ds", hours, minutes, seconds);
         }
         else {
-          win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dm%ds", minutes, seconds);
+          win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dm%ds", minutes, seconds);
         }
     }
 
-    if (status != NULL) {
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", p_contact_status(contact));
+    if (status) {
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", p_contact_status(contact));
     }
 
-    win_save_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
+    win_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
 }
 
 void
@@ -578,21 +616,21 @@ win_show_occupant_info(ProfWin *window, const char * const room, Occupant *occup
 
     theme_item_t presence_colour = theme_main_presence_attrs(presence_str);
 
-    win_save_print(window, '!', NULL, NO_EOL, presence_colour, "", occupant->nick);
-    win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence_str);
+    win_print(window, '!', NULL, NO_EOL, presence_colour, "", occupant->nick);
+    win_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", presence_str);
 
     if (occupant->status) {
-        win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", occupant->status);
+        win_vprint(window, '!', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", occupant->status);
     }
 
-    win_save_newline(window);
+    win_newline(window);
 
     if (occupant->jid) {
-        win_save_vprint(window, '!', NULL, 0, 0, "", "  Jid: %s", occupant->jid);
+        win_vprint(window, '!', NULL, 0, 0, "", "  Jid: %s", occupant->jid);
     }
 
-    win_save_vprint(window, '!', NULL, 0, 0, "", "  Affiliation: %s", occupant_affiliation);
-    win_save_vprint(window, '!', NULL, 0, 0, "", "  Role: %s", occupant_role);
+    win_vprint(window, '!', NULL, 0, 0, "", "  Affiliation: %s", occupant_affiliation);
+    win_vprint(window, '!', NULL, 0, 0, "", "  Role: %s", occupant_role);
 
     Jid *jidp = jid_create_from_bare_and_resource(room, occupant->nick);
     Capabilities *caps = caps_lookup(jidp->fulljid);
@@ -600,47 +638,47 @@ win_show_occupant_info(ProfWin *window, const char * const room, Occupant *occup
 
     if (caps) {
         // show identity
-        if ((caps->category != NULL) || (caps->type != NULL) || (caps->name != NULL)) {
-            win_save_print(window, '!', NULL, NO_EOL, 0, "", "  Identity: ");
-            if (caps->name != NULL) {
-                win_save_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
-                if ((caps->category != NULL) || (caps->type != NULL)) {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+        if (caps->category || caps->type || caps->name) {
+            win_print(window, '!', NULL, NO_EOL, 0, "", "  Identity: ");
+            if (caps->name) {
+                win_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
+                if (caps->category || caps->type) {
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                 }
             }
-            if (caps->type != NULL) {
-                win_save_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
-                if (caps->category != NULL) {
-                    win_save_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", " ");
+            if (caps->type) {
+                win_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
+                if (caps->category) {
+                    win_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", " ");
                 }
             }
-            if (caps->category != NULL) {
-                win_save_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
+            if (caps->category) {
+                win_print(window, '!', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
             }
-            win_save_newline(window);
+            win_newline(window);
         }
-        if (caps->software != NULL) {
-            win_save_vprint(window, '!', NULL, NO_EOL, 0, "", "  Software: %s", caps->software);
+        if (caps->software) {
+            win_vprint(window, '!', NULL, NO_EOL, 0, "", "  Software: %s", caps->software);
         }
-        if (caps->software_version != NULL) {
-            win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
+        if (caps->software_version) {
+            win_vprint(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
         }
-        if ((caps->software != NULL) || (caps->software_version != NULL)) {
-            win_save_newline(window);
+        if (caps->software || caps->software_version) {
+            win_newline(window);
         }
-        if (caps->os != NULL) {
-            win_save_vprint(window, '!', NULL, NO_EOL, 0, "", "  OS: %s", caps->os);
+        if (caps->os) {
+            win_vprint(window, '!', NULL, NO_EOL, 0, "", "  OS: %s", caps->os);
         }
-        if (caps->os_version != NULL) {
-            win_save_vprint(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
+        if (caps->os_version) {
+            win_vprint(window, '!', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
         }
-        if ((caps->os != NULL) || (caps->os_version != NULL)) {
-            win_save_newline(window);
+        if (caps->os || caps->os_version) {
+            win_newline(window);
         }
         caps_destroy(caps);
     }
 
-    win_save_print(window, '-', NULL, 0, 0, "", "");
+    win_print(window, '-', NULL, 0, 0, "", "");
 }
 
 void
@@ -650,24 +688,22 @@ win_show_info(ProfWin *window, PContact contact)
     const char *name = p_contact_name(contact);
     const char *presence = p_contact_presence(contact);
     const char *sub = p_contact_subscription(contact);
-    GList *resources = p_contact_get_available_resources(contact);
-    GList *ordered_resources = NULL;
     GDateTime *last_activity = p_contact_last_activity(contact);
 
     theme_item_t presence_colour = theme_main_presence_attrs(presence);
 
-    win_save_print(window, '-', NULL, 0, 0, "", "");
-    win_save_print(window, '-', NULL, NO_EOL, presence_colour, "", barejid);
-    if (name != NULL) {
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (%s)", name);
+    win_print(window, '-', NULL, 0, 0, "", "");
+    win_print(window, '-', NULL, NO_EOL, presence_colour, "", barejid);
+    if (name) {
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " (%s)", name);
     }
-    win_save_print(window, '-', NULL, NO_DATE, 0, "", ":");
+    win_print(window, '-', NULL, NO_DATE, 0, "", ":");
 
-    if (sub != NULL) {
-        win_save_vprint(window, '-', NULL, 0, 0, "", "Subscription: %s", sub);
+    if (sub) {
+        win_vprint(window, '-', NULL, 0, 0, "", "Subscription: %s", sub);
     }
 
-    if (last_activity != NULL) {
+    if (last_activity) {
         GDateTime *now = g_date_time_new_now_local();
         GTimeSpan span = g_date_time_difference(now, last_activity);
 
@@ -678,36 +714,41 @@ win_show_info(ProfWin *window, PContact contact)
         int seconds = span / G_TIME_SPAN_SECOND;
 
         if (hours > 0) {
-          win_save_vprint(window, '-', NULL, 0, 0, "", "Last activity: %dh%dm%ds", hours, minutes, seconds);
+          win_vprint(window, '-', NULL, 0, 0, "", "Last activity: %dh%dm%ds", hours, minutes, seconds);
         }
         else {
-          win_save_vprint(window, '-', NULL, 0, 0, "", "Last activity: %dm%ds", minutes, seconds);
+          win_vprint(window, '-', NULL, 0, 0, "", "Last activity: %dm%ds", minutes, seconds);
         }
 
         g_date_time_unref(now);
     }
 
-    if (resources != NULL) {
-        win_save_print(window, '-', NULL, 0, 0, "", "Resources:");
+    GList *resources = p_contact_get_available_resources(contact);
+    GList *ordered_resources = NULL;
+    if (resources) {
+        win_print(window, '-', NULL, 0, 0, "", "Resources:");
 
-        // sort in order of availabiltiy
-        while (resources != NULL) {
-            Resource *resource = resources->data;
+        // sort in order of availability
+        GList *curr = resources;
+        while (curr) {
+            Resource *resource = curr->data;
             ordered_resources = g_list_insert_sorted(ordered_resources,
                 resource, (GCompareFunc)resource_compare_availability);
-            resources = g_list_next(resources);
+            curr = g_list_next(curr);
         }
     }
+    g_list_free(resources);
 
-    while (ordered_resources != NULL) {
-        Resource *resource = ordered_resources->data;
+    GList *curr = ordered_resources;
+    while (curr) {
+        Resource *resource = curr->data;
         const char *resource_presence = string_from_resource_presence(resource->presence);
         theme_item_t presence_colour = theme_main_presence_attrs(resource_presence);
-        win_save_vprint(window, '-', NULL, NO_EOL, presence_colour, "", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
-        if (resource->status != NULL) {
-            win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", resource->status);
+        win_vprint(window, '-', NULL, NO_EOL, presence_colour, "", "  %s (%d), %s", resource->name, resource->priority, resource_presence);
+        if (resource->status) {
+            win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", resource->status);
         }
-        win_save_newline(window);
+        win_newline(window);
 
         Jid *jidp = jid_create_from_bare_and_resource(barejid, resource->name);
         Capabilities *caps = caps_lookup(jidp->fulljid);
@@ -715,48 +756,49 @@ win_show_info(ProfWin *window, PContact contact)
 
         if (caps) {
             // show identity
-            if ((caps->category != NULL) || (caps->type != NULL) || (caps->name != NULL)) {
-                win_save_print(window, '-', NULL, NO_EOL, 0, "", "    Identity: ");
-                if (caps->name != NULL) {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
-                    if ((caps->category != NULL) || (caps->type != NULL)) {
-                        win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+            if (caps->category || caps->type || caps->name) {
+                win_print(window, '-', NULL, NO_EOL, 0, "", "    Identity: ");
+                if (caps->name) {
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->name);
+                    if (caps->category || caps->type) {
+                        win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                     }
                 }
-                if (caps->type != NULL) {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
-                    if (caps->category != NULL) {
-                        win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
+                if (caps->type) {
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->type);
+                    if (caps->category) {
+                        win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", " ");
                     }
                 }
-                if (caps->category != NULL) {
-                    win_save_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
+                if (caps->category) {
+                    win_print(window, '-', NULL, NO_DATE | NO_EOL, 0, "", caps->category);
                 }
-                win_save_newline(window);
+                win_newline(window);
             }
-            if (caps->software != NULL) {
-                win_save_vprint(window, '-', NULL, NO_EOL, 0, "", "    Software: %s", caps->software);
+            if (caps->software) {
+                win_vprint(window, '-', NULL, NO_EOL, 0, "", "    Software: %s", caps->software);
             }
-            if (caps->software_version != NULL) {
-                win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
+            if (caps->software_version) {
+                win_vprint(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->software_version);
             }
-            if ((caps->software != NULL) || (caps->software_version != NULL)) {
-                win_save_newline(window);
+            if (caps->software || caps->software_version) {
+                win_newline(window);
             }
-            if (caps->os != NULL) {
-                win_save_vprint(window, '-', NULL, NO_EOL, 0, "", "    OS: %s", caps->os);
+            if (caps->os) {
+                win_vprint(window, '-', NULL, NO_EOL, 0, "", "    OS: %s", caps->os);
             }
-            if (caps->os_version != NULL) {
-                win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
+            if (caps->os_version) {
+                win_vprint(window, '-', NULL, NO_DATE | NO_EOL, 0, "", ", %s", caps->os_version);
             }
-            if ((caps->os != NULL) || (caps->os_version != NULL)) {
-                win_save_newline(window);
+            if (caps->os || caps->os_version) {
+                win_newline(window);
             }
             caps_destroy(caps);
         }
 
-        ordered_resources = g_list_next(ordered_resources);
+        curr = g_list_next(curr);
     }
+    g_list_free(ordered_resources);
 }
 
 void
@@ -767,7 +809,7 @@ win_show_status_string(ProfWin *window, const char * const from,
 {
     theme_item_t presence_colour;
 
-    if (show != NULL) {
+    if (show) {
         presence_colour = theme_main_presence_attrs(show);
     } else if (strcmp(default_show, "online") == 0) {
         presence_colour = THEME_ONLINE;
@@ -776,14 +818,14 @@ win_show_status_string(ProfWin *window, const char * const from,
     }
 
 
-    win_save_vprint(window, '-', NULL, NO_EOL, presence_colour, "", "%s %s", pre, from);
+    win_vprint(window, '-', NULL, NO_EOL, presence_colour, "", "%s %s", pre, from);
 
-    if (show != NULL)
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", show);
+    if (show)
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", show);
     else
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", default_show);
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", " is %s", default_show);
 
-    if (last_activity != NULL) {
+    if (last_activity) {
         GDateTime *now = g_date_time_new_now_local();
         GTimeSpan span = g_date_time_difference(now, last_activity);
         g_date_time_unref(now);
@@ -795,17 +837,17 @@ win_show_status_string(ProfWin *window, const char * const from,
         int seconds = span / G_TIME_SPAN_SECOND;
 
         if (hours > 0) {
-          win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dh%dm%ds", hours, minutes, seconds);
+          win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dh%dm%ds", hours, minutes, seconds);
         }
         else {
-          win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dm%ds", minutes, seconds);
+          win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", idle %dm%ds", minutes, seconds);
         }
     }
 
-    if (status != NULL)
-        win_save_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", status);
+    if (status)
+        win_vprint(window, '-', NULL, NO_DATE | NO_EOL, presence_colour, "", ", \"%s\"", status);
 
-    win_save_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
+    win_print(window, '-', NULL, NO_DATE, presence_colour, "", "");
 
 }
 
@@ -817,7 +859,7 @@ win_print_incoming_message(ProfWin *window, GTimeVal *tv_stamp,
     {
         case WIN_CHAT:
         case WIN_PRIVATE:
-            win_save_print(window, '-', tv_stamp, NO_ME, THEME_TEXT_THEM, from, message);
+            win_print(window, '-', tv_stamp, NO_ME, THEME_TEXT_THEM, from, message);
             break;
         default:
             assert(FALSE);
@@ -826,19 +868,19 @@ win_print_incoming_message(ProfWin *window, GTimeVal *tv_stamp,
 }
 
 void
-win_save_vprint(ProfWin *window, const char show_char, GTimeVal *tstamp,
+win_vprint(ProfWin *window, const char show_char, GTimeVal *tstamp,
     int flags, theme_item_t theme_item, const char * const from, const char * const message, ...)
 {
     va_list arg;
     va_start(arg, message);
     GString *fmt_msg = g_string_new(NULL);
     g_string_vprintf(fmt_msg, message, arg);
-    win_save_print(window, show_char, tstamp, flags, theme_item, from, fmt_msg->str);
+    win_print(window, show_char, tstamp, flags, theme_item, from, fmt_msg->str);
     g_string_free(fmt_msg, TRUE);
 }
 
 void
-win_save_print(ProfWin *window, const char show_char, GTimeVal *tstamp,
+win_print(ProfWin *window, const char show_char, GTimeVal *tstamp,
     int flags, theme_item_t theme_item, const char * const from, const char * const message)
 {
     GDateTime *time;
@@ -849,27 +891,58 @@ win_save_print(ProfWin *window, const char show_char, GTimeVal *tstamp,
         time = g_date_time_new_from_timeval_utc(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);
+    buffer_push(window->layout->buffer, show_char, time, flags, theme_item, from, message, NULL);
+    _win_print(window, show_char, time, flags, theme_item, from, message, NULL);
+    // TODO: cross-reference.. this should be replaced by a real event-based system
+    ui_input_nonblocking(TRUE);
+}
+
+void
+win_print_with_receipt(ProfWin *window, const char show_char, GTimeVal *tstamp,
+    int flags, theme_item_t theme_item, const char * const from, const char * const message, char *id)
+{
+    GDateTime *time;
+
+    if (tstamp == NULL) {
+        time = g_date_time_new_now_local();
+    } else {
+        time = g_date_time_new_from_timeval_utc(tstamp);
+    }
+
+    DeliveryReceipt *receipt = malloc(sizeof(struct delivery_receipt_t));
+    receipt->id = strdup(id);
+    receipt->received = FALSE;
+
+    buffer_push(window->layout->buffer, show_char, time, flags, theme_item, from, message, receipt);
+    _win_print(window, show_char, time, flags, theme_item, from, message, receipt);
     // TODO: cross-reference.. this should be replaced by a real event-based system
     ui_input_nonblocking(TRUE);
 }
 
 void
-win_save_println(ProfWin *window, const char * const message)
+win_mark_received(ProfWin *window, const char * const id)
+{
+    gboolean received = buffer_mark_received(window->layout->buffer, id);
+    if (received) {
+        win_redraw(window);
+    }
+}
+
+void
+win_println(ProfWin *window, const char * const message)
 {
-    win_save_print(window, '-', NULL, 0, 0, "", message);
+    win_print(window, '-', NULL, 0, 0, "", message);
 }
 
 void
-win_save_newline(ProfWin *window)
+win_newline(ProfWin *window)
 {
-    win_save_print(window, '-', NULL, NO_DATE, 0, "", "");
+    win_print(window, '-', NULL, NO_DATE, 0, "", "");
 }
 
 static void
 _win_print(ProfWin *window, const char show_char, GDateTime *time,
-    int flags, theme_item_t theme_item, const char * const from, const char * const message)
+    int flags, theme_item_t theme_item, const char * const from, const char * const message, DeliveryReceipt *receipt)
 {
     // flags : 1st bit =  0/1 - me/not me
     //         2nd bit =  0/1 - date/no date
@@ -907,6 +980,10 @@ _win_print(ProfWin *window, const char show_char, GDateTime *time,
             colour = 0;
         }
 
+        if (receipt && !receipt->received) {
+            colour = theme_attrs(THEME_RECEIPT_SENT);
+        }
+
         wattron(window->layout->win, colour);
         if (strncmp(message, "/me ", 4) == 0) {
             wprintw(window->layout->win, "*%s ", from);
@@ -919,7 +996,11 @@ _win_print(ProfWin *window, const char show_char, GDateTime *time,
     }
 
     if (!me_message) {
-        wattron(window->layout->win, theme_attrs(theme_item));
+        if (receipt && !receipt->received) {
+            wattron(window->layout->win, theme_attrs(THEME_RECEIPT_SENT));
+        } else {
+            wattron(window->layout->win, theme_attrs(theme_item));
+        }
     }
 
     if (prefs_get_boolean(PREF_WRAP)) {
@@ -935,7 +1016,11 @@ _win_print(ProfWin *window, const char show_char, GDateTime *time,
     if (me_message) {
         wattroff(window->layout->win, colour);
     } else {
-        wattroff(window->layout->win, theme_attrs(theme_item));
+        if (receipt && !receipt->received) {
+            wattroff(window->layout->win, theme_attrs(THEME_RECEIPT_SENT));
+        } else {
+            wattroff(window->layout->win, theme_attrs(theme_item));
+        }
     }
 }
 
@@ -951,7 +1036,6 @@ _win_indent(WINDOW *win, int size)
 static void
 _win_print_wrapped(WINDOW *win, const char * const message)
 {
-    int linei = 0;
     int wordi = 0;
     char *word = malloc(strlen(message) + 1);
 
@@ -964,18 +1048,26 @@ _win_print_wrapped(WINDOW *win, const char * const message)
     }
     free(time_pref);
 
-    while (message[linei] != '\0') {
-        if (message[linei] == ' ') {
+    gchar *curr_ch = g_utf8_offset_to_pointer(message, 0);
+
+    while (*curr_ch != '\0') {
+        if (*curr_ch == ' ') {
             waddch(win, ' ');
-            linei++;
-        } else if (message[linei] == '\n') {
+            curr_ch = g_utf8_next_char(curr_ch);
+        } else if (*curr_ch == '\n') {
             waddch(win, '\n');
             _win_indent(win, indent);
-            linei++;
+            curr_ch = g_utf8_next_char(curr_ch);
         } else {
+            // get word
             wordi = 0;
-            while (message[linei] != ' ' && message[linei] != '\n' && message[linei] != '\0') {
-                word[wordi++] = message[linei++];
+            while (*curr_ch != ' ' && *curr_ch != '\n' && *curr_ch != '\0') {
+                size_t ch_len = mbrlen(curr_ch, 4, NULL);
+                int offset = 0;
+                while (offset < ch_len) {
+                    word[wordi++] = curr_ch[offset++];
+                }
+                curr_ch = g_utf8_next_char(curr_ch);
             }
             word[wordi] = '\0';
 
@@ -983,17 +1075,27 @@ _win_print_wrapped(WINDOW *win, const char * const message)
             int maxx = getmaxx(win);
 
             // word larger than line
-            if (strlen(word) > (maxx - indent)) {
-                int i;
-                for (i = 0; i < wordi; i++) {
+            if (utf8_display_len(word) > (maxx - indent)) {
+                gchar *word_ch = g_utf8_offset_to_pointer(word, 0);
+                while(*word_ch != '\0') {
                     curx = getcurx(win);
                     if (curx < indent) {
                         _win_indent(win, indent);
                     }
-                    waddch(win, word[i]);
+
+                    gchar copy[wordi++];
+                    g_utf8_strncpy(copy, word_ch, 1);
+
+                    if (curx + utf8_display_len(copy) > maxx) {
+                        waddch(win, '\n');
+                        _win_indent(win, indent);
+                    }
+                    waddstr(win, copy);
+
+                    word_ch = g_utf8_next_char(word_ch);
                 }
             } else {
-                if (curx + strlen(word) > maxx) {
+                if (curx + utf8_display_len(word) > maxx) {
                     waddch(win, '\n');
                     _win_indent(win, indent);
                 }
@@ -1017,7 +1119,7 @@ win_redraw(ProfWin *window)
 
     for (i = 0; i < size; i++) {
         ProfBuffEntry *e = buffer_yield_entry(window->layout->buffer, i);
-        _win_print(window, e->show_char, e->time, e->flags, e->theme_item, e->from, e->message);
+        _win_print(window, e->show_char, e->time, e->flags, e->theme_item, e->from, e->message, e->receipt);
     }
 }
 
diff --git a/src/ui/window.h b/src/ui/window.h
index fd10a1d7..d5e57971 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -1,7 +1,7 @@
 /*
  * window.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -99,6 +99,11 @@ typedef enum {
     WIN_XML
 } win_type_t;
 
+typedef enum {
+    PROF_ENC_NONE,
+    PROF_ENC_OTR
+} prof_enc_t;
+
 typedef struct prof_win_t {
     win_type_t type;
     ProfLayout *layout;
@@ -113,7 +118,7 @@ typedef struct prof_chat_win_t {
     char *barejid;
     int unread;
     ChatState *state;
-    gboolean is_otr;
+    prof_enc_t enc_mode;
     gboolean is_trusted;
     char *resource_override;
     gboolean history_shown;
@@ -124,6 +129,7 @@ typedef struct prof_muc_win_t {
     ProfWin window;
     char *roomjid;
     int unread;
+    gboolean showjid;
     unsigned long memcheck;
 } ProfMucWin;
 
@@ -168,19 +174,27 @@ void win_print_incoming_message(ProfWin *window, GTimeVal *tv_stamp,
     const char * const from, const char * const message);
 void win_show_info(ProfWin *window, PContact contact);
 void win_show_occupant_info(ProfWin *window, const char * const room, Occupant *occupant);
-void win_save_vprint(ProfWin *window, const char show_char, GTimeVal *tstamp, int flags, theme_item_t theme_item, const char * const from, const char * const message, ...);
-void win_save_print(ProfWin *window, const char show_char, GTimeVal *tstamp, int flags, theme_item_t theme_item, const char * const from, const char * const message);
-void win_save_println(ProfWin *window, const char * const message);
-void win_save_newline(ProfWin *window);
+void win_vprint(ProfWin *window, const char show_char, GTimeVal *tstamp, int flags, theme_item_t theme_item, const char * const from, const char * const message, ...);
+void win_print(ProfWin *window, const char show_char, GTimeVal *tstamp, int flags, theme_item_t theme_item, const char * const from, const char * const message);
+void win_print_with_receipt(ProfWin *window, const char show_char, GTimeVal *tstamp, int flags,
+    theme_item_t theme_item, const char * const from, const char * const message, char *id);
+void win_println(ProfWin *window, const char * const message);
+void win_newline(ProfWin *window);
 void win_redraw(ProfWin *window);
 void win_hide_subwin(ProfWin *window);
 void win_show_subwin(ProfWin *window);
 int win_roster_cols(void);
 int win_occpuants_cols(void);
 void win_printline_nowrap(WINDOW *win, char *msg);
-void win_handle_page(ProfWin *current, const wint_t ch, const int result);
+void win_mouse(ProfWin *current, const wint_t ch, const int result);
+void win_mark_received(ProfWin *window, const char * const id);
 
 int win_unread(ProfWin *window);
 gboolean win_has_active_subwin(ProfWin *window);
 
+void win_page_up(ProfWin *window);
+void win_page_down(ProfWin *window);
+void win_sub_page_down(ProfWin *window);
+void win_sub_page_up(ProfWin *window);
+
 #endif
diff --git a/src/ui/windows.c b/src/ui/windows.c
index 5ecada12..2334efc8 100644
--- a/src/ui/windows.c
+++ b/src/ui/windows.c
@@ -1,7 +1,7 @@
 /*
  * windows.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -53,6 +53,7 @@
 #include "ui/statusbar.h"
 #include "ui/window.h"
 #include "ui/windows.h"
+#include "event/ui_events.h"
 
 static GHashTable *windows;
 static int current;
@@ -83,7 +84,7 @@ wins_get_chat(const char * const barejid)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_CHAT) {
             ProfChatWin *chatwin = (ProfChatWin*)window;
@@ -105,7 +106,7 @@ wins_get_muc_conf(const char * const roomjid)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_MUC_CONFIG) {
             ProfMucConfWin *confwin = (ProfMucConfWin*)window;
@@ -127,11 +128,12 @@ wins_get_muc(const char * const roomjid)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_MUC) {
             ProfMucWin *mucwin = (ProfMucWin*)window;
             if (g_strcmp0(mucwin->roomjid, roomjid) == 0) {
+                g_list_free(values);
                 return mucwin;
             }
         }
@@ -148,11 +150,12 @@ wins_get_private(const char * const fulljid)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_PRIVATE) {
             ProfPrivateWin *privatewin = (ProfPrivateWin*)window;
             if (g_strcmp0(privatewin->fulljid, fulljid) == 0) {
+                g_list_free(values);
                 return privatewin;
             }
         }
@@ -166,7 +169,7 @@ wins_get_private(const char * const fulljid)
 ProfWin *
 wins_get_current(void)
 {
-    if (windows != NULL) {
+    if (windows) {
         return g_hash_table_lookup(windows, GINT_TO_POINTER(current));
     } else {
         return NULL;
@@ -283,7 +286,7 @@ wins_get_next(void)
     GList *curr = keys;
 
     // find our place in the list
-    while (curr != NULL) {
+    while (curr) {
         if (current == GPOINTER_TO_INT(curr->data)) {
             break;
         }
@@ -292,7 +295,7 @@ wins_get_next(void)
 
     // if there is a next window return it
     curr = g_list_next(curr);
-    if (curr != NULL) {
+    if (curr) {
         int next = GPOINTER_TO_INT(curr->data);
         g_list_free(keys);
         return wins_get_by_num(next);
@@ -312,7 +315,7 @@ wins_get_previous(void)
     GList *curr = keys;
 
     // find our place in the list
-    while (curr != NULL) {
+    while (curr) {
         if (current == GPOINTER_TO_INT(curr->data)) {
             break;
         }
@@ -321,7 +324,7 @@ wins_get_previous(void)
 
     // if there is a previous window return it
     curr = g_list_previous(curr);
-    if (curr != NULL) {
+    if (curr) {
         int previous = GPOINTER_TO_INT(curr->data);
         g_list_free(keys);
         return wins_get_by_num(previous);
@@ -339,7 +342,7 @@ wins_get_num(ProfWin *window)
     GList *keys = g_hash_table_get_keys(windows);
     GList *curr = keys;
 
-    while (curr != NULL) {
+    while (curr) {
         gconstpointer num_p = curr->data;
         ProfWin *curr_win = g_hash_table_lookup(windows, num_p);
         if (curr_win == window) {
@@ -408,9 +411,9 @@ wins_new_xmlconsole(void)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = get_next_available_win_num(keys);
+    g_list_free(keys);
     ProfWin *newwin = win_create_xmlconsole();
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
-    g_list_free(keys);
     return newwin;
 }
 
@@ -419,9 +422,9 @@ wins_new_chat(const char * const barejid)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = get_next_available_win_num(keys);
+    g_list_free(keys);
     ProfWin *newwin = win_create_chat(barejid);
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
-    g_list_free(keys);
     return newwin;
 }
 
@@ -430,9 +433,9 @@ wins_new_muc(const char * const roomjid)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = get_next_available_win_num(keys);
+    g_list_free(keys);
     ProfWin *newwin = win_create_muc(roomjid);
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
-    g_list_free(keys);
     return newwin;
 }
 
@@ -441,9 +444,9 @@ wins_new_muc_config(const char * const roomjid, DataForm *form)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = get_next_available_win_num(keys);
+    g_list_free(keys);
     ProfWin *newwin = win_create_muc_config(roomjid, form);
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
-    g_list_free(keys);
     return newwin;
 }
 
@@ -452,9 +455,9 @@ wins_new_private(const char * const fulljid)
 {
     GList *keys = g_hash_table_get_keys(windows);
     int result = get_next_available_win_num(keys);
+    g_list_free(keys);
     ProfWin *newwin = win_create_private(fulljid);
     g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
-    g_list_free(keys);
     return newwin;
 }
 
@@ -465,7 +468,7 @@ wins_get_total_unread(void)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         result += win_unread(window);
         curr = g_list_next(curr);
@@ -481,7 +484,7 @@ wins_resize_all(void)
 
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         int subwin_cols = 0;
 
@@ -495,7 +498,13 @@ wins_resize_all(void)
                 }
                 wresize(layout->base.win, PAD_SIZE, cols - subwin_cols);
                 wresize(layout->subwin, PAD_SIZE, subwin_cols);
-                rosterwin_roster();
+                if (window->type == WIN_CONSOLE) {
+                    rosterwin_roster();
+                } else if (window->type == WIN_MUC) {
+                    ProfMucWin *mucwin = (ProfMucWin *)window;
+                    assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+                    occupantswin_occupants(mucwin->roomjid);
+                }
             } else {
                 wresize(layout->base.win, PAD_SIZE, cols);
             }
@@ -555,7 +564,7 @@ wins_get_xmlconsole(void)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_XML) {
             ProfXMLWin *xmlwin = (ProfXMLWin*)window;
@@ -577,7 +586,7 @@ wins_get_chat_recipients(void)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type == WIN_CHAT) {
             ProfChatWin *chatwin = (ProfChatWin*)window;
@@ -596,7 +605,7 @@ wins_get_prune_wins(void)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (win_unread(window) == 0 &&
                 window->type != WIN_MUC &&
@@ -617,10 +626,10 @@ wins_lost_connection(void)
     GList *values = g_hash_table_get_values(windows);
     GList *curr = values;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = curr->data;
         if (window->type != WIN_CONSOLE) {
-            win_save_print(window, '-', NULL, 0, THEME_ERROR, "", "Lost connection.");
+            win_print(window, '-', NULL, 0, THEME_ERROR, "", "Lost connection.");
 
             // if current win, set current_win_dirty
             if (wins_is_current(window)) {
@@ -636,22 +645,24 @@ gboolean
 wins_swap(int source_win, int target_win)
 {
     ProfWin *source = g_hash_table_lookup(windows, GINT_TO_POINTER(source_win));
+    ProfWin *console = wins_get_console();
 
-    if (source != NULL) {
+    if (source) {
         ProfWin *target = g_hash_table_lookup(windows, GINT_TO_POINTER(target_win));
 
         // target window empty
-        if (target == NULL) {
+        if (!target) {
             g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
-            status_bar_inactive(source_win);
             g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
+            status_bar_inactive(source_win);
             if (win_unread(source) > 0) {
                 status_bar_new(target_win);
             } else {
                 status_bar_active(target_win);
             }
-            if ((wins_get_current_num() == source_win) || (wins_get_current_num() == target_win)) {
-                ui_switch_win(1);
+            if (wins_get_current_num() == source_win) {
+                wins_set_current_by_num(target_win);
+                ui_ev_focus_win(console);
             }
             return TRUE;
 
@@ -672,7 +683,7 @@ wins_swap(int source_win, int target_win)
                 status_bar_active(source_win);
             }
             if ((wins_get_current_num() == source_win) || (wins_get_current_num() == target_win)) {
-                ui_switch_win(1);
+                ui_ev_focus_win(console);
             }
             return TRUE;
         }
@@ -708,7 +719,7 @@ wins_tidy(void)
 
         int num = 1;
         GList *curr = keys;
-        while (curr != NULL) {
+        while (curr) {
             ProfWin *window = g_hash_table_lookup(windows, curr->data);
             if (num == 10) {
                 g_hash_table_insert(new_windows, GINT_TO_POINTER(0), window);
@@ -731,7 +742,8 @@ wins_tidy(void)
 
         windows = new_windows;
         current = 1;
-        ui_switch_win(1);
+        ProfWin *console = wins_get_console();
+        ui_ev_focus_win(console);
         g_list_free(keys);
         return TRUE;
     } else {
@@ -749,7 +761,7 @@ wins_create_summary(void)
     keys = g_list_sort(keys, cmp_win_num);
     GList *curr = keys;
 
-    while (curr != NULL) {
+    while (curr) {
         ProfWin *window = g_hash_table_lookup(windows, curr->data);
         int ui_index = GPOINTER_TO_INT(curr->data);
 
diff --git a/src/ui/windows.h b/src/ui/windows.h
index c144a5a2..97183d51 100644
--- a/src/ui/windows.h
+++ b/src/ui/windows.h
@@ -1,7 +1,7 @@
 /*
  * windows.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/bookmark.c b/src/xmpp/bookmark.c
index 94adabea..68e66569 100644
--- a/src/xmpp/bookmark.c
+++ b/src/xmpp/bookmark.c
@@ -1,7 +1,7 @@
 /*
  * bookmark.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -42,7 +42,7 @@
 #include "common.h"
 #include "log.h"
 #include "muc.h"
-#include "server_events.h"
+#include "event/server_events.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
 #include "xmpp/xmpp.h"
@@ -74,7 +74,7 @@ bookmark_request(void)
 
     autocomplete_free(bookmark_ac);
     bookmark_ac = autocomplete_new();
-    if (bookmark_list != NULL) {
+    if (bookmark_list) {
         g_list_free_full(bookmark_list, _bookmark_item_destroy);
         bookmark_list = NULL;
     }
@@ -96,12 +96,12 @@ bookmark_add(const char *jid, const char *nick, const char *password, const char
     } else {
         Bookmark *item = malloc(sizeof(*item));
         item->jid = strdup(jid);
-        if (nick != NULL) {
+        if (nick) {
             item->nick = strdup(nick);
         } else {
             item->nick = NULL;
         }
-        if (password != NULL) {
+        if (password) {
             item->password = strdup(password);
         } else {
             item->password = NULL;
@@ -136,15 +136,15 @@ bookmark_update(const char *jid, const char *nick, const char *password, const c
         return FALSE;
     } else {
         Bookmark *bm = found->data;
-        if (nick != NULL) {
+        if (nick) {
             free(bm->nick);
             bm->nick = strdup(nick);
         }
-        if (password != NULL) {
+        if (password) {
             free(bm->password);
             bm->password = strdup(password);
         }
-        if (autojoin_str != NULL) {
+        if (autojoin_str) {
             if (g_strcmp0(autojoin_str, "on") == 0) {
                 bm->autojoin = TRUE;
             } else if (g_strcmp0(autojoin_str, "off") == 0) {
@@ -228,7 +228,7 @@ bookmark_find(const char * const search_str)
 void
 bookmark_autocomplete_reset(void)
 {
-    if (bookmark_ac != NULL) {
+    if (bookmark_ac) {
         autocomplete_reset(bookmark_ac);
     }
 }
@@ -413,14 +413,14 @@ _send_bookmarks(void)
     xmpp_stanza_set_ns(storage, "storage:bookmarks");
 
     GList *curr = bookmark_list;
-    while (curr != NULL) {
+    while (curr) {
         Bookmark *bookmark = curr->data;
         xmpp_stanza_t *conference = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(conference, STANZA_NAME_CONFERENCE);
         xmpp_stanza_set_attribute(conference, STANZA_ATTR_JID, bookmark->jid);
 
         Jid *jidp = jid_create(bookmark->jid);
-        if (jidp->localpart != NULL) {
+        if (jidp->localpart) {
             xmpp_stanza_set_attribute(conference, STANZA_ATTR_NAME, jidp->localpart);
         }
         jid_destroy(jidp);
@@ -431,7 +431,7 @@ _send_bookmarks(void)
             xmpp_stanza_set_attribute(conference, STANZA_ATTR_AUTOJOIN, "false");
         }
 
-        if (bookmark->nick != NULL) {
+        if (bookmark->nick) {
             xmpp_stanza_t *nick_st = xmpp_stanza_new(ctx);
             xmpp_stanza_set_name(nick_st, STANZA_NAME_NICK);
             xmpp_stanza_t *nick_text = xmpp_stanza_new(ctx);
@@ -443,7 +443,7 @@ _send_bookmarks(void)
             xmpp_stanza_release(nick_st);
         }
 
-        if (bookmark->password != NULL) {
+        if (bookmark->password) {
             xmpp_stanza_t *password_st = xmpp_stanza_new(ctx);
             xmpp_stanza_set_name(password_st, STANZA_NAME_PASSWORD);
             xmpp_stanza_t *password_text = xmpp_stanza_new(ctx);
diff --git a/src/xmpp/bookmark.h b/src/xmpp/bookmark.h
index f9392182..c8de8147 100644
--- a/src/xmpp/bookmark.h
+++ b/src/xmpp/bookmark.h
@@ -1,7 +1,7 @@
 /*
  * bookmark.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index 164313e1..56475da1 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -1,7 +1,7 @@
 /*
  * capabilities.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -154,56 +154,56 @@ _caps_by_ver(const char * const ver)
 
         char *category = g_key_file_get_string(cache, ver, "category", NULL);
         if (category) {
-            new_caps->category = strdup(category);
+            new_caps->category = category;
         } else {
             new_caps->category = NULL;
         }
 
         char *type = g_key_file_get_string(cache, ver, "type", NULL);
         if (type) {
-            new_caps->type = strdup(type);
+            new_caps->type = type;
         } else {
             new_caps->type = NULL;
         }
 
         char *name = g_key_file_get_string(cache, ver, "name", NULL);
         if (name) {
-            new_caps->name = strdup(name);
+            new_caps->name = name;
         } else {
             new_caps->name = NULL;
         }
 
         char *software = g_key_file_get_string(cache, ver, "software", NULL);
         if (software) {
-            new_caps->software = strdup(software);
+            new_caps->software = software;
         } else {
             new_caps->software = NULL;
         }
 
         char *software_version = g_key_file_get_string(cache, ver, "software_version", NULL);
         if (software_version) {
-            new_caps->software_version = strdup(software_version);
+            new_caps->software_version = software_version;
         } else {
             new_caps->software_version = NULL;
         }
 
         char *os = g_key_file_get_string(cache, ver, "os", NULL);
         if (os) {
-            new_caps->os = strdup(os);
+            new_caps->os = os;
         } else {
             new_caps->os = NULL;
         }
 
         char *os_version = g_key_file_get_string(cache, ver, "os_version", NULL);
         if (os_version) {
-            new_caps->os_version = strdup(os_version);
+            new_caps->os_version = os_version;
         } else {
             new_caps->os_version = NULL;
         }
 
         gsize features_len = 0;
         gchar **features = g_key_file_get_string_list(cache, ver, "features", &features_len, NULL);
-        if (features != NULL && features_len > 0) {
+        if (features && features_len > 0) {
             GSList *features_list = NULL;
             int i;
             for (i = 0; i < features_len; i++) {
@@ -395,16 +395,16 @@ caps_create(xmpp_stanza_t *query)
     GSList *features = NULL;
 
     xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
-    if (softwareinfo != NULL) {
+    if (softwareinfo) {
         DataForm *form = form_create(softwareinfo);
         FormField *formField = NULL;
 
         char *form_type = form_get_form_type_field(form);
         if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) {
             GSList *field = form->fields;
-            while (field != NULL) {
+            while (field) {
                 formField = field->data;
-                if (formField->values != NULL) {
+                if (formField->values) {
                     if (strcmp(formField->var, "software") == 0) {
                         software = strdup(formField->values->data);
                     } else if (strcmp(formField->var, "software_version") == 0) {
@@ -424,7 +424,7 @@ caps_create(xmpp_stanza_t *query)
 
     xmpp_stanza_t *child = xmpp_stanza_get_children(query);
     GSList *identity_stanzas = NULL;
-    while (child != NULL) {
+    while (child) {
         if (g_strcmp0(xmpp_stanza_get_name(child), "feature") == 0) {
             features = g_slist_append(features, strdup(xmpp_stanza_get_attribute(child, "var")));
         }
@@ -490,42 +490,42 @@ caps_create(xmpp_stanza_t *query)
 
     Capabilities *new_caps = malloc(sizeof(struct capabilities_t));
 
-    if (category != NULL) {
+    if (category) {
         new_caps->category = strdup(category);
     } else {
         new_caps->category = NULL;
     }
-    if (type != NULL) {
+    if (type) {
         new_caps->type = strdup(type);
     } else {
         new_caps->type = NULL;
     }
-    if (name != NULL) {
+    if (name) {
         new_caps->name = strdup(name);
     } else {
         new_caps->name = NULL;
     }
-    if (software != NULL) {
+    if (software) {
         new_caps->software = software;
     } else {
         new_caps->software = NULL;
     }
-    if (software_version != NULL) {
+    if (software_version) {
         new_caps->software_version = software_version;
     } else {
         new_caps->software_version = NULL;
     }
-    if (os != NULL) {
+    if (os) {
         new_caps->os = os;
     } else {
         new_caps->os = NULL;
     }
-    if (os_version != NULL) {
+    if (os_version) {
         new_caps->os_version = os_version;
     } else {
         new_caps->os_version = NULL;
     }
-    if (features != NULL) {
+    if (features) {
         new_caps->features = features;
     } else {
         new_caps->features = NULL;
@@ -635,7 +635,7 @@ caps_close(void)
 void
 caps_destroy(Capabilities *caps)
 {
-    if (caps != NULL) {
+    if (caps) {
         free(caps->category);
         free(caps->type);
         free(caps->name);
@@ -643,7 +643,7 @@ caps_destroy(Capabilities *caps)
         free(caps->software_version);
         free(caps->os);
         free(caps->os_version);
-        if (caps->features != NULL) {
+        if (caps->features) {
             g_slist_free_full(caps->features, free);
         }
         free(caps);
diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h
index 692ac49d..85f1d989 100644
--- a/src/xmpp/capabilities.h
+++ b/src/xmpp/capabilities.h
@@ -1,7 +1,7 @@
 /*
  * capabilities.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 915525e4..70d49b7c 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -1,7 +1,7 @@
 /*
  * connection.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -45,7 +45,7 @@
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
-#include "server_events.h"
+#include "event/server_events.h"
 #include "xmpp/bookmark.h"
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
@@ -129,11 +129,11 @@ jabber_connect_with_account(const ProfAccount * const account)
     log_info("Connecting using account: %s", account->name);
 
     // save account name and password for reconnect
-    if (saved_account.name != NULL) {
+    if (saved_account.name) {
         free(saved_account.name);
     }
     saved_account.name = strdup(account->name);
-    if (saved_account.passwd != NULL) {
+    if (saved_account.passwd) {
         free(saved_account.passwd);
     }
     saved_account.passwd = strdup(account->password);
@@ -157,7 +157,7 @@ jabber_connect_with_details(const char * const jid,
     // save details for reconnect, remember name for account creating on success
     saved_details.name = strdup(jid);
     saved_details.passwd = strdup(passwd);
-    if (altdomain != NULL) {
+    if (altdomain) {
         saved_details.altdomain = strdup(altdomain);
     } else {
         saved_details.altdomain = NULL;
@@ -199,11 +199,11 @@ jabber_disconnect(void)
         _connection_free_saved_account();
         _connection_free_saved_details();
         _connection_free_session_data();
-        if (jabber_conn.conn != NULL) {
+        if (jabber_conn.conn) {
             xmpp_conn_release(jabber_conn.conn);
             jabber_conn.conn = NULL;
         }
-        if (jabber_conn.ctx != NULL) {
+        if (jabber_conn.ctx) {
             xmpp_ctx_free(jabber_conn.ctx);
             jabber_conn.ctx = NULL;
         }
@@ -238,7 +238,7 @@ jabber_process_events(void)
             break;
         case JABBER_DISCONNECTED:
             reconnect_sec = prefs_get_reconnect();
-            if ((reconnect_sec != 0) && (reconnect_timer != NULL)) {
+            if ((reconnect_sec != 0) && reconnect_timer) {
                 int elapsed_sec = g_timer_elapsed(reconnect_timer, NULL);
                 if (elapsed_sec > reconnect_sec) {
                     _jabber_reconnect();
@@ -302,7 +302,7 @@ void
 connection_set_presence_message(const char * const message)
 {
     FREE_SET_NULL(jabber_conn.presence_message);
-    if (message != NULL) {
+    if (message) {
         jabber_conn.presence_message = strdup(message);
     }
 }
@@ -371,15 +371,15 @@ _jabber_connect(const char * const fulljid, const char * const passwd,
     jid_destroy(jid);
 
     log_info("Connecting as %s", fulljid);
-    if (jabber_conn.log != NULL) {
+    if (jabber_conn.log) {
         free(jabber_conn.log);
     }
     jabber_conn.log = _xmpp_get_file_logger();
 
-    if (jabber_conn.conn != NULL) {
+    if (jabber_conn.conn) {
         xmpp_conn_release(jabber_conn.conn);
     }
-    if (jabber_conn.ctx != NULL) {
+    if (jabber_conn.ctx) {
         xmpp_ctx_free(jabber_conn.ctx);
     }
     jabber_conn.ctx = xmpp_ctx_new(NULL, jabber_conn.log);
@@ -436,9 +436,9 @@ _connection_handler(xmpp_conn_t * const conn,
         log_debug("Connection handler: XMPP_CONN_CONNECT");
 
         // logged in with account
-        if (saved_account.name != NULL) {
+        if (saved_account.name) {
             log_debug("Connection handler: logged in with account name: %s", saved_account.name);
-            handle_login_account_success(saved_account.name);
+            sv_ev_login_account_success(saved_account.name);
 
         // logged in without account, use details to create new account
         } else {
@@ -446,7 +446,7 @@ _connection_handler(xmpp_conn_t * const conn,
             accounts_add(saved_details.name, saved_details.altdomain, saved_details.port);
             accounts_set_jid(saved_details.name, saved_details.jid);
 
-            handle_login_account_success(saved_details.name);
+            sv_ev_login_account_success(saved_details.name);
             saved_account.name = strdup(saved_details.name);
             saved_account.passwd = strdup(saved_details.passwd);
 
@@ -466,10 +466,15 @@ _connection_handler(xmpp_conn_t * const conn,
 
         roster_request();
         bookmark_request();
+
+        if (prefs_get_boolean(PREF_CARBONS)){
+            iq_enable_carbons();
+        }
+
         jabber_conn.conn_status = JABBER_CONNECTED;
 
         if (prefs_get_reconnect() != 0) {
-            if (reconnect_timer != NULL) {
+            if (reconnect_timer) {
                 g_timer_destroy(reconnect_timer);
                 reconnect_timer = NULL;
             }
@@ -481,7 +486,7 @@ _connection_handler(xmpp_conn_t * const conn,
         // lost connection for unknown reason
         if (jabber_conn.conn_status == JABBER_CONNECTED) {
             log_debug("Connection handler: Lost connection for unknown reason");
-            handle_lost_connection();
+            sv_ev_lost_connection();
             if (prefs_get_reconnect() != 0) {
                 assert(reconnect_timer == NULL);
                 reconnect_timer = g_timer_new();
@@ -498,7 +503,7 @@ _connection_handler(xmpp_conn_t * const conn,
             log_debug("Connection handler: Login failed");
             if (reconnect_timer == NULL) {
                 log_debug("Connection handler: No reconnect timer");
-                handle_failed_login();
+                sv_ev_failed_login();
                 _connection_free_saved_account();
                 _connection_free_saved_details();
                 _connection_free_session_data();
@@ -558,7 +563,7 @@ _xmpp_file_logger(void * const userdata, const xmpp_log_level_t level,
     log_level_t prof_level = _get_log_level(level);
     log_msg(prof_level, area, msg);
     if ((g_strcmp0(area, "xmpp") == 0) || (g_strcmp0(area, "conn")) == 0) {
-        handle_xmpp_stanza(msg);
+        sv_ev_xmpp_stanza(msg);
     }
 }
 
diff --git a/src/xmpp/connection.h b/src/xmpp/connection.h
index f9e2cf22..63f7cde0 100644
--- a/src/xmpp/connection.h
+++ b/src/xmpp/connection.h
@@ -1,7 +1,7 @@
 /*
  * connection.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/form.c b/src/xmpp/form.c
index 1facc754..e6213b64 100644
--- a/src/xmpp/form.c
+++ b/src/xmpp/form.c
@@ -1,7 +1,7 @@
 /*
  * form.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -211,7 +211,7 @@ form_create(xmpp_stanza_t * const form_stanza)
 
             field->var = _get_attr(field_stanza, "var");
 
-            if (field->type_t != FIELD_HIDDEN && field->var != NULL) {
+            if (field->type_t != FIELD_HIDDEN && field->var) {
                 GString *tag = g_string_new("");
                 g_string_printf(tag, "field%d", tag_num++);
                 g_hash_table_insert(form->var_to_tag, strdup(field->var), strdup(tag->str));
diff --git a/src/xmpp/form.h b/src/xmpp/form.h
index fa14e1c5..86cd4b7b 100644
--- a/src/xmpp/form.h
+++ b/src/xmpp/form.h
@@ -1,7 +1,7 @@
 /*
  * form.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 7fedf0ed..18abad36 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -1,7 +1,7 @@
 /*
  * iq.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -47,8 +47,9 @@
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
+#include "ui/ui.h"
 #include "config/preferences.h"
-#include "server_events.h"
+#include "event/server_events.h"
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
@@ -58,48 +59,35 @@
 
 #define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_IQ, type, ctx)
 
-static int _error_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _ping_get_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _version_get_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _disco_info_get_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _disco_info_response_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _version_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _disco_items_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _disco_items_get_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _destroy_room_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_config_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_config_submit_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_affiliation_list_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_affiliation_set_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_role_set_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_role_list_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _room_kick_result_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _manual_pong_handler(xmpp_conn_t *const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _ping_timed_handler(xmpp_conn_t * const conn,
-    void * const userdata);
-static int _caps_response_handler(xmpp_conn_t *const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _caps_response_handler_for_jid(xmpp_conn_t *const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _caps_response_handler_legacy(xmpp_conn_t *const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
+typedef struct p_room_info_data_t {
+    char *room;
+    gboolean display;
+} ProfRoomInfoData;
+
+static int _error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _ping_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _version_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _disco_info_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _version_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _disco_items_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _disco_items_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_config_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_config_submit_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_affiliation_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_affiliation_set_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_role_set_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_role_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_kick_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _enable_carbons_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _disable_carbons_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _ping_timed_handler(xmpp_conn_t * const conn, void * const userdata);
+static int _caps_response_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _caps_response_handler_for_jid(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza, void * const userdata);
 
 void
 iq_add_handlers(void)
@@ -153,6 +141,34 @@ iq_room_list_request(gchar *conferencejid)
 }
 
 void
+iq_enable_carbons()
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_enable_carbons(ctx);
+    char *id = xmpp_stanza_get_id(iq);
+
+    xmpp_id_handler_add(conn, _enable_carbons_handler, id, NULL);
+
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+void
+iq_disable_carbons()
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_disable_carbons(ctx);
+    char *id = xmpp_stanza_get_id(iq);
+
+    xmpp_id_handler_add(conn, _disable_carbons_handler, id, NULL);
+
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+void
 iq_disco_info_request(gchar *jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
@@ -169,14 +185,18 @@ iq_disco_info_request(gchar *jid)
 }
 
 void
-iq_room_info_request(gchar *room)
+iq_room_info_request(const char * const room, gboolean display_result)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
     char *id = create_unique_id("room_disco_info");
     xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, room, NULL);
 
-    xmpp_id_handler_add(conn, _disco_info_response_handler, id, room);
+    ProfRoomInfoData *cb_data = malloc(sizeof(ProfRoomInfoData));
+    cb_data->room = strdup(room);
+    cb_data->display = display_result;
+
+    xmpp_id_handler_add(conn, _room_info_response_handler, id, cb_data);
 
     free(id);
 
@@ -456,7 +476,7 @@ _error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     char *error_msg = stanza_get_error_message(stanza);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ error handler fired, id: %s, error: %s", id, error_msg);
         log_error("IQ error received, id: %s, error: %s", id, error_msg);
     } else {
@@ -476,13 +496,13 @@ _pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     char *id = xmpp_stanza_get_id(stanza);
     char *type = xmpp_stanza_get_type(stanza);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ pong handler fired, id: %s.", id);
     } else {
         log_debug("IQ pong handler fired.");
     }
 
-    if (id != NULL && type != NULL) {
+    if (id && type) {
         // show warning if error
         if (strcmp(type, STANZA_TYPE_ERROR) == 0) {
             char *error_msg = stanza_get_error_message(stanza);
@@ -491,12 +511,13 @@ _pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
 
             // turn off autoping if error type is 'cancel'
             xmpp_stanza_t *error = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
-            if (error != NULL) {
+            if (error) {
                 char *errtype = xmpp_stanza_get_type(error);
-                if (errtype != NULL) {
+                if (errtype) {
                     if (strcmp(errtype, "cancel") == 0) {
                         log_warning("Server ping (id=%s) error type 'cancel', disabling autoping.", id);
-                        handle_autoping_cancel();
+                        prefs_set_autoping(0);
+                        cons_show_error("Server ping not supported, autoping disabled.");
                         xmpp_timed_handler_delete(conn, _ping_timed_handler);
                     }
                 }
@@ -586,12 +607,14 @@ static int
 _caps_response_handler_for_jid(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
+    char *jid = (char *)userdata;
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
 
     char *type = xmpp_stanza_get_type(stanza);
     // ignore non result
     if ((g_strcmp0(type, "get") == 0) || (g_strcmp0(type, "set") == 0)) {
+        free(jid);
         return 1;
     }
 
@@ -604,6 +627,7 @@ _caps_response_handler_for_jid(xmpp_conn_t *const conn, xmpp_stanza_t * const st
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     if (!from) {
         log_info("No from attribute");
+        free(jid);
         return 0;
     }
 
@@ -612,25 +636,29 @@ _caps_response_handler_for_jid(xmpp_conn_t *const conn, xmpp_stanza_t * const st
         char *error_message = stanza_get_error_message(stanza);
         log_warning("Error received for capabilities response from %s: ", from, error_message);
         free(error_message);
+        free(jid);
         return 0;
     }
 
     if (query == NULL) {
         log_warning("No query element found.");
+        free(jid);
         return 0;
     }
 
     char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE);
     if (node == NULL) {
         log_warning("No node attribute found");
+        free(jid);
         return 0;
     }
 
-    char *jid = (char *)userdata;
     log_info("Associating capabilities with: %s", jid);
     Capabilities *capabilities = caps_create(query);
     caps_add_by_jid(jid, capabilities);
 
+    free(jid);
+
     return 0;
 }
 
@@ -640,10 +668,12 @@ _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t * const sta
 {
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+    char *expected_node = (char *)userdata;
 
     char *type = xmpp_stanza_get_type(stanza);
     // ignore non result
     if ((g_strcmp0(type, "get") == 0) || (g_strcmp0(type, "set") == 0)) {
+        free(expected_node);
         return 1;
     }
 
@@ -656,6 +686,7 @@ _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t * const sta
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     if (!from) {
         log_info("No from attribute");
+        free(expected_node);
         return 0;
     }
 
@@ -664,22 +695,23 @@ _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t * const sta
         char *error_message = stanza_get_error_message(stanza);
         log_warning("Error received for capabilities response from %s: ", from, error_message);
         free(error_message);
+        free(expected_node);
         return 0;
     }
 
     if (query == NULL) {
         log_warning("No query element found.");
+        free(expected_node);
         return 0;
     }
 
     char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE);
     if (node == NULL) {
         log_warning("No node attribute found");
+        free(expected_node);
         return 0;
     }
 
-    char *expected_node = (char *)userdata;
-
     // nodes match
     if (g_strcmp0(expected_node, node) == 0) {
         log_info("Legacy capabilities, nodes match %s", node);
@@ -704,6 +736,36 @@ _caps_response_handler_legacy(xmpp_conn_t *const conn, xmpp_stanza_t * const sta
 }
 
 static int
+_enable_carbons_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
+{
+    char *type = xmpp_stanza_get_type(stanza);
+    if (g_strcmp0(type, "error") == 0) {
+        char *error_message = stanza_get_error_message(stanza);
+        cons_show_error("Server error enabling message carbons: %s", error_message);
+        log_debug("Error enabling carbons: %s", error_message);
+    } else {
+        log_debug("Message carbons enabled.");
+    }
+
+    return 0;
+}
+
+static int
+_disable_carbons_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
+{
+    char *type = xmpp_stanza_get_type(stanza);
+    if (g_strcmp0(type, "error") == 0) {
+        char *error_message = stanza_get_error_message(stanza);
+        cons_show_error("Server error disabling message carbons: %s", error_message);
+        log_debug("Error disabling carbons: %s", error_message);
+    } else {
+        log_debug("Message carbons disabled.");
+    }
+
+    return 0;
+}
+
+static int
 _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
@@ -714,7 +776,12 @@ _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_ping_error_result(from, error_message);
+        if (!error_message) {
+            cons_show_error("Error returned from pinging %s.", from);
+        } else {
+            cons_show_error("Error returned from pinging %s: %s.", from, error_message);
+        }
+
         free(error_message);
         g_date_time_unref(sent);
         return 0;
@@ -728,7 +795,11 @@ _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     g_date_time_unref(sent);
     g_date_time_unref(now);
 
-    handle_ping_result(from, elapsed_millis);
+    if (from == NULL) {
+        cons_show("Ping response from server: %dms.", elapsed_millis);
+    } else {
+        cons_show("Ping response from %s: %dms.", from, elapsed_millis);
+    }
 
     return 0;
 }
@@ -759,7 +830,7 @@ _version_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 {
     char *id = xmpp_stanza_get_id(stanza);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ version result handler fired, id: %s.", id);
     } else {
         log_debug("IQ version result handler fired.");
@@ -784,13 +855,13 @@ _version_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     xmpp_stanza_t *version = xmpp_stanza_get_child_by_name(query, "version");
     xmpp_stanza_t *os = xmpp_stanza_get_child_by_name(query, "os");
 
-    if (name != NULL) {
+    if (name) {
         name_str = xmpp_stanza_get_text(name);
     }
-    if (version != NULL) {
+    if (version) {
         version_str = xmpp_stanza_get_text(version);
     }
-    if (os != NULL) {
+    if (os) {
         os_str = xmpp_stanza_get_text(os);
     }
 
@@ -805,7 +876,7 @@ _version_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
         presence = string_from_resource_presence(resource->presence);
     }
 
-    handle_software_version_result(jid, presence, name_str, version_str, os_str);
+    cons_show_software_version(jid, presence, name_str, version_str, os_str);
 
     jid_destroy(jidp);
 
@@ -821,7 +892,7 @@ _ping_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     const char *to = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TO);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ ping get handler fired, id: %s.", id);
     } else {
         log_debug("IQ ping get handler fired.");
@@ -837,7 +908,7 @@ _ping_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     xmpp_stanza_set_attribute(pong, STANZA_ATTR_FROM, to);
     xmpp_stanza_set_attribute(pong, STANZA_ATTR_TYPE, STANZA_TYPE_RESULT);
 
-    if (id != NULL) {
+    if (id) {
         xmpp_stanza_set_attribute(pong, STANZA_ATTR_ID, id);
     }
 
@@ -855,16 +926,16 @@ _version_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ version get handler fired, id: %s.", id);
     } else {
         log_debug("IQ version get handler fired.");
     }
 
-    if (from != NULL) {
+    if (from) {
         xmpp_stanza_t *response = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(response, STANZA_NAME_IQ);
-        if (id != NULL) {
+        if (id) {
             xmpp_stanza_set_id(response, id);
         }
         xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from);
@@ -923,13 +994,13 @@ _disco_items_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ disco items get handler fired, id: %s.", id);
     } else {
         log_debug("IQ disco items get handler fired.");
     }
 
-    if (from != NULL) {
+    if (from) {
         xmpp_stanza_t *response = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(response, STANZA_NAME_IQ);
         xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza));
@@ -960,20 +1031,20 @@ _disco_info_get_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ disco info get handler fired, id: %s.", id);
     } else {
         log_debug("IQ disco info get handler fired.");
     }
 
-    if (from != NULL) {
+    if (from) {
         xmpp_stanza_t *response = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(response, STANZA_NAME_IQ);
         xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza));
         xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from);
         xmpp_stanza_set_type(response, STANZA_TYPE_RESULT);
         xmpp_stanza_t *query = caps_create_query_response_stanza(ctx);
-        if (node_str != NULL) {
+        if (node_str) {
             xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node_str);
         }
         xmpp_stanza_add_child(response, query);
@@ -992,7 +1063,7 @@ _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
 {
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ destroy room result handler fired, id: %s.", id);
     } else {
         log_debug("IQ destroy room result handler fired.");
@@ -1002,7 +1073,7 @@ _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
     if (from == NULL) {
         log_error("No from attribute for IQ destroy room result");
     } else {
-        handle_room_destroy(from);
+        sv_ev_room_destroy(from);
     }
 
     return 0;
@@ -1016,7 +1087,7 @@ _room_config_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     const char *type = xmpp_stanza_get_type(stanza);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ room config handler fired, id: %s.", id);
     } else {
         log_debug("IQ room config handler fired.");
@@ -1025,40 +1096,40 @@ _room_config_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_configuration_form_error(from, error_message);
+        ui_handle_room_configuration_form_error(from, error_message);
         free(error_message);
         return 0;
     }
 
     if (from == NULL) {
         log_warning("No from attribute for IQ config request result");
-        handle_room_configuration_form_error(from, "No from attribute for room cofig response.");
+        ui_handle_room_configuration_form_error(from, "No from attribute for room cofig response.");
         return 0;
     }
 
     xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
     if (query == NULL) {
         log_warning("No query element found parsing room config response");
-        handle_room_configuration_form_error(from, "No query element found parsing room config response");
+        ui_handle_room_configuration_form_error(from, "No query element found parsing room config response");
         return 0;
     }
 
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
     if (x == NULL) {
         log_warning("No x element found with %s namespace parsing room config response", STANZA_NS_DATA);
-        handle_room_configuration_form_error(from, "No form configuration options available");
+        ui_handle_room_configuration_form_error(from, "No form configuration options available");
         return 0;
     }
 
     char *form_type = xmpp_stanza_get_attribute(x, STANZA_ATTR_TYPE);
     if (g_strcmp0(form_type, "form") != 0) {
         log_warning("x element not of type 'form' parsing room config response");
-        handle_room_configuration_form_error(from, "Form not of type 'form' parsing room config response.");
+        ui_handle_room_configuration_form_error(from, "Form not of type 'form' parsing room config response.");
         return 0;
     }
 
     DataForm *form = form_create(x);
-    handle_room_configure(from, form);
+    ui_handle_room_configuration(from, form);
 
     return 0;
 }
@@ -1071,7 +1142,7 @@ static int _room_affiliation_set_result_handler(xmpp_conn_t * const conn, xmpp_s
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     struct privilege_set_t *affiliation_set = (struct privilege_set_t *)userdata;
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ affiliation set handler fired, id: %s.", id);
     } else {
         log_debug("IQ affiliation set handler fired.");
@@ -1080,7 +1151,8 @@ static int _room_affiliation_set_result_handler(xmpp_conn_t * const conn, xmpp_s
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_affiliation_set_error(from, affiliation_set->item, affiliation_set->privilege, error_message);
+        log_debug("Error setting affiliation %s list for room %s, user %s: %s", affiliation_set->privilege, from, affiliation_set->item, error_message);
+        ui_handle_room_affiliation_set_error(from, affiliation_set->item, affiliation_set->privilege, error_message);
         free(error_message);
     }
 
@@ -1099,7 +1171,7 @@ static int _room_role_set_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     struct privilege_set_t *role_set = (struct privilege_set_t *)userdata;
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ role set handler fired, id: %s.", id);
     } else {
         log_debug("IQ role set handler fired.");
@@ -1108,7 +1180,8 @@ static int _room_role_set_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_role_set_error(from, role_set->item, role_set->privilege, error_message);
+        log_debug("Error setting role %s list for room %s, user %s: %s", role_set->privilege, from, role_set->item, error_message);
+        ui_handle_room_role_set_error(from, role_set->item, role_set->privilege, error_message);
         free(error_message);
     }
 
@@ -1127,7 +1200,7 @@ _room_affiliation_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t *
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     char *affiliation = (char *)userdata;
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ affiliation list result handler fired, id: %s.", id);
     } else {
         log_debug("IQ affiliation list result handler fired.");
@@ -1136,7 +1209,8 @@ _room_affiliation_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t *
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_affiliation_list_result_error(from, affiliation, error_message);
+        log_debug("Error retrieving %s list for room %s: %s", affiliation, from, error_message);
+        ui_handle_room_affiliation_list_error(from, affiliation, error_message);
         free(error_message);
         free(affiliation);
         return 0;
@@ -1158,7 +1232,8 @@ _room_affiliation_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t *
         }
     }
 
-    handle_room_affiliation_list(from, affiliation, jids);
+    muc_jid_autocomplete_add_all(from, jids);
+    ui_handle_room_affiliation_list(from, affiliation, jids);
     free(affiliation);
     g_slist_free(jids);
 
@@ -1173,7 +1248,7 @@ _room_role_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const s
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     char *role = (char *)userdata;
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ role list result handler fired, id: %s.", id);
     } else {
         log_debug("IQ role list result handler fired.");
@@ -1182,7 +1257,8 @@ _room_role_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const s
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_role_list_result_error(from, role, error_message);
+        log_debug("Error retrieving %s list for room %s: %s", role, from, error_message);
+        ui_handle_room_role_list_error(from, role, error_message);
         free(error_message);
         free(role);
         return 0;
@@ -1204,7 +1280,7 @@ _room_role_list_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const s
         }
     }
 
-    handle_room_role_list(from, role, nicks);
+    ui_handle_room_role_list(from, role, nicks);
     free(role);
     g_slist_free(nicks);
 
@@ -1219,7 +1295,7 @@ _room_config_submit_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stan
     const char *type = xmpp_stanza_get_type(stanza);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ room config submit handler fired, id: %s.", id);
     } else {
         log_debug("IQ room config submit handler fired.");
@@ -1228,12 +1304,12 @@ _room_config_submit_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stan
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_config_submit_result_error(from, error_message);
+        ui_handle_room_config_submit_result_error(from, error_message);
         free(error_message);
         return 0;
     }
 
-    handle_room_config_submit_result(from);
+    ui_handle_room_config_submit_result(from);
 
     return 0;
 }
@@ -1246,7 +1322,7 @@ _room_kick_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     char *nick = (char *)userdata;
 
-    if (id != NULL) {
+    if (id) {
         log_debug("IQ kick result handler fired, id: %s.", id);
     } else {
         log_debug("IQ kick result handler fired.");
@@ -1255,7 +1331,7 @@ _room_kick_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        handle_room_kick_result_error(from, nick, error_message);
+        ui_handle_room_kick_error(from, nick, error_message);
         free(error_message);
         free(nick);
         return 0;
@@ -1269,7 +1345,7 @@ _room_kick_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza
 static void
 _identity_destroy(DiscoIdentity *identity)
 {
-    if (identity != NULL) {
+    if (identity) {
         free(identity->name);
         free(identity->type);
         free(identity->category);
@@ -1280,7 +1356,7 @@ _identity_destroy(DiscoIdentity *identity)
 static void
 _item_destroy(DiscoItem *item)
 {
-    if (item != NULL) {
+    if (item) {
         free(item->jid);
         free(item->name);
         free(item);
@@ -1288,32 +1364,104 @@ _item_destroy(DiscoItem *item)
 }
 
 static int
+_room_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    const char *type = xmpp_stanza_get_type(stanza);
+    ProfRoomInfoData *cb_data = (ProfRoomInfoData *)userdata;
+    log_info("Received diso#info response for room: %s", cb_data->room);
+
+    // handle error responses
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        if (cb_data->display) {
+            char *error_message = stanza_get_error_message(stanza);
+            ui_handle_room_info_error(cb_data->room, error_message);
+            free(error_message);
+        }
+        free(cb_data->room);
+        free(cb_data);
+        return 0;
+    }
+
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+
+    if (query) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(query);
+        GSList *identities = NULL;
+        GSList *features = NULL;
+        while (child) {
+            const char *stanza_name = xmpp_stanza_get_name(child);
+            if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) {
+                const char *var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR);
+                if (var) {
+                    features = g_slist_append(features, strdup(var));
+                }
+            } else if (g_strcmp0(stanza_name, STANZA_NAME_IDENTITY) == 0) {
+                const char *name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
+                const char *type = xmpp_stanza_get_attribute(child, STANZA_ATTR_TYPE);
+                const char *category = xmpp_stanza_get_attribute(child, STANZA_ATTR_CATEGORY);
+
+                if (name || category || type) {
+                    DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t));
+
+                    if (name) {
+                        identity->name = strdup(name);
+                    } else {
+                        identity->name = NULL;
+                    }
+                    if (category) {
+                        identity->category = strdup(category);
+                    } else {
+                        identity->category = NULL;
+                    }
+                    if (type) {
+                        identity->type = strdup(type);
+                    } else {
+                        identity->type = NULL;
+                    }
+
+                    identities = g_slist_append(identities, identity);
+                }
+            }
+
+            child = xmpp_stanza_get_next(child);
+        }
+
+        muc_set_features(cb_data->room, features);
+        if (cb_data->display) {
+            ui_show_room_disco_info(cb_data->room, identities, features);
+        }
+
+        g_slist_free_full(features, free);
+        g_slist_free_full(identities, (GDestroyNotify)_identity_destroy);
+    }
+
+    free(cb_data->room);
+    free(cb_data);
+
+    return 0;
+}
+
+static int
 _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     const char *type = xmpp_stanza_get_type(stanza);
 
-    char *room = NULL;
-    if (userdata) {
-        room = (char *) userdata;
-        log_info("Received diso#info response for room: %s", room);
+    if (from) {
+        log_info("Received diso#info response from: %s", from);
     } else {
-        room = NULL;
-        if (from) {
-            log_info("Received diso#info response from: %s", from);
-        } else {
-            log_info("Received diso#info response");
-        }
+        log_info("Received diso#info response");
     }
 
     // handle error responses
     if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
         char *error_message = stanza_get_error_message(stanza);
-        if (room) {
-            handle_room_info_error(room, error_message);
+        if (from) {
+            cons_show_error("Service discovery failed for %s: %s", from, error_message);
         } else {
-            handle_disco_info_error(from, error_message);
+            cons_show_error("Service discovery failed: %s", error_message);
         }
         free(error_message);
         return 0;
@@ -1321,15 +1469,15 @@ _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
 
     xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
 
-    if (query != NULL) {
+    if (query) {
         xmpp_stanza_t *child = xmpp_stanza_get_children(query);
         GSList *identities = NULL;
         GSList *features = NULL;
-        while (child != NULL) {
+        while (child) {
             const char *stanza_name = xmpp_stanza_get_name(child);
             if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) {
                 const char *var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR);
-                if (var != NULL) {
+                if (var) {
                     features = g_slist_append(features, strdup(var));
                 }
             } else if (g_strcmp0(stanza_name, STANZA_NAME_IDENTITY) == 0) {
@@ -1337,20 +1485,20 @@ _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
                 const char *type = xmpp_stanza_get_attribute(child, STANZA_ATTR_TYPE);
                 const char *category = xmpp_stanza_get_attribute(child, STANZA_ATTR_CATEGORY);
 
-                if ((name != NULL) || (category != NULL) || (type != NULL)) {
+                if (name || category || type) {
                     DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t));
 
-                    if (name != NULL) {
+                    if (name) {
                         identity->name = strdup(name);
                     } else {
                         identity->name = NULL;
                     }
-                    if (category != NULL) {
+                    if (category) {
                         identity->category = strdup(category);
                     } else {
                         identity->category = NULL;
                     }
-                    if (type != NULL) {
+                    if (type) {
                         identity->type = strdup(type);
                     } else {
                         identity->type = NULL;
@@ -1363,16 +1511,13 @@ _disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
             child = xmpp_stanza_get_next(child);
         }
 
-        if (room) {
-            handle_room_disco_info(room, identities, features);
-        } else {
-            handle_disco_info(from, identities, features);
-        }
+        cons_show_disco_info(from, identities, features);
 
         g_slist_free_full(features, free);
         g_slist_free_full(identities, (GDestroyNotify)_identity_destroy);
     }
-    return 1;
+
+    return 0;
 }
 
 static int
@@ -1389,17 +1534,17 @@ _disco_items_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stan
         log_debug("Response to query: %s", id);
         xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
 
-        if (query != NULL) {
+        if (query) {
             xmpp_stanza_t *child = xmpp_stanza_get_children(query);
-            while (child != NULL) {
+            while (child) {
                 const char *stanza_name = xmpp_stanza_get_name(child);
-                if ((stanza_name != NULL) && (g_strcmp0(stanza_name, STANZA_NAME_ITEM) == 0)) {
+                if (stanza_name && (g_strcmp0(stanza_name, STANZA_NAME_ITEM) == 0)) {
                     const char *item_jid = xmpp_stanza_get_attribute(child, STANZA_ATTR_JID);
-                    if (item_jid != NULL) {
+                    if (item_jid) {
                         DiscoItem *item = malloc(sizeof(struct disco_item_t));
                         item->jid = strdup(item_jid);
                         const char *item_name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
-                        if (item_name != NULL) {
+                        if (item_name) {
                             item->name = strdup(item_name);
                         } else {
                             item->name = NULL;
@@ -1414,9 +1559,9 @@ _disco_items_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stan
     }
 
     if (g_strcmp0(id, "confreq") == 0) {
-        handle_room_list(items, from);
+        cons_show_room_list(items, from);
     } else if (g_strcmp0(id, "discoitemsreq") == 0) {
-        handle_disco_items(items, from);
+        cons_show_disco_items(items, from);
     }
 
     g_slist_free_full(items, (GDestroyNotify)_item_destroy);
diff --git a/src/xmpp/iq.h b/src/xmpp/iq.h
index 8c803ab5..d3a22fe4 100644
--- a/src/xmpp/iq.h
+++ b/src/xmpp/iq.h
@@ -1,7 +1,7 @@
 /*
  * iq.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index e96c1a74..bc702199 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -1,7 +1,7 @@
 /*
  * message.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -42,7 +42,8 @@
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
-#include "server_events.h"
+#include "ui/ui.h"
+#include "event/server_events.h"
 #include "xmpp/connection.h"
 #include "xmpp/message.h"
 #include "xmpp/roster.h"
@@ -52,18 +53,13 @@
 
 #define HANDLE(ns, type, func) xmpp_handler_add(conn, func, ns, STANZA_NAME_MESSAGE, type, ctx)
 
-static int _groupchat_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _chat_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _muc_user_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _conference_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _captcha_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
-static int _message_error_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata);
+static int _groupchat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _conference_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _captcha_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
+static int _receipt_received_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata);
 
 void
 message_add_handlers(void)
@@ -77,34 +73,89 @@ message_add_handlers(void)
     HANDLE(STANZA_NS_MUC_USER,   NULL,                   _muc_user_handler);
     HANDLE(STANZA_NS_CONFERENCE, NULL,                   _conference_handler);
     HANDLE(STANZA_NS_CAPTCHA,    NULL,                   _captcha_handler);
+    HANDLE(STANZA_NS_RECEIPTS,   NULL,                   _receipt_received_handler);
 }
 
-void
+char *
 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();
 
     ChatSession *session = chat_session_get(barejid);
+    char *state = NULL;
+    char *jid = NULL;
+    if (session) {
+        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);
+        jid = strdup(jidp->fulljid);
+        jid_destroy(jidp);
+
+    } else {
+        if (prefs_get_boolean(PREF_STATES)) {
+            state = STANZA_NAME_ACTIVE;
+        }
+        jid = strdup(barejid);
+    }
+
+    char *id = create_unique_id("msg");
+    xmpp_stanza_t *message = stanza_create_message(ctx, id, jid, STANZA_TYPE_CHAT, msg);
+    free(jid);
+
+    if (state) {
+        stanza_attach_state(ctx, message, state);
+    }
+    if (prefs_get_boolean(PREF_RECEIPTS_REQUEST)) {
+        stanza_attach_receipt_request(ctx, message);
+    }
+
+    xmpp_send(conn, message);
+    xmpp_stanza_release(message);
+
+    return id;
+}
+
+char *
+message_send_chat_encrypted(const char * const barejid, const char * const msg)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+
+    ChatSession *session = chat_session_get(barejid);
+    char *state = NULL;
+    char *jid = NULL;
     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 = strdup(jidp->fulljid);
         jid_destroy(jidp);
     } else {
-        char *state = NULL;
         if (prefs_get_boolean(PREF_STATES)) {
             state = STANZA_NAME_ACTIVE;
         }
-        message = stanza_create_message(ctx, barejid, STANZA_TYPE_CHAT, msg, state);
+        jid = strdup(barejid);
+    }
+
+    char *id = create_unique_id("msg");
+    xmpp_stanza_t *message = stanza_create_message(ctx, id, barejid, STANZA_TYPE_CHAT, msg);
+    free(jid);
+
+    if (state) {
+        stanza_attach_state(ctx, message, state);
+    }
+    stanza_attach_carbons_private(ctx, message);
+    if (prefs_get_boolean(PREF_RECEIPTS_REQUEST)) {
+        stanza_attach_receipt_request(ctx, message);
     }
 
     xmpp_send(conn, message);
     xmpp_stanza_release(message);
+
+    return id;
 }
 
 void
@@ -112,7 +163,9 @@ message_send_private(const char * const fulljid, const char * const msg)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *message = stanza_create_message(ctx, fulljid, STANZA_TYPE_CHAT, msg, NULL);
+    char *id = create_unique_id("prv");
+    xmpp_stanza_t *message = stanza_create_message(ctx, id, fulljid, STANZA_TYPE_CHAT, msg);
+    free(id);
 
     xmpp_send(conn, message);
     xmpp_stanza_release(message);
@@ -123,7 +176,9 @@ message_send_groupchat(const char * const roomjid, const char * const msg)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *message = stanza_create_message(ctx, roomjid, STANZA_TYPE_GROUPCHAT, msg, NULL);
+    char *id = create_unique_id("muc");
+    xmpp_stanza_t *message = stanza_create_message(ctx, id, roomjid, STANZA_TYPE_GROUPCHAT, msg);
+    free(id);
 
     xmpp_send(conn, message);
     xmpp_stanza_release(message);
@@ -146,7 +201,18 @@ message_send_invite(const char * const roomjid, const char * const contact,
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *stanza = stanza_create_invite(ctx, roomjid, contact, reason);
+    xmpp_stanza_t *stanza;
+
+
+    muc_member_type_t member_type = muc_member_type(roomjid);
+    if (member_type == MUC_MEMBER_TYPE_PUBLIC) {
+        log_debug("Sending direct invite to %s, for %s", contact, roomjid);
+        char *password = muc_password(roomjid);
+        stanza = stanza_create_invite(ctx, roomjid, contact, reason, password);
+    } else {
+        log_debug("Sending mediated invite to %s, for %s", contact, roomjid);
+        stanza = stanza_create_mediated_invite(ctx, roomjid, contact, reason);
+    }
 
     xmpp_send(conn, stanza);
     xmpp_stanza_release(stanza);
@@ -196,14 +262,13 @@ message_send_gone(const char * const jid)
 }
 
 static int
-_message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     char *id = xmpp_stanza_get_id(stanza);
     char *jid = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
     char *type = NULL;
-    if (error_stanza != NULL) {
+    if (error_stanza) {
         type = xmpp_stanza_get_attribute(error_stanza, STANZA_ATTR_TYPE);
     }
 
@@ -211,15 +276,15 @@ _message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     char *err_msg = stanza_get_error_message(stanza);
 
     GString *log_msg = g_string_new("message stanza error received");
-    if (id != NULL) {
+    if (id) {
         g_string_append(log_msg, " id=");
         g_string_append(log_msg, id);
     }
-    if (jid != NULL) {
+    if (jid) {
         g_string_append(log_msg, " from=");
         g_string_append(log_msg, jid);
     }
-    if (type != NULL) {
+    if (type) {
         g_string_append(log_msg, " type=");
         g_string_append(log_msg, type);
     }
@@ -230,7 +295,16 @@ _message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
     g_string_free(log_msg, TRUE);
 
-    handle_message_error(jid, type, err_msg);
+    if (!jid) {
+        ui_handle_error(err_msg);
+    } else if (type && (strcmp(type, "cancel") == 0)) {
+        log_info("Recipient %s not found: %s", jid, err_msg);
+        Jid *jidp = jid_create(jid);
+        chat_session_remove(jidp->barejid);
+        jid_destroy(jidp);
+    } else {
+        ui_handle_recipient_error(jid, err_msg);
+    }
 
     free(err_msg);
 
@@ -238,113 +312,120 @@ _message_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 }
 
 static int
-_muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     xmpp_ctx_t *ctx = connection_get_ctx();
     xmpp_stanza_t *xns_muc_user = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
     char *room = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (room == NULL) {
+    if (!room) {
         log_warning("Message received with no from attribute, ignoring");
         return 1;
     }
 
     // XEP-0045
     xmpp_stanza_t *invite = xmpp_stanza_get_child_by_name(xns_muc_user, STANZA_NAME_INVITE);
-    if (invite != NULL) {
-        char *invitor_jid = xmpp_stanza_get_attribute(invite, STANZA_ATTR_FROM);
-        if (invitor_jid == NULL) {
-            log_warning("Chat room invite received with no from attribute");
-            return 1;
-        }
+    if (!invite) {
+        return 1;
+    }
 
-        Jid *jidp = jid_create(invitor_jid);
-        if (jidp == NULL) {
-            return 1;
-        }
-        char *invitor = jidp->barejid;
+    char *invitor_jid = xmpp_stanza_get_attribute(invite, STANZA_ATTR_FROM);
+    if (!invitor_jid) {
+        log_warning("Chat room invite received with no from attribute");
+        return 1;
+    }
 
-        char *reason = NULL;
-        xmpp_stanza_t *reason_st = xmpp_stanza_get_child_by_name(invite, STANZA_NAME_REASON);
-        if (reason_st != NULL) {
-            reason = xmpp_stanza_get_text(reason_st);
-        }
+    Jid *jidp = jid_create(invitor_jid);
+    if (!jidp) {
+        return 1;
+    }
+    char *invitor = jidp->barejid;
 
-        handle_room_invite(INVITE_MEDIATED, invitor, room, reason);
-        jid_destroy(jidp);
-        if (reason != NULL) {
-            xmpp_free(ctx, reason);
-        }
+    char *reason = NULL;
+    xmpp_stanza_t *reason_st = xmpp_stanza_get_child_by_name(invite, STANZA_NAME_REASON);
+    if (reason_st) {
+        reason = xmpp_stanza_get_text(reason_st);
+    }
+
+    char *password = NULL;
+    xmpp_stanza_t *password_st = xmpp_stanza_get_child_by_name(xns_muc_user, STANZA_NAME_PASSWORD);
+    if (password_st) {
+        password = xmpp_stanza_get_text(password_st);
+    }
+
+    sv_ev_room_invite(INVITE_MEDIATED, invitor, room, reason, password);
+    jid_destroy(jidp);
+    if (reason) {
+        xmpp_free(ctx, reason);
+    }
+    if (password) {
+        xmpp_free(ctx, password);
     }
 
     return 1;
 }
 
 static int
-_conference_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_conference_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     xmpp_stanza_t *xns_conference = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE);
-    char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
-    char *room = NULL;
-    char *invitor = NULL;
-    char *reason = NULL;
 
-    if (from == NULL) {
+    char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (!from) {
         log_warning("Message received with no from attribute, ignoring");
         return 1;
     }
 
-    // XEP-0429
-    room = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_JID);
-    if (room == NULL) {
+    Jid *jidp = jid_create(from);
+    if (!jidp) {
         return 1;
     }
 
-    Jid *jidp = jid_create(from);
-    if (jidp == NULL) {
+    // XEP-0249
+    char *room = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_JID);
+    if (!room) {
         return 1;
     }
-    invitor = jidp->barejid;
 
-    reason = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_REASON);
-
-    handle_room_invite(INVITE_DIRECT, invitor, room, reason);
+    char *reason = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_REASON);
+    char *password = xmpp_stanza_get_attribute(xns_conference, STANZA_ATTR_PASSWORD);
 
+    sv_ev_room_invite(INVITE_DIRECT, jidp->barejid, room, reason, password);
     jid_destroy(jidp);
 
     return 1;
 }
 
 static int
-_captcha_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_captcha_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     xmpp_ctx_t *ctx = connection_get_ctx();
     char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (from == NULL) {
+    if (!from) {
         log_warning("Message received with no from attribute, ignoring");
         return 1;
     }
 
     // XEP-0158
     xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
-    if (body != NULL) {
-        char *message = xmpp_stanza_get_text(body);
-        if (message != NULL) {
-            handle_room_broadcast(from, message);
-            xmpp_free(ctx, message);
-        }
+    if (!body) {
+        return 1;
     }
 
+    char *message = xmpp_stanza_get_text(body);
+    if (!message) {
+        return 1;
+    }
+
+    sv_ev_room_broadcast(from, message);
+    xmpp_free(ctx, message);
+
     return 1;
 }
 
 static int
-_groupchat_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata)
+_groupchat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     xmpp_ctx_t *ctx = connection_get_ctx();
     char *message = NULL;
@@ -353,9 +434,9 @@ _groupchat_handler(xmpp_conn_t * const conn,
 
     // handle room subject
     xmpp_stanza_t *subject = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SUBJECT);
-    if (subject != NULL) {
+    if (subject) {
         message = xmpp_stanza_get_text(subject);
-        handle_room_subject(jid->barejid, jid->resourcepart, message);
+        sv_ev_room_subject(jid->barejid, jid->resourcepart, message);
         xmpp_free(ctx, message);
 
         jid_destroy(jid);
@@ -363,16 +444,22 @@ _groupchat_handler(xmpp_conn_t * const conn,
     }
 
     // handle room broadcasts
-    if (jid->resourcepart == NULL) {
+    if (!jid->resourcepart) {
         xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
-        if (body != NULL) {
-            message = xmpp_stanza_get_text(body);
-            if (message != NULL) {
-                handle_room_broadcast(room_jid, message);
-                xmpp_free(ctx, message);
-            }
+        if (!body) {
+            jid_destroy(jid);
+            return 1;
         }
 
+        message = xmpp_stanza_get_text(body);
+        if (!message) {
+            jid_destroy(jid);
+            return 1;
+        }
+
+        sv_ev_room_broadcast(room_jid, message);
+        xmpp_free(ctx, message);
+
         jid_destroy(jid);
         return 1;
     }
@@ -390,32 +477,191 @@ _groupchat_handler(xmpp_conn_t * const conn,
         return 1;
     }
 
+    xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
+
+    // check for and deal with message
+    if (!body) {
+        jid_destroy(jid);
+        return 1;
+    }
+
+    message = xmpp_stanza_get_text(body);
+    if (!message) {
+        jid_destroy(jid);
+        return 1;
+    }
+
     // determine if the notifications happened whilst offline
     GTimeVal tv_stamp;
     gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
+    if (delayed) {
+        sv_ev_room_history(jid->barejid, jid->resourcepart, tv_stamp, message);
+    } else {
+        sv_ev_room_message(jid->barejid, jid->resourcepart, message);
+    }
+
+    xmpp_free(ctx, message);
+    jid_destroy(jid);
+
+    return 1;
+}
+
+void
+_message_send_receipt(const char * const fulljid, const char * const message_id)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *message = xmpp_stanza_new(ctx);
+    char *id = create_unique_id("receipt");
+    xmpp_stanza_set_name(message, STANZA_NAME_MESSAGE);
+    xmpp_stanza_set_id(message, id);
+    xmpp_stanza_set_attribute(message, STANZA_ATTR_TO, fulljid);
+
+    xmpp_stanza_t *receipt = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(receipt, "received");
+    xmpp_stanza_set_ns(receipt, STANZA_NS_RECEIPTS);
+    xmpp_stanza_set_attribute(receipt, STANZA_ATTR_ID, message_id);
+
+    xmpp_stanza_add_child(message, receipt);
+    xmpp_stanza_release(receipt);
+
+    xmpp_send(conn, message);
+    xmpp_stanza_release(message);
+}
+
+static int
+_receipt_received_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
+{
+    xmpp_stanza_t *receipt = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS);
+    char *name = xmpp_stanza_get_name(receipt);
+    if (g_strcmp0(name, "received") != 0) {
+        return 1;
+    }
+
+    char *id = xmpp_stanza_get_attribute(receipt, STANZA_ATTR_ID);
+    if (!id) {
+        return 1;
+    }
+
+    char *fulljid = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (!fulljid) {
+        return 1;
+    }
+
+    Jid *jidp = jid_create(fulljid);
+    sv_ev_message_receipt(jidp->barejid, id);
+    jid_destroy(jidp);
+
+    return 1;
+}
+
+void
+_receipt_request_handler(xmpp_stanza_t * const stanza)
+{
+    if (!prefs_get_boolean(PREF_RECEIPTS_SEND)) {
+        return;
+    }
+
+    char *id = xmpp_stanza_get_id(stanza);
+    if (!id) {
+        return;
+    }
+
+    xmpp_stanza_t *receipts = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_RECEIPTS);
+    if (!receipts) {
+        return;
+    }
+
+    char *receipts_name = xmpp_stanza_get_name(receipts);
+    if (g_strcmp0(receipts_name, "request") != 0) {
+        return;
+    }
+
+    gchar *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    Jid *jid = jid_create(from);
+    _message_send_receipt(jid->fulljid, id);
+    jid_destroy(jid);
+}
+
+void
+_private_chat_handler(xmpp_stanza_t * const stanza, const char * const fulljid)
+{
     xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
+    if (!body) {
+        return;
+    }
 
-    // check for and deal with message
-    if (body != NULL) {
-        message = xmpp_stanza_get_text(body);
-        if (message != NULL) {
-            if (delayed) {
-                handle_room_history(jid->barejid, jid->resourcepart, tv_stamp, message);
-            } else {
-                handle_room_message(jid->barejid, jid->resourcepart, message);
+    char *message = xmpp_stanza_get_text(body);
+    if (!message) {
+        return;
+    }
+
+    GTimeVal tv_stamp;
+    gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
+    if (delayed) {
+        sv_ev_delayed_private_message(fulljid, message, tv_stamp);
+    } else {
+        sv_ev_incoming_private_message(fulljid, message);
+    }
+
+    xmpp_ctx_t *ctx = connection_get_ctx();
+    xmpp_free(ctx, message);
+}
+
+static gboolean
+_handle_carbons(xmpp_stanza_t * const stanza)
+{
+    xmpp_stanza_t *carbons = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CARBONS);
+    if (!carbons) {
+        return FALSE;
+    }
+
+    char *name = xmpp_stanza_get_name(carbons);
+    if ((g_strcmp0(name, "received") == 0) || (g_strcmp0(name, "sent")) == 0) {
+        xmpp_stanza_t *forwarded = xmpp_stanza_get_child_by_ns(carbons, STANZA_NS_FORWARD);
+        xmpp_stanza_t *message = xmpp_stanza_get_child_by_name(forwarded, STANZA_NAME_MESSAGE);
+
+        xmpp_ctx_t *ctx = connection_get_ctx();
+
+        gchar *to = xmpp_stanza_get_attribute(message, STANZA_ATTR_TO);
+        gchar *from = xmpp_stanza_get_attribute(message, STANZA_ATTR_FROM);
+
+        // happens when receive a carbon of a self sent message
+        if (!to) to = from;
+
+        Jid *jid_from = jid_create(from);
+        Jid *jid_to = jid_create(to);
+        Jid *my_jid = jid_create(jabber_get_fulljid());
+
+        // check for and deal with message
+        xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(message, STANZA_NAME_BODY);
+        if (body) {
+            char *message = xmpp_stanza_get_text(body);
+            if (message) {
+                // if we are the recipient, treat as standard incoming message
+                if(g_strcmp0(my_jid->barejid, jid_to->barejid) == 0){
+                    sv_ev_incoming_message(jid_from->barejid, jid_from->resourcepart, message);
+                }
+                // else treat as a sent message
+                else{
+                    sv_ev_carbon(jid_to->barejid, message);
+                }
+                xmpp_free(ctx, message);
             }
-            xmpp_free(ctx, message);
         }
-    }
 
-    jid_destroy(jid);
+        jid_destroy(jid_from);
+        jid_destroy(jid_to);
+        jid_destroy(my_jid);
 
-    return 1;
+        return TRUE;
+    }
+
+    return FALSE;
 }
 
 static int
-_chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     // ignore if type not chat or absent
     char *type = xmpp_stanza_get_type(stanza);
@@ -423,6 +669,12 @@ _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
         return 1;
     }
 
+    // check if carbon message
+    gboolean res = _handle_carbons(stanza);
+    if (res) {
+        return 1;
+    }
+
     // ignore handled namespaces
     xmpp_stanza_t *conf = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE);
     xmpp_stanza_t *mucuser = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
@@ -431,76 +683,57 @@ _chat_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
         return 1;
     }
 
-    xmpp_ctx_t *ctx = connection_get_ctx();
     gchar *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
-
     Jid *jid = jid_create(from);
 
     // private message from chat room use full jid (room/nick)
     if (muc_active(jid->barejid)) {
-        // determine if the notifications happened whilst offline
-        GTimeVal tv_stamp;
-        gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
-
-        // check for and deal with message
-        xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
-        if (body != NULL) {
-            char *message = xmpp_stanza_get_text(body);
-            if (message != NULL) {
-                if (delayed) {
-                    handle_delayed_private_message(jid->str, message, tv_stamp);
-                } else {
-                    handle_incoming_private_message(jid->str, message);
-                }
-                xmpp_free(ctx, message);
-            }
-        }
-
-        jid_destroy(jid);
+        _private_chat_handler(stanza, jid->fulljid);
         return 1;
+    }
 
     // standard chat message, use jid without resource
-    } else {
-        // determine if the notifications happened whilst offline
-        GTimeVal tv_stamp;
-        gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
-
-        // check for and deal with message
-        xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
-        if (body != NULL) {
-            char *message = xmpp_stanza_get_text(body);
-            if (message != NULL) {
-                if (delayed) {
-                    handle_delayed_message(jid->barejid, message, tv_stamp);
-                } else {
-                    handle_incoming_message(jid->barejid, jid->resourcepart, message);
-                }
-                xmpp_free(ctx, message);
-            }
-        }
+    GTimeVal tv_stamp;
+    gboolean delayed = stanza_get_delay(stanza, &tv_stamp);
 
-        // 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);
+    xmpp_stanza_t *body = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_BODY);
+    if (body) {
+        char *message = xmpp_stanza_get_text(body);
+        if (message) {
+            if (delayed) {
+                sv_ev_delayed_message(jid->barejid, message, tv_stamp);
             } else {
-                handle_activity(jid->barejid, jid->resourcepart, FALSE);
+                sv_ev_incoming_message(jid->barejid, jid->resourcepart, message);
             }
+
+            _receipt_request_handler(stanza);
+
+            xmpp_ctx_t *ctx = connection_get_ctx();
+            xmpp_free(ctx, message);
         }
+    }
 
-        jid_destroy(jid);
-        return 1;
+    // 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) {
+            sv_ev_gone(jid->barejid, jid->resourcepart);
+        } else if (typing) {
+            sv_ev_typing(jid->barejid, jid->resourcepart);
+        } else if (paused) {
+            sv_ev_paused(jid->barejid, jid->resourcepart);
+        } else if (inactive) {
+            sv_ev_inactive(jid->barejid, jid->resourcepart);
+        } else if (stanza_contains_chat_state(stanza)) {
+            sv_ev_activity(jid->barejid, jid->resourcepart, TRUE);
+        } else {
+            sv_ev_activity(jid->barejid, jid->resourcepart, FALSE);
+        }
     }
-}
\ No newline at end of file
+
+    jid_destroy(jid);
+    return 1;
+}
diff --git a/src/xmpp/message.h b/src/xmpp/message.h
index b3410dc9..6fbc27bd 100644
--- a/src/xmpp/message.h
+++ b/src/xmpp/message.h
@@ -1,7 +1,7 @@
 /*
  * message.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 65384a0d..e46730e3 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -1,7 +1,7 @@
 /*
  * presence.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -44,7 +44,8 @@
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
-#include "server_events.h"
+#include "ui/ui.h"
+#include "event/server_events.h"
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
@@ -170,7 +171,7 @@ presence_sub_request_exists(const char * const bare_jid)
     GSList *requests_p = autocomplete_create_list(sub_requests_ac);
     GSList *requests = requests_p;
 
-    while (requests != NULL) {
+    while (requests) {
         if (strcmp(requests->data, bare_jid) == 0) {
             result = TRUE;
             break;
@@ -178,7 +179,7 @@ presence_sub_request_exists(const char * const bare_jid)
         requests = g_slist_next(requests);
     }
 
-    if (requests_p != NULL) {
+    if (requests_p) {
         g_slist_free_full(requests_p, free);
     }
 
@@ -192,27 +193,22 @@ presence_reset_sub_request_search(void)
 }
 
 void
-presence_update(const resource_presence_t presence_type, const char * const msg,
-    const int idle)
+presence_send(const resource_presence_t presence_type, const char * const msg, const int idle)
 {
     if (jabber_get_connection_status() != JABBER_CONNECTED) {
         log_warning("Error setting presence, not connected.");
         return;
     }
 
-    if (msg != NULL) {
-        log_debug("Updating presence: %s, \"%s\"",
-            string_from_resource_presence(presence_type), msg);
+    if (msg) {
+        log_debug("Updating presence: %s, \"%s\"", string_from_resource_presence(presence_type), msg);
     } else {
-        log_debug("Updating presence: %s",
-            string_from_resource_presence(presence_type));
+        log_debug("Updating presence: %s", string_from_resource_presence(presence_type));
     }
 
     xmpp_ctx_t * const ctx = connection_get_ctx();
     xmpp_conn_t * const conn = connection_get_conn();
-    const int pri =
-        accounts_get_priority_for_presence_type(jabber_get_account_name(),
-                                                presence_type);
+    const int pri = accounts_get_priority_for_presence_type(jabber_get_account_name(), presence_type);
     const char *show = stanza_get_presence_string_from_type(presence_type);
 
     connection_set_presence_message(msg);
@@ -245,11 +241,11 @@ _send_room_presence(xmpp_conn_t *conn, xmpp_stanza_t *presence)
     GList *rooms_p = muc_rooms();
     GList *rooms = rooms_p;
 
-    while (rooms != NULL) {
+    while (rooms) {
         const char *room = rooms->data;
         const char *nick = muc_nick(room);
 
-        if (nick != NULL) {
+        if (nick) {
             char *full_room_jid = create_fulljid(room, nick);
 
             xmpp_stanza_set_attribute(presence, STANZA_ATTR_TO, full_room_jid);
@@ -261,7 +257,7 @@ _send_room_presence(xmpp_conn_t *conn, xmpp_stanza_t *presence)
         rooms = g_list_next(rooms);
     }
 
-    if (rooms_p != NULL) {
+    if (rooms_p) {
         g_list_free(rooms_p);
     }
 }
@@ -333,7 +329,7 @@ presence_leave_chat_room(const char * const room_jid)
     xmpp_conn_t *conn = connection_get_conn();
     char *nick = muc_nick(room_jid);
 
-    if (nick != NULL) {
+    if (nick) {
         xmpp_stanza_t *presence = stanza_create_room_leave_presence(ctx, room_jid,
             nick);
         xmpp_send(conn, presence);
@@ -350,11 +346,11 @@ _presence_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     xmpp_stanza_t *error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X);
     char *xmlns = NULL;
-    if (x != NULL) {
+    if (x) {
         xmlns = xmpp_stanza_get_ns(x);
     }
     char *type = NULL;
-    if (error_stanza != NULL) {
+    if (error_stanza) {
         type = xmpp_stanza_get_attribute(error_stanza, STANZA_ATTR_TYPE);
     }
 
@@ -364,7 +360,7 @@ _presence_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
         char *error_cond = NULL;
         xmpp_stanza_t *reason_st = xmpp_stanza_get_child_by_ns(error_stanza, STANZA_NS_STANZAS);
-        if (reason_st != NULL) {
+        if (reason_st) {
             error_cond = xmpp_stanza_get_name(reason_st);
         }
         if (error_cond == NULL) {
@@ -372,7 +368,10 @@ _presence_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
         }
 
         log_info("Error joining room: %s, reason: %s", fulljid->barejid, error_cond);
-        handle_room_join_error(fulljid->barejid, error_cond);
+        if (muc_active(fulljid->barejid)) {
+            muc_leave(fulljid->barejid);
+        }
+        ui_handle_room_join_error(fulljid->barejid, error_cond);
         jid_destroy(fulljid);
         return 1;
     }
@@ -381,15 +380,15 @@ _presence_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     char *err_msg = stanza_get_error_message(stanza);
 
     GString *log_msg = g_string_new("presence stanza error received");
-    if (id != NULL) {
+    if (id) {
         g_string_append(log_msg, " id=");
         g_string_append(log_msg, id);
     }
-    if (from != NULL) {
+    if (from) {
         g_string_append(log_msg, " from=");
         g_string_append(log_msg, from);
     }
-    if (type != NULL) {
+    if (type) {
         g_string_append(log_msg, " type=");
         g_string_append(log_msg, type);
     }
@@ -400,7 +399,11 @@ _presence_error_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
 
     g_string_free(log_msg, TRUE);
 
-    handle_presence_error(from, type, err_msg);
+    if (from) {
+        ui_handle_recipient_error(from, err_msg);
+    } else {
+        ui_handle_error(err_msg);
+    }
 
     free(err_msg);
 
@@ -416,7 +419,7 @@ _unsubscribed_handler(xmpp_conn_t * const conn,
     Jid *from_jid = jid_create(from);
     log_debug("Unsubscribed presence handler fired for %s", from);
 
-    handle_subscription(from_jid->barejid, PRESENCE_UNSUBSCRIBED);
+    sv_ev_subscription(from_jid->barejid, PRESENCE_UNSUBSCRIBED);
     autocomplete_remove(sub_requests_ac, from_jid->barejid);
 
     jid_destroy(from_jid);
@@ -432,7 +435,7 @@ _subscribed_handler(xmpp_conn_t * const conn,
     Jid *from_jid = jid_create(from);
     log_debug("Subscribed presence handler fired for %s", from);
 
-    handle_subscription(from_jid->barejid, PRESENCE_SUBSCRIBED);
+    sv_ev_subscription(from_jid->barejid, PRESENCE_SUBSCRIBED);
     autocomplete_remove(sub_requests_ac, from_jid->barejid);
 
     jid_destroy(from_jid);
@@ -452,7 +455,7 @@ _subscribe_handler(xmpp_conn_t * const conn,
         return 1;
     }
 
-    handle_subscription(from_jid->barejid, PRESENCE_SUBSCRIBE);
+    sv_ev_subscription(from_jid->barejid, PRESENCE_SUBSCRIBE);
     autocomplete_add(sub_requests_ac, from_jid->barejid);
 
     jid_destroy(from_jid);
@@ -464,6 +467,8 @@ static int
 _unavailable_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
 {
+    ui_input_nonblocking(TRUE);
+
     const char *jid = xmpp_conn_get_jid(conn);
     char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
     log_debug("Unavailable presence handler fired for %s", from);
@@ -479,15 +484,15 @@ _unavailable_handler(xmpp_conn_t * const conn,
     char *status_str = stanza_get_status(stanza, NULL);
 
     if (strcmp(my_jid->barejid, from_jid->barejid) !=0) {
-        if (from_jid->resourcepart != NULL) {
-            handle_contact_offline(from_jid->barejid, from_jid->resourcepart, status_str);
+        if (from_jid->resourcepart) {
+            sv_ev_contact_offline(from_jid->barejid, from_jid->resourcepart, status_str);
 
         // hack for servers that do not send full jid with unavailable presence
         } else {
-            handle_contact_offline(from_jid->barejid, "__prof_default", status_str);
+            sv_ev_contact_offline(from_jid->barejid, "__prof_default", status_str);
         }
     } else {
-        if (from_jid->resourcepart != NULL) {
+        if (from_jid->resourcepart) {
             connection_remove_available_resource(from_jid->resourcepart);
         }
     }
@@ -517,7 +522,7 @@ _handle_caps(char *jid, XMPPCaps *caps)
             }
         }
 
-    // unsupported hash, xep-0115, assoiciate with JID, no cache
+    // unsupported hash, xep-0115, associate with JID, no cache
     } else if (caps->hash) {
         log_info("Hash %s not supported: %s, sending service discovery request", caps->hash, jid);
         char *id = create_unique_id("caps");
@@ -539,6 +544,8 @@ static int
 _available_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
 {
+    ui_input_nonblocking(TRUE);
+
     // handler still fires if error
     if (g_strcmp0(xmpp_stanza_get_type(stanza), STANZA_TYPE_ERROR) == 0) {
         return 1;
@@ -596,7 +603,7 @@ _available_handler(xmpp_conn_t * const conn,
     if (g_strcmp0(xmpp_presence->jid->barejid, my_jid->barejid) == 0) {
         connection_add_available_resource(resource);
     } else {
-        handle_contact_online(xmpp_presence->jid->barejid, resource, xmpp_presence->last_activity);
+        sv_ev_contact_online(xmpp_presence->jid->barejid, resource, xmpp_presence->last_activity);
     }
 
     jid_destroy(my_jid);
@@ -611,7 +618,7 @@ _send_caps_request(char *node, char *caps_key, char *id, char *from)
     xmpp_ctx_t *ctx = connection_get_ctx();
     xmpp_conn_t *conn = connection_get_conn();
 
-    if (node != NULL) {
+    if (node) {
         log_debug("Node string: %s.", node);
         if (!caps_contains(caps_key)) {
             log_debug("Capabilities not cached for '%s', sending discovery IQ.", from);
@@ -629,6 +636,8 @@ _send_caps_request(char *node, char *caps_key, char *id, char *from)
 static int
 _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
+    ui_input_nonblocking(TRUE);
+
     char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
     char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
@@ -682,27 +691,27 @@ _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void *
                     char *new_jid = stanza_get_muc_destroy_alternative_room(stanza);
                     char *password = stanza_get_muc_destroy_alternative_password(stanza);
                     char *reason = stanza_get_muc_destroy_reason(stanza);
-                    handle_room_destroyed(room, new_jid, password, reason);
+                    sv_ev_room_destroyed(room, new_jid, password, reason);
                     free(password);
                     free(reason);
 
                 // kicked from room
-                } else if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0) != NULL) {
+                } else if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
                     char *actor = stanza_get_actor(stanza);
                     char *reason = stanza_get_reason(stanza);
-                    handle_room_kicked(room, actor, reason);
+                    sv_ev_room_kicked(room, actor, reason);
                     free(reason);
 
                 // banned from room
-                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0) != NULL) {
+                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
                     char *actor = stanza_get_actor(stanza);
                     char *reason = stanza_get_reason(stanza);
-                    handle_room_banned(room, actor, reason);
+                    sv_ev_room_banned(room, actor, reason);
                     free(reason);
 
                 // normal exit
                 } else {
-                    handle_leave_room(room);
+                    sv_ev_leave_room(room);
                 }
 
                 g_slist_free_full(status_codes, free);
@@ -713,7 +722,7 @@ _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void *
             gboolean config_required = stanza_muc_requires_config(stanza);
             char *actor = stanza_get_actor(stanza);
             char *reason = stanza_get_reason(stanza);
-            handle_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str);
+            sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str);
         }
 
     // handle presence from room members
@@ -732,22 +741,22 @@ _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void *
                 GSList *status_codes = stanza_get_status_codes_by_ns(stanza, STANZA_NS_MUC_USER);
 
                 // kicked from room
-                if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0) != NULL) {
+                if (g_slist_find_custom(status_codes, "307", (GCompareFunc)g_strcmp0)) {
                     char *actor = stanza_get_actor(stanza);
                     char *reason = stanza_get_reason(stanza);
-                    handle_room_occupent_kicked(room, nick, actor, reason);
+                    sv_ev_room_occupent_kicked(room, nick, actor, reason);
                     free(reason);
 
                 // banned from room
-                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0) != NULL) {
+                } else if (g_slist_find_custom(status_codes, "301", (GCompareFunc)g_strcmp0)) {
                     char *actor = stanza_get_actor(stanza);
                     char *reason = stanza_get_reason(stanza);
-                    handle_room_occupent_banned(room, nick, actor, reason);
+                    sv_ev_room_occupent_banned(room, nick, actor, reason);
                     free(reason);
 
                 // normal exit
                 } else {
-                    handle_room_occupant_offline(room, nick, "offline", status_str);
+                    sv_ev_room_occupant_offline(room, nick, "offline", status_str);
                 }
 
                 g_slist_free_full(status_codes, free);
@@ -765,7 +774,7 @@ _muc_user_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void *
 
             char *actor = stanza_get_actor(stanza);
             char *reason = stanza_get_reason(stanza);
-            handle_muc_occupant_online(room, nick, jid, role, affiliation, actor, reason, show_str, status_str);
+            sv_ev_muc_occupant_online(room, nick, jid, role, affiliation, actor, reason, show_str, status_str);
         }
     }
 
diff --git a/src/xmpp/presence.h b/src/xmpp/presence.h
index e704aea0..90b83473 100644
--- a/src/xmpp/presence.h
+++ b/src/xmpp/presence.h
@@ -1,7 +1,7 @@
 /*
  * presence.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index 3449c8d5..5c9fa5d4 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -1,7 +1,7 @@
 /*
  * roster.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -41,7 +41,9 @@
 
 #include "log.h"
 #include "profanity.h"
-#include "server_events.h"
+#include "ui/ui.h"
+#include "event/server_events.h"
+#include "event/client_events.h"
 #include "tools/autocomplete.h"
 #include "xmpp/connection.h"
 #include "xmpp/roster.h"
@@ -131,7 +133,7 @@ roster_send_add_to_group(const char * const group, PContact contact)
 {
     GSList *groups = p_contact_groups(contact);
     GSList *new_groups = NULL;
-    while (groups != NULL) {
+    while (groups) {
         new_groups = g_slist_append(new_groups, strdup(groups->data));
         groups = g_slist_next(groups);
     }
@@ -141,7 +143,7 @@ roster_send_add_to_group(const char * const group, PContact contact)
     char *unique_id = create_unique_id(NULL);
     GroupData *data = malloc(sizeof(GroupData));
     data->group = strdup(group);
-    if (p_contact_name(contact) != NULL) {
+    if (p_contact_name(contact)) {
         data->name = strdup(p_contact_name(contact));
     } else {
         data->name = strdup(p_contact_barejid(contact));
@@ -161,9 +163,9 @@ static int
 _group_add_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
-    if (userdata != NULL) {
+    if (userdata) {
         GroupData *data = userdata;
-        handle_group_add(data->name, data->group);
+        ui_group_added(data->name, data->group);
         free(data->name);
         free(data->group);
         free(userdata);
@@ -176,7 +178,7 @@ roster_send_remove_from_group(const char * const group, PContact contact)
 {
     GSList *groups = p_contact_groups(contact);
     GSList *new_groups = NULL;
-    while (groups != NULL) {
+    while (groups) {
         if (strcmp(groups->data, group) != 0) {
             new_groups = g_slist_append(new_groups, strdup(groups->data));
         }
@@ -190,7 +192,7 @@ roster_send_remove_from_group(const char * const group, PContact contact)
     char *unique_id = create_unique_id(NULL);
     GroupData *data = malloc(sizeof(GroupData));
     data->group = strdup(group);
-    if (p_contact_name(contact) != NULL) {
+    if (p_contact_name(contact)) {
         data->name = strdup(p_contact_name(contact));
     } else {
         data->name = strdup(p_contact_barejid(contact));
@@ -208,9 +210,9 @@ static int
 _group_remove_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
-    if (userdata != NULL) {
+    if (userdata) {
         GroupData *data = userdata;
-        handle_group_remove(data->name, data->group);
+        ui_group_removed(data->name, data->group);
         free(data->name);
         free(data->group);
         free(userdata);
@@ -234,13 +236,14 @@ _roster_set_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     // if from attribute exists and it is not current users barejid, ignore push
     Jid *my_jid = jid_create(jabber_get_fulljid());
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
-    if ((from != NULL) && (strcmp(from, my_jid->barejid) != 0)) {
+    if (from && (strcmp(from, my_jid->barejid) != 0)) {
         jid_destroy(my_jid);
         return 1;
     }
     jid_destroy(my_jid);
 
     const char *barejid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID);
+    gchar *barejid_lower = g_utf8_strdown(barejid, -1);
     const char *name = xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME);
     const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION);
     const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK);
@@ -254,83 +257,83 @@ _roster_set_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     if (g_strcmp0(sub, "remove") == 0) {
         // remove barejid and name
         if (name == NULL) {
-            name = barejid;
+            name = barejid_lower;
         }
-
-        roster_remove(name, barejid);
-
-        handle_roster_remove(barejid);
+        roster_remove(name, barejid_lower);
+        ui_roster_remove(barejid_lower);
 
     // otherwise update local roster
     } else {
 
         // check for pending out subscriptions
         gboolean pending_out = FALSE;
-        if ((ask != NULL) && (strcmp(ask, "subscribe") == 0)) {
+        if (ask && (strcmp(ask, "subscribe") == 0)) {
             pending_out = TRUE;
         }
 
         GSList *groups = _get_groups_from_item(item);
 
         // update the local roster
-        PContact contact = roster_get_contact(barejid);
+        PContact contact = roster_get_contact(barejid_lower);
         if (contact == NULL) {
-            gboolean added = roster_add(barejid, name, groups, sub, pending_out);
+            gboolean added = roster_add(barejid_lower, name, groups, sub, pending_out);
             if (added) {
-                handle_roster_add(barejid, name);
+                ui_roster_add(barejid_lower, name);
             }
         } else {
-            handle_roster_update(barejid, name, groups, sub, pending_out);
+            sv_ev_roster_update(barejid_lower, name, groups, sub, pending_out);
         }
     }
 
+    g_free(barejid_lower);
+
     return 1;
 }
 
 static int
-_roster_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
-    void * const userdata)
+_roster_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata)
 {
     const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
 
+    if (g_strcmp0(id, "roster") != 0) {
+        return 1;
+    }
+
     // handle initial roster response
-    if (g_strcmp0(id, "roster") == 0) {
-        xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
-        xmpp_stanza_t *item = xmpp_stanza_get_children(query);
-
-        while (item != NULL) {
-            const char *barejid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID);
-            const char *name = xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME);
-            const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION);
-
-            // do not set nickname to empty string, set to NULL instead
-            if (name && (strlen(name) == 0)) {
-                name = NULL;
-            }
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+    xmpp_stanza_t *item = xmpp_stanza_get_children(query);
 
-            gboolean pending_out = FALSE;
-            const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK);
-            if (g_strcmp0(ask, "subscribe") == 0) {
-                pending_out = TRUE;
-            }
+    while (item) {
+        const char *barejid = xmpp_stanza_get_attribute(item, STANZA_ATTR_JID);
+        gchar *barejid_lower = g_utf8_strdown(barejid, -1);
+        const char *name = xmpp_stanza_get_attribute(item, STANZA_ATTR_NAME);
+        const char *sub = xmpp_stanza_get_attribute(item, STANZA_ATTR_SUBSCRIPTION);
 
-            GSList *groups = _get_groups_from_item(item);
+        // do not set nickname to empty string, set to NULL instead
+        if (name && (strlen(name) == 0)) name = NULL;
 
-            gboolean added = roster_add(barejid, name, groups, sub, pending_out);
+        gboolean pending_out = FALSE;
+        const char *ask = xmpp_stanza_get_attribute(item, STANZA_ATTR_ASK);
+        if (g_strcmp0(ask, "subscribe") == 0) {
+            pending_out = TRUE;
+        }
 
-            if (!added) {
-                log_warning("Attempt to add contact twice: %s", barejid);
-            }
+        GSList *groups = _get_groups_from_item(item);
 
-            item = xmpp_stanza_get_next(item);
+        gboolean added = roster_add(barejid_lower, name, groups, sub, pending_out);
+        if (!added) {
+            log_warning("Attempt to add contact twice: %s", barejid_lower);
         }
 
-        handle_roster_received();
-
-        resource_presence_t conn_presence = accounts_get_login_presence(jabber_get_account_name());
-        presence_update(conn_presence, NULL, 0);
+        g_free(barejid_lower);
+        item = xmpp_stanza_get_next(item);
     }
 
+    sv_ev_roster_received();
+
+    resource_presence_t conn_presence = accounts_get_login_presence(jabber_get_account_name());
+    cl_ev_presence_send(conn_presence, NULL, 0);
+
     return 1;
 }
 
@@ -340,10 +343,10 @@ _get_groups_from_item(xmpp_stanza_t *item)
     GSList *groups = NULL;
     xmpp_stanza_t *group_element = xmpp_stanza_get_children(item);
 
-    while (group_element != NULL) {
+    while (group_element) {
         if (strcmp(xmpp_stanza_get_name(group_element), STANZA_NAME_GROUP) == 0) {
             char *groupname = xmpp_stanza_get_text(group_element);
-            if (groupname != NULL) {
+            if (groupname) {
                 groups = g_slist_append(groups, groupname);
             }
         }
diff --git a/src/xmpp/roster.h b/src/xmpp/roster.h
index c29a674a..68811a5c 100644
--- a/src/xmpp/roster.h
+++ b/src/xmpp/roster.h
@@ -1,7 +1,7 @@
 /*
  * roster.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 4f1d412d..1f25239b 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -1,7 +1,7 @@
 /*
  * stanza.c
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -141,7 +141,7 @@ stanza_create_bookmarks_pubsub_add(xmpp_ctx_t *ctx, const char * const jid,
         xmpp_stanza_set_attribute(conference, STANZA_ATTR_AUTOJOIN, "false");
     }
 
-    if (nick != NULL) {
+    if (nick) {
         xmpp_stanza_t *nick_st = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(nick_st, STANZA_NAME_NICK);
         xmpp_stanza_set_text(nick_st, nick);
@@ -199,6 +199,44 @@ stanza_create_bookmarks_pubsub_add(xmpp_ctx_t *ctx, const char * const jid,
 #endif
 
 xmpp_stanza_t *
+stanza_enable_carbons(xmpp_ctx_t *ctx){
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    char *id = create_unique_id("carbons");
+
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
+    xmpp_stanza_set_id(iq, id);
+    free(id);
+
+    xmpp_stanza_t *carbons_enable = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(carbons_enable, STANZA_NAME_ENABLE);
+    xmpp_stanza_set_ns(carbons_enable, STANZA_NS_CARBONS);
+
+    xmpp_stanza_add_child(iq, carbons_enable);
+
+    return iq;
+}
+
+xmpp_stanza_t *
+stanza_disable_carbons(xmpp_ctx_t *ctx){
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    char *id = create_unique_id("carbons");
+
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
+    xmpp_stanza_set_id(iq, id);
+    free(id);
+
+    xmpp_stanza_t *carbons_disable = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(carbons_disable, STANZA_NAME_DISABLE);
+    xmpp_stanza_set_ns(carbons_disable, STANZA_NS_CARBONS);
+
+    xmpp_stanza_add_child(iq, carbons_disable);
+
+    return iq;
+}
+
+xmpp_stanza_t *
 stanza_create_chat_state(xmpp_ctx_t *ctx, const char * const fulljid, const char * const state)
 {
     xmpp_stanza_t *msg, *chat_state;
@@ -244,9 +282,44 @@ stanza_create_room_subject_message(xmpp_ctx_t *ctx, const char * const room, con
 }
 
 xmpp_stanza_t *
-stanza_create_message(xmpp_ctx_t *ctx, const char * const recipient,
-    const char * const type, const char * const message,
-    const char * const state)
+stanza_attach_state(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char * const state)
+{
+    xmpp_stanza_t *chat_state = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(chat_state, state);
+    xmpp_stanza_set_ns(chat_state, STANZA_NS_CHATSTATES);
+    xmpp_stanza_add_child(stanza, chat_state);
+    xmpp_stanza_release(chat_state);
+
+    return stanza;
+}
+
+xmpp_stanza_t *
+stanza_attach_carbons_private(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
+{
+    xmpp_stanza_t *private_carbon = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(private_carbon, "private");
+    xmpp_stanza_set_ns(private_carbon, STANZA_NS_CARBONS);
+    xmpp_stanza_add_child(stanza, private_carbon);
+    xmpp_stanza_release(private_carbon);
+
+    return stanza;
+}
+
+xmpp_stanza_t *
+stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
+{
+    xmpp_stanza_t *receipet_request = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(receipet_request, "request");
+    xmpp_stanza_set_ns(receipet_request, STANZA_NS_RECEIPTS);
+    xmpp_stanza_add_child(stanza, receipet_request);
+    xmpp_stanza_release(receipet_request);
+
+    return stanza;
+}
+
+xmpp_stanza_t *
+stanza_create_message(xmpp_ctx_t *ctx, char *id, const char * const recipient,
+    const char * const type, const char * const message)
 {
     xmpp_stanza_t *msg, *body, *text;
 
@@ -254,9 +327,7 @@ stanza_create_message(xmpp_ctx_t *ctx, const char * const recipient,
     xmpp_stanza_set_name(msg, STANZA_NAME_MESSAGE);
     xmpp_stanza_set_type(msg, type);
     xmpp_stanza_set_attribute(msg, STANZA_ATTR_TO, recipient);
-    char *id = create_unique_id(NULL);
     xmpp_stanza_set_id(msg, id);
-    free(id);
 
     body = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(body, STANZA_NAME_BODY);
@@ -268,14 +339,6 @@ stanza_create_message(xmpp_ctx_t *ctx, const char * const recipient,
     xmpp_stanza_add_child(msg, body);
     xmpp_stanza_release(body);
 
-    if (state != NULL) {
-        xmpp_stanza_t *chat_state = xmpp_stanza_new(ctx);
-        xmpp_stanza_set_name(chat_state, state);
-        xmpp_stanza_set_ns(chat_state, STANZA_NS_CHATSTATES);
-        xmpp_stanza_add_child(msg, chat_state);
-        xmpp_stanza_release(chat_state);
-    }
-
     return msg;
 }
 
@@ -311,7 +374,7 @@ stanza_create_roster_set(xmpp_ctx_t *ctx, const char * const id,
     xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
     xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
-    if (id != NULL) {
+    if (id) {
         xmpp_stanza_set_id(iq, id);
     }
 
@@ -323,13 +386,13 @@ stanza_create_roster_set(xmpp_ctx_t *ctx, const char * const id,
     xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
     xmpp_stanza_set_attribute(item, STANZA_ATTR_JID, jid);
 
-    if (handle != NULL) {
+    if (handle) {
         xmpp_stanza_set_attribute(item, STANZA_ATTR_NAME, handle);
     } else {
         xmpp_stanza_set_attribute(item, STANZA_ATTR_NAME, "");
     }
 
-    while (groups != NULL) {
+    while (groups) {
         xmpp_stanza_t *group = xmpp_stanza_new(ctx);
         xmpp_stanza_t *groupname = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(group, STANZA_NAME_GROUP);
@@ -351,7 +414,7 @@ stanza_create_roster_set(xmpp_ctx_t *ctx, const char * const id,
 
 xmpp_stanza_t *
 stanza_create_invite(xmpp_ctx_t *ctx, const char * const room,
-    const char * const contact, const char * const reason)
+    const char * const contact, const char * const reason, const char * const password)
 {
     xmpp_stanza_t *message, *x;
 
@@ -367,10 +430,53 @@ stanza_create_invite(xmpp_ctx_t *ctx, const char * const room,
     xmpp_stanza_set_ns(x, STANZA_NS_CONFERENCE);
 
     xmpp_stanza_set_attribute(x, STANZA_ATTR_JID, room);
-    if (reason != NULL) {
+    if (reason) {
         xmpp_stanza_set_attribute(x, STANZA_ATTR_REASON, reason);
     }
+    if (password) {
+        xmpp_stanza_set_attribute(x, STANZA_ATTR_PASSWORD, password);
+    }
+
+    xmpp_stanza_add_child(message, x);
+    xmpp_stanza_release(x);
+
+    return message;
+}
+
+xmpp_stanza_t *
+stanza_create_mediated_invite(xmpp_ctx_t *ctx, const char * const room,
+    const char * const contact, const char * const reason)
+{
+    xmpp_stanza_t *message, *x, *invite;
+
+    message = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(message, STANZA_NAME_MESSAGE);
+    xmpp_stanza_set_attribute(message, STANZA_ATTR_TO, room);
+    char *id = create_unique_id(NULL);
+    xmpp_stanza_set_id(message, id);
+    free(id);
+
+    x = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(x, STANZA_NAME_X);
+    xmpp_stanza_set_ns(x, STANZA_NS_MUC_USER);
+
+    invite = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(invite, STANZA_NAME_INVITE);
+    xmpp_stanza_set_attribute(invite, STANZA_ATTR_TO, contact);
+
+    if (reason) {
+        xmpp_stanza_t *reason_st = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(reason_st, STANZA_NAME_REASON);
+        xmpp_stanza_t *reason_txt = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_text(reason_txt, reason);
+        xmpp_stanza_add_child(reason_st, reason_txt);
+        xmpp_stanza_release(reason_txt);
+        xmpp_stanza_add_child(invite, reason_st);
+        xmpp_stanza_release(reason_st);
+    }
 
+    xmpp_stanza_add_child(x, invite);
+    xmpp_stanza_release(invite);
     xmpp_stanza_add_child(message, x);
     xmpp_stanza_release(x);
 
@@ -393,7 +499,7 @@ stanza_create_room_join_presence(xmpp_ctx_t * const ctx,
     xmpp_stanza_set_ns(x, STANZA_NS_MUC);
 
     // if a password was given
-    if (passwd != NULL) {
+    if (passwd) {
         xmpp_stanza_t *pass = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(pass, "password");
         xmpp_stanza_t *text = xmpp_stanza_new(ctx);
@@ -787,7 +893,7 @@ stanza_create_disco_info_iq(xmpp_ctx_t *ctx, const char * const id, const char *
     xmpp_stanza_t *query = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
     xmpp_stanza_set_ns(query, XMPP_NS_DISCO_INFO);
-    if (node != NULL) {
+    if (node) {
         xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node);
     }
 
@@ -858,7 +964,7 @@ stanza_create_ping_iq(xmpp_ctx_t *ctx, const char * const target)
     xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
     xmpp_stanza_set_type(iq, STANZA_TYPE_GET);
-    if (target != NULL) {
+    if (target) {
         xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, target);
     }
     char *id = create_unique_id("ping");
@@ -881,11 +987,11 @@ stanza_get_delay(xmpp_stanza_t * const stanza, GTimeVal *tv_stamp)
 {
     // first check for XEP-0203 delayed delivery
     xmpp_stanza_t *delay = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_DELAY);
-    if (delay != NULL) {
+    if (delay) {
         char *xmlns = xmpp_stanza_get_attribute(delay, STANZA_ATTR_XMLNS);
-        if ((xmlns != NULL) && (strcmp(xmlns, "urn:xmpp:delay") == 0)) {
+        if (xmlns && (strcmp(xmlns, "urn:xmpp:delay") == 0)) {
             char *stamp = xmpp_stanza_get_attribute(delay, STANZA_ATTR_STAMP);
-            if ((stamp != NULL) && (g_time_val_from_iso8601(stamp, tv_stamp))) {
+            if (stamp && (g_time_val_from_iso8601(stamp, tv_stamp))) {
                 return TRUE;
             }
         }
@@ -894,11 +1000,11 @@ stanza_get_delay(xmpp_stanza_t * const stanza, GTimeVal *tv_stamp)
     // otherwise check for XEP-0091 legacy delayed delivery
     // stanp format : CCYYMMDDThh:mm:ss
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X);
-    if (x != NULL) {
+    if (x) {
         char *xmlns = xmpp_stanza_get_attribute(x, STANZA_ATTR_XMLNS);
-        if ((xmlns != NULL) && (strcmp(xmlns, "jabber:x:delay") == 0)) {
+        if (xmlns && (strcmp(xmlns, "jabber:x:delay") == 0)) {
             char *stamp = xmpp_stanza_get_attribute(x, STANZA_ATTR_STAMP);
-            if ((stamp != NULL) && (g_time_val_from_iso8601(stamp, tv_stamp))) {
+            if (stamp && (g_time_val_from_iso8601(stamp, tv_stamp))) {
                 return TRUE;
             }
         }
@@ -914,17 +1020,17 @@ stanza_get_status(xmpp_stanza_t *stanza, char *def)
     xmpp_stanza_t *status =
         xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS);
 
-    if (status != NULL) {
+    if (status) {
         // xmpp_free and free may be different functions so convert all to
         // libc malloc
         char *s1, *s2 = NULL;
         s1 = xmpp_stanza_get_text(status);
-        if (s1 != NULL) {
+        if (s1) {
             s2 = strdup(s1);
             xmpp_free(ctx, s1);
         }
         return s2;
-    } else if (def != NULL) {
+    } else if (def) {
         return strdup(def);
     } else {
         return NULL;
@@ -938,17 +1044,17 @@ stanza_get_show(xmpp_stanza_t *stanza, char *def)
     xmpp_stanza_t *show =
         xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SHOW);
 
-    if (show != NULL) {
+    if (show) {
         // xmpp_free and free may be different functions so convert all to
         // libc malloc
         char *s1, *s2 = NULL;
         s1 = xmpp_stanza_get_text(show);
-        if (s1 != NULL) {
+        if (s1) {
             s2 = strdup(s1);
             xmpp_free(ctx, s1);
         }
         return s2;
-    } else if (def != NULL) {
+    } else if (def) {
         return strdup(def);
     } else {
         return NULL;
@@ -984,7 +1090,7 @@ stanza_muc_requires_config(xmpp_stanza_t * const stanza)
 
     // muc user namespaced x element
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
-    if (x != NULL) {
+    if (x) {
 
         // check for item element with owner affiliation
         xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(x, "item");
@@ -998,7 +1104,7 @@ stanza_muc_requires_config(xmpp_stanza_t * const stanza)
 
         // check for status code 201
         xmpp_stanza_t *x_children = xmpp_stanza_get_children(x);
-        while (x_children != NULL) {
+        while (x_children) {
             if (g_strcmp0(xmpp_stanza_get_name(x_children), STANZA_NAME_STATUS) == 0) {
                 char *code = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_CODE);
                 if (g_strcmp0(code, "201") == 0) {
@@ -1240,11 +1346,11 @@ stanza_is_room_nick_change(xmpp_stanza_t * const stanza)
 
     // muc user namespaced x element
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_MUC_USER);
-    if (x != NULL) {
+    if (x) {
 
         // check for status child element with 303 code
         xmpp_stanza_t *x_children = xmpp_stanza_get_children(x);
-        while (x_children != NULL) {
+        while (x_children) {
             if (g_strcmp0(xmpp_stanza_get_name(x_children), STANZA_NAME_STATUS) == 0) {
                 char *code = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_CODE);
                 if (g_strcmp0(code, "303") == 0) {
@@ -1268,7 +1374,7 @@ stanza_get_new_nick(xmpp_stanza_t * const stanza)
     xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_X);
     xmpp_stanza_t *x_children = xmpp_stanza_get_children(x);
 
-    while (x_children != NULL) {
+    while (x_children) {
         if (strcmp(xmpp_stanza_get_name(x_children), STANZA_NAME_ITEM) == 0) {
             char *nick = xmpp_stanza_get_attribute(x_children, STANZA_ATTR_NICK);
             if (nick) {
@@ -1382,9 +1488,9 @@ stanza_get_error_message(xmpp_stanza_t *stanza)
         xmpp_stanza_t *text_stanza = xmpp_stanza_get_child_by_name(error_stanza, STANZA_NAME_TEXT);
 
         // check for text
-        if (text_stanza != NULL) {
+        if (text_stanza) {
             gchar *err_msg = xmpp_stanza_get_text(text_stanza);
-            if (err_msg != NULL) {
+            if (err_msg) {
                 char *result =  strdup(err_msg);
                 xmpp_free(ctx, err_msg);
                 return result;
@@ -1419,7 +1525,7 @@ stanza_get_error_message(xmpp_stanza_t *stanza)
             int i;
             for (i = 0; i < ARRAY_SIZE(defined_conditions); i++) {
                 xmpp_stanza_t *cond_stanza = xmpp_stanza_get_child_by_name(error_stanza, defined_conditions[i]);
-                if (cond_stanza != NULL) {
+                if (cond_stanza) {
                     char *result = strdup(xmpp_stanza_get_name(cond_stanza));
                     return result;
                 }
@@ -1456,7 +1562,7 @@ void
 stanza_attach_show(xmpp_ctx_t * const ctx, xmpp_stanza_t * const presence,
     const char * const show)
 {
-    if (show != NULL) {
+    if (show) {
         xmpp_stanza_t *show_stanza = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(show_stanza, STANZA_NAME_SHOW);
         xmpp_stanza_t *text = xmpp_stanza_new(ctx);
@@ -1472,7 +1578,7 @@ void
 stanza_attach_status(xmpp_ctx_t * const ctx, xmpp_stanza_t * const presence,
     const char * const status)
 {
-    if (status != NULL) {
+    if (status) {
         xmpp_stanza_t *status_stanza = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(status_stanza, STANZA_NAME_STATUS);
         xmpp_stanza_t *text = xmpp_stanza_new(ctx);
@@ -1619,9 +1725,9 @@ stanza_parse_presence(xmpp_stanza_t *stanza, int *err)
 
     result->priority = 0;
     xmpp_stanza_t *priority_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_PRIORITY);
-    if (priority_stanza != NULL) {
+    if (priority_stanza) {
         char *priority_str = xmpp_stanza_get_text(priority_stanza);
-        if (priority_str != NULL) {
+        if (priority_str) {
             result->priority = atoi(priority_str);
         }
         free(priority_str);
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 84282401..89dbda57 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -1,7 +1,7 @@
 /*
  * stanza.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -77,6 +77,8 @@
 #define STANZA_NAME_VALUE "value"
 #define STANZA_NAME_DESTROY "destroy"
 #define STANZA_NAME_ACTOR "actor"
+#define STANZA_NAME_ENABLE "enable"
+#define STANZA_NAME_DISABLE "disable"
 
 // error conditions
 #define STANZA_NAME_BAD_REQUEST "bad-request"
@@ -133,6 +135,7 @@
 #define STANZA_ATTR_CATEGORY "category"
 #define STANZA_ATTR_REASON "reason"
 #define STANZA_ATTR_AUTOJOIN "autojoin"
+#define STANZA_ATTR_PASSWORD "password"
 
 #define STANZA_TEXT_AWAY "away"
 #define STANZA_TEXT_DND "dnd"
@@ -154,6 +157,9 @@
 #define STANZA_NS_CONFERENCE "jabber:x:conference"
 #define STANZA_NS_CAPTCHA "urn:xmpp:captcha"
 #define STANZA_NS_PUBSUB "http://jabber.org/protocol/pubsub"
+#define STANZA_NS_CARBONS "urn:xmpp:carbons:2"
+#define STANZA_NS_FORWARD "urn:xmpp:forward:0"
+#define STANZA_NS_RECEIPTS "urn:xmpp:receipts"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -178,12 +184,19 @@ typedef enum {
 
 xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx);
 
+xmpp_stanza_t * stanza_enable_carbons(xmpp_ctx_t *ctx);
+
+xmpp_stanza_t * stanza_disable_carbons(xmpp_ctx_t *ctx);
+
 xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx,
     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,
-    const char * const message, const char * const state);
+xmpp_stanza_t * stanza_attach_state(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char * const state);
+xmpp_stanza_t * stanza_attach_carbons_private(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
+xmpp_stanza_t * stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
+
+xmpp_stanza_t* stanza_create_message(xmpp_ctx_t *ctx, char *id,
+    const char * const recipient, const char * const type, const char * const message);
 
 xmpp_stanza_t* stanza_create_room_join_presence(xmpp_ctx_t * const ctx,
     const char * const full_room_jid, const char * const passwd);
@@ -202,6 +215,8 @@ xmpp_stanza_t* stanza_create_disco_info_iq(xmpp_ctx_t *ctx, const char * const i
     const char * const to, const char * const node);
 
 xmpp_stanza_t* stanza_create_invite(xmpp_ctx_t *ctx, const char * const room,
+    const char * const contact, const char * const reason, const char * const password);
+xmpp_stanza_t* stanza_create_mediated_invite(xmpp_ctx_t *ctx, const char * const room,
     const char * const contact, const char * const reason);
 
 gboolean stanza_contains_chat_state(xmpp_stanza_t *stanza);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index a004a4bf..398c9f46 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -1,7 +1,7 @@
 /*
  * xmpp.h
  *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2015 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -145,7 +145,8 @@ 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 msg);
+char* message_send_chat(const char * const barejid, const char * const msg);
+char* message_send_chat_encrypted(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);
@@ -167,11 +168,13 @@ char * presence_sub_request_find(const char * const search_str);
 void presence_join_room(char *room, char *nick, char * passwd);
 void presence_change_room_nick(const char * const room, const char * const nick);
 void presence_leave_chat_room(const char * const room_jid);
-void presence_update(resource_presence_t status, const char * const msg,
+void presence_send(resource_presence_t status, const char * const msg,
     int idle);
 gboolean presence_sub_request_exists(const char * const bare_jid);
 
 // iq functions
+void iq_enable_carbons();
+void iq_disable_carbons();
 void iq_send_software_version(const char * const fulljid);
 void iq_room_list_request(gchar *conferencejid);
 void iq_disco_info_request(gchar *jid);
@@ -189,7 +192,7 @@ void iq_send_caps_request_for_jid(const char * const to, const char * const id,
     const char * const node, const char * const ver);
 void iq_send_caps_request_legacy(const char * const to, const char * const id,
     const char * const node, const char * const ver);
-void iq_room_info_request(gchar *room);
+void iq_room_info_request(const char * const room, gboolean display_result);
 void iq_room_affiliation_list(const char * const room, char *affiliation);
 void iq_room_affiliation_set(const char * const room, const char * const jid, char *affiliation,
     const char * const reason);
diff --git a/tests/helpers.c b/tests/helpers.c
index 10310886..564b2716 100644
--- a/tests/helpers.c
+++ b/tests/helpers.c
@@ -85,6 +85,24 @@ void close_chat_sessions(void **state)
     close_preferences(NULL);
 }
 
+int
+utf8_pos_to_col(char *str, int utf8_pos)
+{
+    int col = 0;
+
+    int i = 0;
+    for (i = 0; i<utf8_pos; i++) {
+        col++;
+        gchar *ch = g_utf8_offset_to_pointer(str, i);
+        gunichar uni = g_utf8_get_char(ch);
+        if (g_unichar_iswide(uni)) {
+            col++;
+        }
+    }
+
+    return col;
+}
+
 static GCompareFunc cmp_func;
 
 void
diff --git a/tests/helpers.h b/tests/helpers.h
index 2d7af6e7..75d446d0 100644
--- a/tests/helpers.h
+++ b/tests/helpers.h
@@ -6,5 +6,7 @@ void close_preferences(void **state);
 void init_chat_sessions(void **state);
 void close_chat_sessions(void **state);
 
+int utf8_pos_to_col(char *str, int utf8_pos);
+
 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/log/stub_log.c b/tests/log/stub_log.c
index 8ace164a..f88871a7 100644
--- a/tests/log/stub_log.c
+++ b/tests/log/stub_log.c
@@ -50,8 +50,14 @@ log_level_t log_level_from_string(char *log_level)
 }
 
 void chat_log_init(void) {}
-void chat_log_chat(const gchar * const login, gchar *other,
-    const gchar * const msg, chat_log_direction_t direction, GTimeVal *tv_stamp) {}
+
+void chat_log_msg_out(const char * const barejid, const char * const msg) {}
+void chat_log_otr_msg_out(const char * const barejid, const char * const msg) {}
+
+void chat_log_msg_in(const char * const barejid, const char * const msg) {}
+void chat_log_msg_in_delayed(const char * const barejid, const char * msg, GTimeVal *tv_stamp) {}
+void chat_log_otr_msg_in(const char * const barejid, const char * const msg, gboolean was_decrypted) {}
+
 void chat_log_close(void) {}
 GSList * chat_log_get_previous(const gchar * const login,
     const gchar * const recipient)
diff --git a/tests/otr/stub_otr.c b/tests/otr/stub_otr.c
index 82994034..482f0a7f 100644
--- a/tests/otr/stub_otr.c
+++ b/tests/otr/stub_otr.c
@@ -41,6 +41,8 @@ char* otr_start_query(void)
 
 void otr_poll(void) {}
 void otr_on_connect(ProfAccount *account) {}
+void otr_on_message_recv(const char * const barejid, const char * const resource, const char * const message) {}
+void otr_on_message_send(ProfChatWin *chatwin, const char * const message) {}
 
 void otr_keygen(ProfAccount *account)
 {
@@ -52,6 +54,11 @@ gboolean otr_key_loaded(void)
     return (gboolean)mock();
 }
 
+char* otr_tag_message(const char * const msg)
+{
+    return NULL;
+}
+
 gboolean otr_is_secure(const char * const recipient)
 {
     return FALSE;
@@ -98,4 +105,4 @@ void otr_free_message(char *message) {}
 prof_otrpolicy_t otr_get_policy(const char * const recipient)
 {
     return PROF_OTRPOLICY_MANUAL;
-}
\ No newline at end of file
+}
diff --git a/tests/test_cmd_account.c b/tests/test_cmd_account.c
index d02fc5ba..bddc4c6d 100644
--- a/tests/test_cmd_account.c
+++ b/tests/test_cmd_account.c
@@ -941,9 +941,9 @@ void cmd_account_set_priority_updates_presence_when_account_connected_with_prese
 
     will_return(jabber_get_presence_message, "Free to chat");
 
-    expect_value(presence_update, status, RESOURCE_ONLINE);
-    expect_string(presence_update, msg, "Free to chat");
-    expect_value(presence_update, idle, 0);
+    expect_value(presence_send, status, RESOURCE_ONLINE);
+    expect_string(presence_send, msg, "Free to chat");
+    expect_value(presence_send, idle, 0);
 
     expect_cons_show("Updated online priority for account a_account: 10");
     expect_cons_show("");
diff --git a/tests/test_cmd_join.c b/tests/test_cmd_join.c
index 10ffa547..19824b3a 100644
--- a/tests/test_cmd_join.c
+++ b/tests/test_cmd_join.c
@@ -50,23 +50,6 @@ void cmd_join_shows_message_when_undefined(void **state)
     test_with_connection_status(JABBER_UNDEFINED);
 }
 
-void cmd_join_shows_usage_when_no_args(void **state)
-{
-    CommandHelp *help = malloc(sizeof(CommandHelp));
-    help->usage = "some usage";
-    gchar *args[] = { NULL };
-
-    will_return(jabber_get_connection_status, JABBER_CONNECTED);
-
-    expect_cons_show("Usage: some usage");
-    expect_cons_show("");
-
-    gboolean result = cmd_join(args, *help);
-    assert_true(result);
-
-    free(help);
-}
-
 void cmd_join_shows_error_message_when_invalid_room_jid(void **state)
 {
     CommandHelp *help = malloc(sizeof(CommandHelp));
diff --git a/tests/test_cmd_join.h b/tests/test_cmd_join.h
index 812aee87..a96fa435 100644
--- a/tests/test_cmd_join.h
+++ b/tests/test_cmd_join.h
@@ -2,7 +2,6 @@ void cmd_join_shows_message_when_disconnecting(void **state);
 void cmd_join_shows_message_when_connecting(void **state);
 void cmd_join_shows_message_when_disconnected(void **state);
 void cmd_join_shows_message_when_undefined(void **state);
-void cmd_join_shows_usage_when_no_args(void **state);
 void cmd_join_shows_error_message_when_invalid_room_jid(void **state);
 void cmd_join_uses_account_mucservice_when_no_service_specified(void **state);
 void cmd_join_uses_supplied_nick(void **state);
diff --git a/tests/test_cmd_otr.c b/tests/test_cmd_otr.c
index c6c6f7cf..dae17947 100644
--- a/tests/test_cmd_otr.c
+++ b/tests/test_cmd_otr.c
@@ -551,8 +551,8 @@ cmd_otr_start_sends_otr_query_message_to_current_recipeint(void **state)
     will_return(otr_key_loaded, TRUE);
     will_return(otr_start_query, query_message);
 
-    expect_string(message_send_chat, barejid, chatwin->barejid);
-    expect_string(message_send_chat, msg, query_message);
+    expect_string(message_send_chat_encrypted, barejid, chatwin->barejid);
+    expect_string(message_send_chat_encrypted, msg, query_message);
 
     gboolean result = cmd_otr(args, *help);
     assert_true(result);
diff --git a/tests/test_cmd_win.c b/tests/test_cmd_win.c
deleted file mode 100644
index bc19ebf3..00000000
--- a/tests/test_cmd_win.c
+++ /dev/null
@@ -1,41 +0,0 @@
-#include <stdarg.h>
-#include <stddef.h>
-#include <setjmp.h>
-#include <cmocka.h>
-#include <stdlib.h>
-#include <glib.h>
-
-#include "ui/ui.h"
-#include "ui/stub_ui.h"
-
-#include "command/commands.h"
-
-void cmd_win_shows_message_when_win_doesnt_exist(void **state)
-{
-    CommandHelp *help = malloc(sizeof(CommandHelp));
-    gchar *args[] = { "3", NULL };
-
-    expect_value(ui_switch_win, i, 3);
-    will_return(ui_switch_win, FALSE);
-
-    expect_cons_show("Window 3 does not exist.");
-
-    gboolean result = cmd_win(args, *help);
-    assert_true(result);
-
-    free(help);
-}
-
-void cmd_win_switches_to_given_win_when_exists(void **state)
-{
-    CommandHelp *help = malloc(sizeof(CommandHelp));
-    gchar *args[] = { "12", NULL };
-
-    expect_value(ui_switch_win, i, 12);
-    will_return(ui_switch_win, TRUE);
-
-    gboolean result = cmd_win(args, *help);
-    assert_true(result);
-
-    free(help);
-}
diff --git a/tests/test_cmd_win.h b/tests/test_cmd_win.h
deleted file mode 100644
index a0f61a6b..00000000
--- a/tests/test_cmd_win.h
+++ /dev/null
@@ -1,2 +0,0 @@
-void cmd_win_shows_message_when_win_doesnt_exist(void **state);
-void cmd_win_switches_to_given_win_when_exists(void **state);
diff --git a/tests/test_common.c b/tests/test_common.c
index 0c4530a0..980f2198 100644
--- a/tests/test_common.c
+++ b/tests/test_common.c
@@ -587,3 +587,47 @@ void utf8_display_len_all_wide(void **state)
     assert_int_equal(8, result);
 }
 
+void strip_quotes_does_nothing_when_no_quoted(void **state)
+{
+    char *input = "/cmd test string";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
+void strip_quotes_strips_first(void **state)
+{
+    char *input = "/cmd \"test string";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+    
+    free(result);
+}
+
+void strip_quotes_strips_last(void **state)
+{
+    char *input = "/cmd test string\"";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
+void strip_quotes_strips_both(void **state)
+{
+    char *input = "/cmd \"test string\"";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
diff --git a/tests/test_common.h b/tests/test_common.h
index 1866e73d..b4b98e5a 100644
--- a/tests/test_common.h
+++ b/tests/test_common.h
@@ -51,4 +51,8 @@ void utf8_display_len_1_non_wide(void **state);
 void utf8_display_len_1_wide(void **state);
 void utf8_display_len_non_wide(void **state);
 void utf8_display_len_wide(void **state);
-void utf8_display_len_all_wide(void **state);
\ No newline at end of file
+void utf8_display_len_all_wide(void **state);
+void strip_quotes_does_nothing_when_no_quoted(void **state);
+void strip_quotes_strips_first(void **state);
+void strip_quotes_strips_last(void **state);
+void strip_quotes_strips_both(void **state);
diff --git a/tests/test_history.c b/tests/test_history.c
deleted file mode 100644
index 1584b390..00000000
--- a/tests/test_history.c
+++ /dev/null
@@ -1,219 +0,0 @@
-#include <stdarg.h>
-#include <stddef.h>
-#include <setjmp.h>
-#include <cmocka.h>
-#include <stdlib.h>
-
-#include "tools/history.h"
-
-void previous_on_empty_returns_null(void **state)
-{
-    History history = history_new(10);
-    char *item = history_previous(history, "inp");
-
-    assert_null(item);
-}
-
-void next_on_empty_returns_null(void **state)
-{
-    History history = history_new(10);
-    char *item = history_next(history, "inp");
-
-    assert_null(item);
-}
-
-void previous_once_returns_last(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item = history_previous(history, "inp");
-
-    assert_string_equal("Hello", item);
-}
-
-void previous_twice_when_one_returns_first(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item1 = history_previous(history, NULL);
-    char *item2 = history_previous(history, item1);
-
-    assert_string_equal("Hello", item2);
-}
-
-void previous_always_stops_at_first(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item1 = history_previous(history, NULL);
-    char *item2 = history_previous(history, item1);
-    char *item3 = history_previous(history, item2);
-    char *item4 = history_previous(history, item3);
-    char *item5 = history_previous(history, item4);
-    char *item6 = history_previous(history, item5);
-
-    assert_string_equal("Hello", item6);
-}
-
-void previous_goes_to_correct_element(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-    history_append(history, "world");
-    history_append(history, "whats");
-    history_append(history, "going");
-    history_append(history, "on");
-    history_append(history, "here");
-
-    char *item1 = history_previous(history, NULL);
-    char *item2 = history_previous(history, item1);
-    char *item3 = history_previous(history, item2);
-
-    assert_string_equal("going", item3);
-}
-
-void prev_then_next_returns_empty(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item1 = history_previous(history, NULL);
-    char *item2 = history_next(history, item1);
-
-    assert_string_equal("", item2);
-}
-
-void prev_with_val_then_next_returns_val(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item1 = history_previous(history, "Oioi");
-    char *item2 = history_next(history, item1);
-
-    assert_string_equal("Oioi", item2);
-}
-
-void prev_with_val_then_next_twice_returns_null(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-
-    char *item1 = history_previous(history, "Oioi");
-    char *item2 = history_next(history, item1);
-    char *item3 = history_next(history, item2);
-
-    assert_null(item3);
-}
-
-void navigate_then_append_new(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-    history_append(history, "again");
-    history_append(history, "testing");
-    history_append(history, "history");
-    history_append(history, "append");
-
-    char *item1 = history_previous(history, "new text");
-    assert_string_equal("append", item1);
-
-    char *item2 = history_previous(history, item1);
-    assert_string_equal("history", item2);
-
-    char *item3 = history_previous(history, item2);
-    assert_string_equal("testing", item3);
-
-    char *item4 = history_next(history, item3);
-    assert_string_equal("history", item4);
-
-    char *item5 = history_next(history, item4);
-    assert_string_equal("append", item5);
-
-    char *item6 = history_next(history, item5);
-    assert_string_equal("new text", item6);
-}
-
-void edit_item_mid_history(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-    history_append(history, "again");
-    history_append(history, "testing");
-    history_append(history, "history");
-    history_append(history, "append");
-
-    char *item1 = history_previous(history, "new item");
-    assert_string_equal("append", item1);
-
-    char *item2 = history_previous(history, item1);
-    assert_string_equal("history", item2);
-
-    char *item3 = history_previous(history, item2);
-    assert_string_equal("testing", item3);
-
-    char *item4 = history_previous(history, "EDITED");
-    assert_string_equal("again", item4);
-
-    char *item5 = history_previous(history, item4);
-    assert_string_equal("Hello", item5);
-
-    char *item6 = history_next(history, item5);
-    assert_string_equal("again", item6);
-
-    char *item7 = history_next(history, item6);
-    assert_string_equal("EDITED", item7);
-
-    char *item8 = history_next(history, item7);
-    assert_string_equal("history", item8);
-
-    char *item9 = history_next(history, item8);
-    assert_string_equal("append", item9);
-
-    char *item10 = history_next(history, item9);
-    assert_string_equal("new item", item10);
-}
-
-void edit_previous_and_append(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "Hello");
-    history_append(history, "again");
-    history_append(history, "testing");
-    history_append(history, "history");
-    history_append(history, "append");
-
-    char *item1 = history_previous(history, "new item");
-    assert_string_equal("append", item1);
-
-    char *item2 = history_previous(history, item1);
-    assert_string_equal("history", item2);
-
-    char *item3 = history_previous(history, item2);
-    assert_string_equal("testing", item3);
-
-    history_append(history, "EDITED");
-
-    char *item4 = history_previous(history, NULL);
-    assert_string_equal("EDITED", item4);
-}
-
-void start_session_add_new_submit_previous(void **state)
-{
-    History history = history_new(10);
-    history_append(history, "hello");
-
-    char *item1 = history_previous(history, NULL);
-    assert_string_equal("hello", item1);
-
-    char *item2 = history_next(history, item1);
-    assert_string_equal("", item2);
-
-    char *item3 = history_previous(history, "new text");
-    assert_string_equal("hello", item3);
-
-    history_append(history, item3);
-}
diff --git a/tests/test_history.h b/tests/test_history.h
deleted file mode 100644
index e6e8ec3f..00000000
--- a/tests/test_history.h
+++ /dev/null
@@ -1,13 +0,0 @@
-void previous_on_empty_returns_null(void **state);
-void next_on_empty_returns_null(void **state);
-void previous_once_returns_last(void **state);
-void previous_twice_when_one_returns_first(void **state);
-void previous_always_stops_at_first(void **state);
-void previous_goes_to_correct_element(void **state);
-void prev_then_next_returns_empty(void **state);
-void prev_with_val_then_next_returns_val(void **state);
-void prev_with_val_then_next_twice_returns_null(void **state);
-void navigate_then_append_new(void **state);
-void edit_item_mid_history(void **state);
-void edit_previous_and_append(void **state);
-void start_session_add_new_submit_previous(void **state);
diff --git a/tests/test_keyhandlers.c b/tests/test_keyhandlers.c
new file mode 100644
index 00000000..a6d39143
--- /dev/null
+++ b/tests/test_keyhandlers.c
@@ -0,0 +1,734 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <locale.h>
+
+#include "ui/keyhandlers.h"
+#include "ui/inputwin.h"
+#include "tests/helpers.h"
+
+static char line[INP_WIN_MAX];
+
+// append
+
+void append_to_empty(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'a', 80);
+
+    assert_string_equal("a", line);
+    assert_int_equal(line_utf8_pos, 1);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_wide_to_empty(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);
+
+    assert_string_equal("å››", line);
+    assert_int_equal(line_utf8_pos, 1);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_to_single(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "a", 1);
+    line[1] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);
+
+    assert_string_equal("ab", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+
+void append_wide_to_single_non_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "a", 1);
+    line[1] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);
+
+    assert_string_equal("aå››", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_non_wide_to_single_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "å››", 1);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);
+
+    assert_string_equal("å››b", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_wide_to_single_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "å››", 1);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x4E09, 80);
+
+    assert_string_equal("四三", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_non_wide_when_overrun(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "0123456789å››1234567", 18);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 18;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+
+    assert_string_equal("0123456789å››1234567zzz", line);
+    assert_int_equal(line_utf8_pos, 21);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 3);
+}
+
+void insert_non_wide_to_non_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd", 4);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '0', 80);
+
+    assert_string_equal("ab0cd", line);
+    assert_int_equal(line_utf8_pos, 3);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void insert_single_non_wide_when_pad_scrolled(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);
+
+    assert_string_equal("AABAAAAAAAAAAAAA", line);
+    assert_int_equal(line_utf8_pos, 3);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 2);
+}
+
+void insert_many_non_wide_when_pad_scrolled(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'C', 12);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'D', 12);
+
+    assert_string_equal("AABCDAAAAAAAAAAAAA", line);
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 2);
+}
+
+void insert_single_non_wide_last_column(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcdefghijklmno", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);
+
+    assert_string_equal("abcdefg1hijklmno", line);
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 3);
+}
+
+void insert_many_non_wide_last_column(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcdefghijklmno", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '2', 5);
+
+    assert_string_equal("abcdefg12hijklmno", line);
+    assert_int_equal(line_utf8_pos, 9);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 4);
+}
+
+void ctrl_left_when_no_input(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_first_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_first_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start_of_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 8;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end_of_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 10;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_second_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 11;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start_of_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 12;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 14;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end_of_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 15;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_third_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 16;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 20;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 17);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_only_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       ", 7);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_start_whitespace_start_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "    hello", 9);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_start_whitespace_middle_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "    hello", 9);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 4);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words_start_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words_middle_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 9;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 7);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_word_overrun_to_left(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 18;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 14;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 9);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 9);
+}
+
+void ctrl_right_when_no_input(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_when_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 20;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_at_start(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_in_middle(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_middle_first(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_end_first(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 8;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_start_second(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 9;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_leading_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       someword", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 15);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_in_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       someword        adfasdf", 30);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 19;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 30);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_trailing_whitespace_from_middle(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword        ", 16);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
\ No newline at end of file
diff --git a/tests/test_keyhandlers.h b/tests/test_keyhandlers.h
new file mode 100644
index 00000000..4be429a9
--- /dev/null
+++ b/tests/test_keyhandlers.h
@@ -0,0 +1,47 @@
+void append_to_empty(void **state);
+void append_wide_to_empty(void **state);
+void append_to_single(void **state);
+void append_wide_to_single_non_wide(void **state);
+void append_non_wide_to_single_wide(void **state);
+void append_wide_to_single_wide(void **state);
+void append_non_wide_when_overrun(void **state);
+
+void insert_non_wide_to_non_wide(void **state);
+void insert_single_non_wide_when_pad_scrolled(void **state);
+void insert_many_non_wide_when_pad_scrolled(void **state);
+void insert_single_non_wide_last_column(void **state);
+void insert_many_non_wide_last_column(void **state);
+
+void ctrl_left_when_no_input(void **state);
+void ctrl_left_when_at_start(void **state);
+void ctrl_left_when_in_first_word(void **state);
+void ctrl_left_when_in_first_space(void **state);
+void ctrl_left_when_at_start_of_second_word(void **state);
+void ctrl_left_when_in_second_word(void **state);
+void ctrl_left_when_at_end_of_second_word(void **state);
+void ctrl_left_when_in_second_space(void **state);
+void ctrl_left_when_at_start_of_third_word(void **state);
+void ctrl_left_when_in_third_word(void **state);
+void ctrl_left_when_at_end_of_third_word(void **state);
+void ctrl_left_when_in_third_space(void **state);
+void ctrl_left_when_at_end(void **state);
+void ctrl_left_when_in_only_whitespace(void **state);
+void ctrl_left_when_start_whitespace_start_of_word(void **state);
+void ctrl_left_when_start_whitespace_middle_of_word(void **state);
+void ctrl_left_in_whitespace_between_words(void **state);
+void ctrl_left_in_whitespace_between_words_start_of_word(void **state);
+void ctrl_left_in_whitespace_between_words_middle_of_word(void **state);
+void ctrl_left_when_word_overrun_to_left(void **state);
+
+void ctrl_right_when_no_input(void **state);
+void ctrl_right_when_at_end(void **state);
+void ctrl_right_one_word_at_start(void **state);
+void ctrl_right_one_word_in_middle(void **state);
+void ctrl_right_one_word_at_end(void **state);
+void ctrl_right_two_words_from_middle_first(void **state);
+void ctrl_right_two_words_from_end_first(void **state);
+void ctrl_right_two_words_from_space(void **state);
+void ctrl_right_two_words_from_start_second(void **state);
+void ctrl_right_one_word_leading_whitespace(void **state);
+void ctrl_right_two_words_in_whitespace(void **state);
+void ctrl_right_trailing_whitespace_from_middle(void **state);
\ No newline at end of file
diff --git a/tests/test_muc.c b/tests/test_muc.c
index 5566c17e..e3b7f9b0 100644
--- a/tests/test_muc.c
+++ b/tests/test_muc.c
@@ -19,7 +19,7 @@ void muc_after_test(void **state)
 void test_muc_invites_add(void **state)
 {
     char *room = "room@conf.server";
-    muc_invites_add(room);
+    muc_invites_add(room, NULL);
 
     gboolean invite_exists = muc_invites_contain(room);
 
@@ -29,7 +29,7 @@ void test_muc_invites_add(void **state)
 void test_muc_remove_invite(void **state)
 {
     char *room = "room@conf.server";
-    muc_invites_add(room);
+    muc_invites_add(room, NULL);
     muc_invites_remove(room);
 
     gboolean invite_exists = muc_invites_contain(room);
@@ -46,11 +46,11 @@ void test_muc_invites_count_0(void **state)
 
 void test_muc_invites_count_5(void **state)
 {
-    muc_invites_add("room1@conf.server");
-    muc_invites_add("room2@conf.server");
-    muc_invites_add("room3@conf.server");
-    muc_invites_add("room4@conf.server");
-    muc_invites_add("room5@conf.server");
+    muc_invites_add("room1@conf.server", NULL);
+    muc_invites_add("room2@conf.server", NULL);
+    muc_invites_add("room3@conf.server", NULL);
+    muc_invites_add("room4@conf.server", NULL);
+    muc_invites_add("room5@conf.server", NULL);
 
     int invite_count = muc_invites_count();
 
diff --git a/tests/test_server_events.c b/tests/test_server_events.c
index ce54b4f2..58489807 100644
--- a/tests/test_server_events.c
+++ b/tests/test_server_events.c
@@ -6,7 +6,7 @@
 #include <string.h>
 #include <glib.h>
 
-#include "server_events.h"
+#include "event/server_events.h"
 #include "roster_list.h"
 #include "chat_session.h"
 #include "config/preferences.h"
@@ -14,31 +14,19 @@
 #include "ui/stub_ui.h"
 #include "muc.h"
 
-void console_doesnt_show_online_presence_when_set_none(void **state)
-{
-    prefs_set_string(PREF_STATUSES_CONSOLE, "none");
-    roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
-    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
-
-    handle_contact_online("test1@server", resource, NULL);
-
-    roster_clear();
-}
-
 void console_shows_online_presence_when_set_online(void **state)
 {
     prefs_set_string(PREF_STATUSES_CONSOLE, "online");
     roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
     Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
-    PContact contact = roster_get_contact("test1@server");
 
-    expect_memory(cons_show_contact_online, contact, contact, sizeof(contact));
-    expect_memory(cons_show_contact_online, resource, resource, sizeof(resource));
-    expect_value(cons_show_contact_online, last_activity, NULL);
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
 
-    handle_contact_online("test1@server", resource, NULL);
+    sv_ev_contact_online(barejid, resource, NULL);
 
     roster_clear();
 }
@@ -47,39 +35,15 @@ void console_shows_online_presence_when_set_all(void **state)
 {
     prefs_set_string(PREF_STATUSES_CONSOLE, "all");
     roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
     Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
-    PContact contact = roster_get_contact("test1@server");
-
-    expect_memory(cons_show_contact_online, contact, contact, sizeof(contact));
-    expect_memory(cons_show_contact_online, resource, resource, sizeof(resource));
-    expect_value(cons_show_contact_online, last_activity, NULL);
-
-    handle_contact_online("test1@server", resource, NULL);
-
-    roster_clear();
-}
-
-void console_doesnt_show_dnd_presence_when_set_none(void **state)
-{
-    prefs_set_string(PREF_STATUSES_CONSOLE, "none");
-    roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
-    Resource *resource = resource_new("resource", RESOURCE_DND, NULL, 10);
-
-    handle_contact_online("test1@server", resource, NULL);
-
-    roster_clear();
-}
 
-void console_doesnt_show_dnd_presence_when_set_online(void **state)
-{
-    prefs_set_string(PREF_STATUSES_CONSOLE, "online");
-    roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
-    Resource *resource = resource_new("resource", RESOURCE_DND, NULL, 10);
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
 
-    handle_contact_online("test1@server", resource, NULL);
+    sv_ev_contact_online(barejid, resource, NULL);
 
     roster_clear();
 }
@@ -88,95 +52,19 @@ void console_shows_dnd_presence_when_set_all(void **state)
 {
     prefs_set_string(PREF_STATUSES_CONSOLE, "all");
     roster_init();
-    roster_add("test1@server", "bob", NULL, "both", FALSE);
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
     Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
-    PContact contact = roster_get_contact("test1@server");
 
-    expect_memory(cons_show_contact_online, contact, contact, sizeof(contact));
-    expect_memory(cons_show_contact_online, resource, resource, sizeof(resource));
-    expect_value(cons_show_contact_online, last_activity, NULL);
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
 
-    handle_contact_online("test1@server", resource, NULL);
+    sv_ev_contact_online(barejid, resource, NULL);
 
     roster_clear();
 }
 
-void handle_message_error_when_no_recipient(void **state)
-{
-    char *err_msg = "Some error.";
-    char *from = NULL;
-    char *type = "cancel";
-
-    expect_string(ui_handle_error, err_msg, err_msg);
-
-    handle_message_error(from, type, err_msg);
-}
-
-void handle_message_error_when_recipient_cancel(void **state)
-{
-    char *err_msg = "Some error.";
-    char *from = "bob@server.com";
-    char *type = "cancel";
-
-    prefs_set_boolean(PREF_STATES, FALSE);
-    chat_sessions_init();
-
-    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 *barejid = "bob@server.com";
-    char *resource = "resource";
-    char *type = "cancel";
-
-    prefs_set_boolean(PREF_STATES, TRUE);
-    chat_sessions_init();
-    chat_session_recipient_active(barejid, resource, FALSE);
-
-    handle_message_error(barejid, type, err_msg);
-    ChatSession *session = chat_session_get(barejid);
-
-    assert_null(session);
-    chat_sessions_clear();
-}
-
-void handle_message_error_when_recipient_and_no_type(void **state)
-{
-    char *err_msg = "Some error.";
-    char *from = "bob@server.com";
-    char *type = NULL;
-
-    expect_string(ui_handle_recipient_error, recipient, from);
-    expect_string(ui_handle_recipient_error, err_msg, err_msg);
-
-    handle_message_error(from, type, err_msg);
-}
-
-void handle_presence_error_when_no_recipient(void **state)
-{
-    char *err_msg = "Some error.";
-    char *from = NULL;
-    char *type = NULL;
-
-    expect_string(ui_handle_error, err_msg, err_msg);
-
-    handle_presence_error(from, type, err_msg);
-}
-
-void handle_presence_error_when_from_recipient(void **state)
-{
-    char *err_msg = "Some error.";
-    char *from = "bob@server.com";
-    char *type = NULL;
-
-    expect_string(ui_handle_recipient_error, recipient, from);
-    expect_string(ui_handle_recipient_error, err_msg, err_msg);
-
-    handle_presence_error(from, type, err_msg);
-}
-
 void handle_offline_removes_chat_session(void **state)
 {
     chat_sessions_init();
@@ -187,7 +75,7 @@ void handle_offline_removes_chat_session(void **state)
     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);
+    sv_ev_contact_offline(barejid, resource, NULL);
     ChatSession *session = chat_session_get(barejid);
 
     assert_null(session);
@@ -203,7 +91,7 @@ void lost_connection_clears_chat_sessions(void **state)
     chat_session_recipient_active("steve@server.org", "mobile", FALSE);
     expect_any_cons_show_error();
 
-    handle_lost_connection();
+    sv_ev_lost_connection();
 
     ChatSession *session1 = chat_session_get("bob@server.org");
     ChatSession *session2 = chat_session_get("steve@server.org");
diff --git a/tests/testsuite.c b/tests/testsuite.c
index cf511c59..3f860178 100644
--- a/tests/testsuite.c
+++ b/tests/testsuite.c
@@ -21,7 +21,6 @@
 #include "test_cmd_sub.h"
 #include "test_cmd_statuses.h"
 #include "test_cmd_otr.h"
-#include "test_history.h"
 #include "test_jid.h"
 #include "test_parser.h"
 #include "test_roster_list.h"
@@ -32,7 +31,6 @@
 #include "test_cmd_join.h"
 #include "test_muc.h"
 #include "test_cmd_roster.h"
-#include "test_cmd_win.h"
 #include "test_cmd_disconnect.h"
 #include "test_form.h"
 
@@ -91,6 +89,10 @@ int main(int argc, char* argv[]) {
         unit_test(utf8_display_len_non_wide),
         unit_test(utf8_display_len_wide),
         unit_test(utf8_display_len_all_wide),
+        unit_test(strip_quotes_does_nothing_when_no_quoted),
+        unit_test(strip_quotes_strips_first),
+        unit_test(strip_quotes_strips_last),
+        unit_test(strip_quotes_strips_both),
 
         unit_test(clear_empty),
         unit_test(reset_after_create),
@@ -103,20 +105,6 @@ int main(int argc, char* argv[]) {
         unit_test(add_two_same_adds_one),
         unit_test(add_two_same_updates),
 
-        unit_test(previous_on_empty_returns_null),
-        unit_test(next_on_empty_returns_null),
-        unit_test(previous_once_returns_last),
-        unit_test(previous_twice_when_one_returns_first),
-        unit_test(previous_always_stops_at_first),
-        unit_test(previous_goes_to_correct_element),
-        unit_test(prev_then_next_returns_empty),
-        unit_test(prev_with_val_then_next_returns_val),
-        unit_test(prev_with_val_then_next_twice_returns_null),
-        unit_test(navigate_then_append_new),
-        unit_test(edit_item_mid_history),
-        unit_test(edit_previous_and_append),
-        unit_test(start_session_add_new_submit_previous),
-
         unit_test(create_jid_from_null_returns_null),
         unit_test(create_jid_from_empty_string_returns_null),
         unit_test(create_jid_from_full_returns_full),
@@ -342,9 +330,11 @@ int main(int argc, char* argv[]) {
         unit_test(cmd_account_set_eval_password_when_password_set),
         unit_test(cmd_account_set_muc_sets_muc),
         unit_test(cmd_account_set_nick_sets_nick),
+#ifdef HAVE_LIBOTR
         unit_test(cmd_account_show_message_for_missing_otr_policy),
         unit_test(cmd_account_show_message_for_invalid_otr_policy),
         unit_test(cmd_account_set_otr_sets_otr),
+#endif
         unit_test(cmd_account_set_status_shows_message_when_invalid_status),
         unit_test(cmd_account_set_status_sets_status_when_valid),
         unit_test(cmd_account_set_status_sets_status_when_last),
@@ -435,34 +425,15 @@ int main(int argc, char* argv[]) {
             load_preferences,
             close_preferences),
 
-        unit_test_setup_teardown(console_doesnt_show_online_presence_when_set_none,
-            load_preferences,
-            close_preferences),
         unit_test_setup_teardown(console_shows_online_presence_when_set_online,
             load_preferences,
             close_preferences),
         unit_test_setup_teardown(console_shows_online_presence_when_set_all,
             load_preferences,
             close_preferences),
-        unit_test_setup_teardown(console_doesnt_show_dnd_presence_when_set_none,
-            load_preferences,
-            close_preferences),
-        unit_test_setup_teardown(console_doesnt_show_dnd_presence_when_set_online,
-            load_preferences,
-            close_preferences),
         unit_test_setup_teardown(console_shows_dnd_presence_when_set_all,
             load_preferences,
             close_preferences),
-        unit_test(handle_message_error_when_no_recipient),
-        unit_test_setup_teardown(handle_message_error_when_recipient_cancel,
-            load_preferences,
-            close_preferences),
-        unit_test_setup_teardown(handle_message_error_when_recipient_cancel_disables_chat_session,
-            load_preferences,
-            close_preferences),
-        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),
 
@@ -572,7 +543,6 @@ int main(int argc, char* argv[]) {
         unit_test(cmd_join_shows_message_when_connecting),
         unit_test(cmd_join_shows_message_when_disconnected),
         unit_test(cmd_join_shows_message_when_undefined),
-        unit_test(cmd_join_shows_usage_when_no_args),
         unit_test(cmd_join_shows_error_message_when_invalid_room_jid),
         unit_test(cmd_join_uses_account_mucservice_when_no_service_specified),
         unit_test(cmd_join_uses_supplied_nick),
@@ -596,9 +566,6 @@ int main(int argc, char* argv[]) {
         unit_test(cmd_roster_clearnick_shows_message_when_no_contact_exists),
         unit_test(cmd_roster_clearnick_sends_name_change_request_with_empty_nick),
 
-        unit_test(cmd_win_shows_message_when_win_doesnt_exist),
-        unit_test(cmd_win_switches_to_given_win_when_exists),
-
         unit_test(get_form_type_field_returns_null_no_fields),
         unit_test(get_form_type_field_returns_null_when_not_present),
         unit_test(get_form_type_field_returns_value_when_present),
diff --git a/tests/ui/stub_ui.c b/tests/ui/stub_ui.c
index 51b82d42..52c38570 100644
--- a/tests/ui/stub_ui.c
+++ b/tests/ui/stub_ui.c
@@ -64,15 +64,7 @@ GSList* ui_get_chat_recipients(void)
     return NULL;
 }
 
-gboolean ui_switch_win(const int i)
-{
-    check_expected(i);
-    return (gboolean)mock();
-    return FALSE;
-}
-
-void ui_next_win(void) {}
-void ui_previous_win(void) {}
+void ui_switch_win(ProfWin *win) {}
 
 void ui_gone_secure(const char * const barejid, gboolean trusted) {}
 void ui_gone_insecure(const char * const barejid) {}
@@ -91,6 +83,7 @@ void ui_smp_answer_failure(const char * const barejid) {}
 
 void ui_otr_authenticating(const char * const barejid) {}
 void ui_otr_authetication_waiting(const char * const recipient) {}
+void ui_sigwinch_handler(int sig) {}
 
 unsigned long ui_get_idle_time(void)
 {
@@ -98,8 +91,16 @@ unsigned long ui_get_idle_time(void)
 }
 
 void ui_reset_idle_time(void) {}
-void ui_new_chat_win(const char * const barejid) {}
-void ui_new_private_win(const char * const fulljid) {}
+ProfPrivateWin* ui_new_private_win(const char * const fulljid)
+{
+    return NULL;
+}
+
+ProfChatWin* ui_new_chat_win(const char * const barejid)
+{
+    return NULL;
+}
+
 void ui_print_system_msg_from_recipient(const char * const barejid, const char *message) {}
 gint ui_unread(void)
 {
@@ -125,11 +126,6 @@ win_type_t ui_current_win_type(void)
     return (win_type_t)mock();
 }
 
-int ui_current_win_index(void)
-{
-    return 0;
-}
-
 gboolean ui_current_win_is_otr(void)
 {
     return (gboolean)mock();
@@ -161,6 +157,8 @@ void ui_current_print_formatted_line(const char show_char, int attrs, const char
 }
 
 void ui_current_error_line(const char * const msg) {}
+void ui_win_error_line(ProfWin *window, const char * const msg) {}
+
 
 win_type_t ui_win_type(int index)
 {
@@ -168,16 +166,17 @@ win_type_t ui_win_type(int index)
 }
 
 void ui_close_win(int index) {}
-gboolean ui_win_exists(int index)
-{
-    return FALSE;
-}
 
 int ui_win_unread(int index)
 {
     return 0;
 }
 
+void ui_page_up(void) {}
+void ui_page_down(void) {}
+void ui_subwin_page_up(void) {}
+void ui_subwin_page_down(void) {}
+
 char * ui_ask_password(void)
 {
     return mock_ptr_type(char *);
@@ -186,17 +185,25 @@ char * ui_ask_password(void)
 void ui_handle_stanza(const char * const msg) {}
 
 // ui events
+void ui_contact_online(char *barejid, Resource *resource, GDateTime *last_activity)
+{
+    check_expected(barejid);
+    check_expected(resource);
+    check_expected(last_activity);
+}
+
 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_message_receipt(const char * const barejid, const char * const id) {}
+
 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, const char * const resource) {}
 
-void ui_outgoing_chat_msg(const char * const from, const char * const barejid,
-    const char * const message) {}
-void ui_outgoing_private_msg(const char * const from, const char * const fulljid,
-    const char * const message) {}
+void ui_outgoing_chat_msg(ProfChatWin *chatwin, const char * const message, char *id) {}
+void ui_outgoing_chat_msg_carbon(const char * const barejid, const char * const message) {}
+void ui_outgoing_private_msg(ProfPrivateWin *privwin, const char * const message) {}
 
 void ui_room_join(const char * const roomjid, gboolean focus) {}
 void ui_switch_to_room(const char * const roomjid) {}
@@ -245,6 +252,7 @@ void ui_room_member_nick_change(const char * const roomjid,
 void ui_room_nick_change(const char * const roomjid, const char * const nick) {}
 void ui_room_member_presence(const char * const roomjid,
     const char * const nick, const char * const show, const char * const status) {}
+void ui_room_update_occupants(const char * const roomjid) {}
 void ui_room_show_occupants(const char * const roomjid) {}
 void ui_room_hide_occupants(const char * const roomjid) {}
 void ui_show_roster(void) {}
@@ -323,7 +331,7 @@ void ui_update_presence(const resource_presence_t resource_presence,
 void ui_about(void) {}
 void ui_statusbar_new(const int win) {}
 
-char * ui_readline(void)
+char*  ui_readline(void)
 {
     return NULL;
 }
@@ -348,6 +356,9 @@ gboolean ui_win_has_unsaved_form(int num)
     return FALSE;
 }
 
+void
+ui_write(char *line, int offset) {}
+
 // console window actions
 
 void cons_show(const char * const msg, ...)
@@ -457,6 +468,8 @@ void cons_outtype_setting(void) {}
 void cons_intype_setting(void) {}
 void cons_gone_setting(void) {}
 void cons_history_setting(void) {}
+void cons_carbons_setting(void) {}
+void cons_receipts_setting(void) {}
 void cons_log_setting(void) {}
 void cons_chlog_setting(void) {}
 void cons_grlog_setting(void) {}
@@ -487,7 +500,7 @@ void occupantswin_occupants(const char * const room) {}
 void notifier_uninit(void) {}
 
 void notify_typing(const char * const handle) {}
-void notify_message(const char * const handle, int win, const char * const text) {}
+void notify_message(ProfWin *window, const char * const name, const char * const text) {}
 void notify_room_message(const char * const handle, const char * const room,
     int win, const char * const text) {}
 void notify_remind(void) {}
diff --git a/tests/xmpp/stub_xmpp.c b/tests/xmpp/stub_xmpp.c
index 281857f0..cc9580bf 100644
--- a/tests/xmpp/stub_xmpp.c
+++ b/tests/xmpp/stub_xmpp.c
@@ -58,10 +58,18 @@ GList * jabber_get_available_resources(void)
 }
 
 // message functions
-void message_send_chat(const char * const barejid, const char * const msg)
+char* message_send_chat(const char * const barejid, const char * const msg)
 {
     check_expected(barejid);
     check_expected(msg);
+    return NULL;
+}
+
+char* message_send_chat_encrypted(const char * const barejid, const char * const msg)
+{
+    check_expected(barejid);
+    check_expected(msg);
+    return NULL;
 }
 
 void message_send_private(const char * const fulljid, const char * const msg) {}
@@ -106,7 +114,7 @@ void presence_join_room(char *room, char *nick, char * passwd)
 void presence_change_room_nick(const char * const room, const char * const nick) {}
 void presence_leave_chat_room(const char * const room_jid) {}
 
-void presence_update(resource_presence_t status, const char * const msg, int idle)
+void presence_send(resource_presence_t status, const char * const msg, int idle)
 {
     check_expected(status);
     check_expected(msg);
@@ -119,6 +127,8 @@ gboolean presence_sub_request_exists(const char * const bare_jid)
 }
 
 // iq functions
+void iq_disable_carbons() {};
+void iq_enable_carbons() {};
 void iq_send_software_version(const char * const fulljid) {}
 
 void iq_room_list_request(gchar *conferencejid)
@@ -141,7 +151,7 @@ void iq_send_caps_request_for_jid(const char * const to, const char * const id,
     const char * const node, const char * const ver) {}
 void iq_send_caps_request_legacy(const char * const to, const char * const id,
     const char * const node, const char * const ver) {}
-void iq_room_info_request(gchar *room) {}
+void iq_room_info_request(const char * const room, gboolean display) {}
 void iq_room_affiliation_list(const char * const room, char *affiliation) {}
 void iq_room_affiliation_set(const char * const room, const char * const jid, char *affiliation,
     const char * const reason) {}
diff --git a/theme_template b/theme_template
new file mode 100644
index 00000000..c088e181
--- /dev/null
+++ b/theme_template
@@ -0,0 +1,74 @@
+[colours]
+bkgnd=
+titlebar=
+titlebar.text=
+titlebar.brackets=
+titlebar.unencrypted=
+titlebar.encrypted=
+titlebar.untrusted=
+titlebar.trusted=
+titlebar.online=
+titlebar.offline=
+titlebar.away=
+titlebar.chat=
+titlebar.dnd=
+titlebar.xa=
+statusbar=
+statusbar.text=
+statusbar.brackets=
+statusbar.active=
+statusbar.new=
+main.text=
+main.text.me=
+main.text.them=
+main.splash=
+main.time=
+input.text=
+subscribed=
+unsubscribed=
+otr.started.trusted=
+otr.started.untrusted=
+otr.ended=
+otr.trusted=
+otr.untrusted=
+online=
+away=
+chat=
+dnd=
+xa=
+offline=
+incoming=
+typing=
+gone=
+error=
+roominfo=
+roommention=
+me=
+them=
+roster.header=
+occupants.header=
+receipt.sent=
+
+[ui]
+beep=
+flash=
+splash=
+wrap=
+time=
+privileges=
+presence=
+intype=
+otr.warn=
+resource.title=
+resource.message=
+statuses.console=
+statuses.chat=
+statuses.muc=
+roster=
+roster.offline=
+roster.resource=
+roster.by=
+roster.size=
+occupants=
+occupants.size=
+occupants.jid=
diff --git a/themes/advanced b/themes/advanced
deleted file mode 100644
index a00948d8..00000000
--- a/themes/advanced
+++ /dev/null
@@ -1,69 +0,0 @@
-[colours]
-bkgnd=default
-titlebar=blue
-statusbar=blue
-titlebar.text=bold_white
-titlebar.brackets=white
-statusbar.text=bold_white
-statusbar.brackets=white
-statusbar.active=bold_cyan
-statusbar.new=bold_green
-main.text=white
-main.text.me=cyan
-main.text.them=bold_white
-input.text=bold_green
-main.time=yellow
-main.splash=bold_red
-online=bold_green
-away=bold_cyan
-chat=bold_white
-dnd=magenta
-xa=bold_blue
-offline=red
-typing=yellow
-gone=red
-error=red
-incoming=bold_yellow
-roominfo=yellow
-roommention=bold_red
-me=blue
-them=bold_green
-titlebar.unencrypted=bold_red
-titlebar.encrypted=bold_white
-titlebar.untrusted=bold_yellow
-titlebar.trusted=bold_white
-titlebar.online=bold_green
-titlebar.offline=bold_red
-titlebar.away=bold_cyan
-titlebar.xa=bold_cyan
-titlebar.dnd=bold_red
-titlebar.chat=bold_green
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
-roster.header=bold_yellow
-occupants.header=bold_yellow
-
-[ui]
-intype=true
-beep=false
-flash=false
-privileges=true
-presence=true
-wrap=true
-time=minutes
-statuses.muc=off
-statuses.chat=online
-statuses.console=off
-occupants=true
-occupants.size=15
-roster=true
-roster.size=25
-roster.offline=true
-roster.resource=true
-roster.by=presence
-otr.warn=true
-
-
diff --git a/themes/aqua b/themes/aqua
index 7474d072..11c250f4 100644
--- a/themes/aqua
+++ b/themes/aqua
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=blue
-statusbar=blue
 titlebar.text=bold_white
 titlebar.brackets=white
+titlebar.unencrypted=cyan
+titlebar.encrypted=white
+titlebar.untrusted=cyan
+titlebar.trusted=white
+titlebar.online=bold_white
+titlebar.offline=cyan
+titlebar.away=white
+titlebar.chat=bold_white
+titlebar.dnd=cyan
+titlebar.xa=bold_cyan
+statusbar=blue
 statusbar.text=bold_white
 statusbar.brackets=white
 statusbar.active=cyan
@@ -11,31 +21,30 @@ statusbar.new=white
 main.text=blue
 main.text.me=bold_cyan
 main.text.them=bold_white
-input.text=white
-main.time=cyan
 main.splash=bold_white
+main.time=cyan
+input.text=white
+subscribed=bold_cyan
+unsubscribed=blue
+otr.started.trusted=white
+otr.started.untrusted=cyan
+otr.ended=cyan
+otr.trusted=white
+otr.untrusted=cyan
 online=bold_cyan
 away=bold_blue
 chat=white
 dnd=blue
 xa=cyan
 offline=bold_black
+incoming=bold_cyan
 typing=cyan
 gone=blue
 error=bold_white
-incoming=bold_cyan
 roominfo=white
 roommention=bold_blue
 me=cyan
 them=white
-titlebar.unencrypted=cyan
-titlebar.encrypted=white
-titlebar.untrusted=cyan
-titlebar.trusted=white
-otr.started.trusted=white
-otr.started.untrusted=cyan
-otr.ended=cyan
-otr.trusted=white
-otr.untrusted=cyan
 roster.header=bold_white
 occupants.header=bold_white
+receipt.sent=white
diff --git a/themes/batman b/themes/batman
index fc8ce1be..4d346ac1 100644
--- a/themes/batman
+++ b/themes/batman
@@ -17,14 +17,13 @@ away=yellow
 chat=green
 dnd=green
 xa=yellow
-offline=grey
+offline=bold_black
 typing=cyan
 gone=red
 error=red
 incoming=yellow
 roominfo=green
-roommention=cyan
-me=black
+me=black_bold
 them=yellow
 titlebar.unencrypted=red
 titlebar.encrypted=green
@@ -35,3 +34,17 @@ otr.started.untrusted=red
 otr.ended=yellow
 otr.trusted=green
 otr.untrusted=red
+titlebar.online=magenta
+titlebar.offline=black
+titlebar.away=magenta
+titlebar.chat=magenta
+titlebar.dnd=magenta
+titlebar.xa=magenta
+main.text.me=white
+main.text.them=white
+subscribed=magenta
+unsubscribed=black_bold
+roommention=cyan
+roster.header=yellow
+occupants.header=yellow
+receipt.sent=red
diff --git a/themes/boothj5 b/themes/boothj5
index 3e086903..9ed3fe69 100644
--- a/themes/boothj5
+++ b/themes/boothj5
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=blue
-statusbar=blue
 titlebar.text=bold_white
 titlebar.brackets=white
+titlebar.unencrypted=bold_red
+titlebar.encrypted=bold_white
+titlebar.untrusted=bold_yellow
+titlebar.trusted=bold_white
+titlebar.online=bold_green
+titlebar.offline=bold_red
+titlebar.away=bold_cyan
+titlebar.xa=bold_cyan
+titlebar.dnd=bold_red
+titlebar.chat=bold_green
+statusbar=blue
 statusbar.text=bold_white
 statusbar.brackets=white
 statusbar.active=bold_cyan
@@ -11,59 +21,55 @@ statusbar.new=bold_green
 main.text=white
 main.text.me=cyan
 main.text.them=bold_white
-input.text=bold_green
-main.time=yellow
 main.splash=bold_red
+main.time=yellow
+input.text=bold_green
+subscribed=bold_green
+unsubscribed=red
+otr.started.trusted=green
+otr.started.untrusted=yellow
+otr.ended=red
+otr.trusted=green
+otr.untrusted=yellow
 online=bold_green
 away=bold_cyan
 chat=bold_white
 dnd=magenta
 xa=bold_blue
 offline=red
+incoming=bold_yellow
 typing=yellow
 gone=red
 error=red
-incoming=bold_yellow
 roominfo=yellow
 roommention=bold_red
 me=blue
 them=bold_green
-titlebar.unencrypted=bold_red
-titlebar.encrypted=bold_white
-titlebar.untrusted=bold_yellow
-titlebar.trusted=bold_white
-titlebar.online=bold_green
-titlebar.offline=bold_red
-titlebar.away=bold_cyan
-titlebar.xa=bold_cyan
-titlebar.dnd=bold_red
-titlebar.chat=bold_green
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
 roster.header=bold_yellow
 occupants.header=bold_yellow
+receipt.sent=bold_black
 
 [ui]
-intype=true
 beep=false
 flash=false
+splash=true
+wrap=true
+time=seconds
+time.statusbar=seconds
 privileges=true
 presence=true
-wrap=true
-time=minutes
-statuses.muc=none
-statuses.chat=none
-statuses.console=none
-occupants=true
-occupants.size=15
+intype=true
+otr.warn=true
+resource.title=true
+resource.message=true
+statuses.console=all
+statuses.chat=all
+statuses.muc=all
 roster=true
-roster.size=25
 roster.offline=true
 roster.resource=true
 roster.by=presence
-otr.warn=true
-
-
+roster.size=25
+occupants=true
+occupants.size=15
+occupants.jid=true
diff --git a/themes/complex b/themes/complex
new file mode 100644
index 00000000..a5510baa
--- /dev/null
+++ b/themes/complex
@@ -0,0 +1,24 @@
+[ui]
+beep=false
+flash=false
+splash=true
+wrap=true
+time=seconds
+time.statusbar=seconds
+resource.title=true
+resource.message=true
+statuses.console=all
+statuses.chat=all
+statuses.muc=all
+occupants=true
+occupants.size=15
+occupants.jid=true
+roster=true
+roster.offline=true
+roster.resource=true
+roster.by=presence
+roster.size=25
+privileges=true
+presence=true
+intype=true
+otr.warn=true
diff --git a/themes/forest b/themes/forest
index fd170162..be3f3a82 100644
--- a/themes/forest
+++ b/themes/forest
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=cyan
-statusbar=green
 titlebar.text=black
 titlebar.brackets=black
+titlebar.unencrypted=black
+titlebar.encrypted=white
+titlebar.untrusted=white
+titlebar.trusted=white
+titlebar.online=white
+titlebar.offline=white
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=white
+titlebar.xa=white
+statusbar=green
 statusbar.text=black
 statusbar.brackets=black
 statusbar.active=bold_green
@@ -11,39 +21,30 @@ statusbar.new=white
 main.text=bold_cyan
 main.text.me=yellow
 main.text.them=green
-input.text=bold_blue
-main.time=bold_green
 main.splash=bold_yellow
+main.time=bold_green
+input.text=bold_blue
 subscribed=green
 unsubscribed=bold_black
+otr.started.trusted=green
+otr.started.untrusted=yellow
+otr.ended=red
+otr.trusted=green
+otr.untrusted=yellow
 online=green
 away=blue
 chat=green
 dnd=bold_black
 xa=blue
 offline=bold_black
+incoming=bold_yellow
 typing=yellow
 gone=bold_black
 error=bold_black
-incoming=bold_yellow
 roominfo=yellow
 roommention=bold_cyan
 me=blue
 them=bold_blue
-titlebar.unencrypted=black
-titlebar.encrypted=white
-titlebar.untrusted=white
-titlebar.trusted=white
-titlebar.online=white
-titlebar.offline=white
-titlebar.away=white
-titlebar.xa=white
-titlebar.dnd=white
-titlebar.chat=white
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
 roster.header=bold_green
 occupants.header=bold_green
+receipt.sent=bold_black
\ No newline at end of file
diff --git a/themes/hacker b/themes/hacker
index 09928f61..6486126f 100644
--- a/themes/hacker
+++ b/themes/hacker
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=green
-statusbar=green
 titlebar.text=black
 titlebar.brackets=black
+titlebar.unencrypted=black
+titlebar.encrypted=black
+titlebar.untrusted=black
+titlebar.trusted=black
+titlebar.online=black
+titlebar.offline=black
+titlebar.away=black
+titlebar.chat=black
+titlebar.dnd=black
+titlebar.xa=black
+statusbar=green
 statusbar.text=black
 statusbar.brackets=black
 statusbar.active=black
@@ -11,37 +21,30 @@ statusbar.new=black
 main.text=green
 main.text.me=green
 main.text.them=green
-input.text=bold_green
-main.time=bold_green
 main.splash=bold_green
+main.time=bold_green
+input.text=bold_green
+subscribed=bold_green
+unsubscribed=green
+otr.started.trusted=green
+otr.started.untrusted=green
+otr.ended=green
+otr.trusted=green
+otr.untrusted=green
 online=bold_green
 away=green
 chat=bold_green
 dnd=green
 xa=green
 offline=green
+incoming=bold_green
 typing=green
 gone=green
 error=bold_green
-incoming=bold_green
 roominfo=green
 roommention=bold_green
 me=green
 them=bold_green
-titlebar.unencrypted=black
-titlebar.encrypted=black
-titlebar.untrusted=black
-titlebar.trusted=black
-titlebar.online=black
-titlebar.offline=black
-titlebar.away=black
-titlebar.xa=black
-titlebar.dnd=black
-titlebar.chat=black
-otr.started.trusted=green
-otr.started.untrusted=green
-otr.ended=green
-otr.trusted=green
-otr.untrusted=green
 roster.header=bold_green
 occupants.header=bold_green
+receipt.sent=bold_black
\ No newline at end of file
diff --git a/themes/headache b/themes/headache
index ea5738ce..5c06ca78 100644
--- a/themes/headache
+++ b/themes/headache
@@ -1,36 +1,50 @@
 [colours]
 bkgnd=default
 titlebar=magenta
-statusbar=default
 titlebar.text=white
 titlebar.brackets=white
+titlebar.unencrypted=black
+titlebar.encrypted=cyan
+titlebar.untrusted=yellow
+titlebar.trusted=cyan
+titlebar.online=white
+titlebar.offline=black
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=black
+titlebar.xa=white
+statusbar=default
 statusbar.text=white
 statusbar.brackets=red
 statusbar.active=cyan
 statusbar.new=white
 main.text=blue
-input.text=yellow
-main.time=green
+main.text.me=white
+main.text.them=bold_white
 main.splash=red
+main.time=green
+input.text=yellow
+subscribed=bold_magenta
+unsubscribed=bold_black
+otr.started.trusted=cyan
+otr.started.untrusted=yellow
+otr.ended=blue
+otr.trusted=cyan
+otr.untrusted=yellow
 online=red
 away=cyan
 chat=green
 dnd=megenta
 xa=cyan
 offline=green
+incoming=yellow
 typing=magenta
 gone=yellow
 error=red
-incoming=yellow
 roominfo=white
+roommention=bold_green
 me=white
 them=white
-titlebar.unencrypted=black
-titlebar.encrypted=cyan
-titlebar.untrusted=yellow
-titlebar.trusted=cyan
-otr.started.trusted=cyan
-otr.started.untrusted=yellow
-otr.ended=blue
-otr.trusted=cyan
-otr.untrusted=yellow
+roster.header=bold_cyan
+occupants.header=bold_cyan
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/joker b/themes/joker
index 3c8adc39..59e9ed94 100644
--- a/themes/joker
+++ b/themes/joker
@@ -1,36 +1,50 @@
 [colours]
 bkgnd=default
 titlebar=magenta
-statusbar=magenta
 titlebar.text=white
 titlebar.brackets=cyan
+titlebar.unencrypted=red
+titlebar.encrypted=green
+titlebar.untrusted=red
+titlebar.trusted=green
+titlebar.online=white
+titlebar.offline=black
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=black
+titlebar.xa=white
+statusbar=magenta
 statusbar.text=green
 statusbar.brackets=cyan
 statusbar.active=green
 statusbar.new=white
 main.text=white
-input.text=green
-main.time=white
+main.text.me=white
+main.text.them=white
 main.splash=green
+main.time=white
+input.text=green
+subscribed=green
+unsubscribed=white
+otr.started.trusted=green
+otr.started.untrusted=red
+otr.ended=yellow
+otr.trusted=green
+otr.untrusted=red
 online=green
 away=yellow
 chat=green
 dnd=green
 xa=yellow
-offline=grey
+offline=bold_black
+incoming=yellow
 typing=green
 gone=red
 error=red
-incoming=yellow
 roominfo=green
+roommention=green
 me=magenta
 them=green
-titlebar.unencrypted=red
-titlebar.encrypted=green
-titlebar.untrusted=red
-titlebar.trusted=green
-otr.started.trusted=green
-otr.started.untrusted=red
-otr.ended=yellow
-otr.trusted=green
-otr.untrusted=red
+roster.header=magenta
+occupants.header=magenta
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/minimal b/themes/minimal
deleted file mode 100644
index cabf1887..00000000
--- a/themes/minimal
+++ /dev/null
@@ -1,62 +0,0 @@
-[colours]
-bkgnd=default
-titlebar=blue
-statusbar=blue
-titlebar.text=bold_white
-titlebar.brackets=white
-statusbar.text=bold_white
-statusbar.brackets=white
-statusbar.active=bold_cyan
-statusbar.new=bold_green
-main.text=white
-main.text.me=cyan
-main.text.them=bold_white
-input.text=bold_green
-main.time=yellow
-main.splash=bold_red
-online=bold_green
-away=bold_cyan
-chat=bold_white
-dnd=magenta
-xa=bold_blue
-offline=red
-typing=yellow
-gone=red
-error=red
-incoming=bold_yellow
-roominfo=yellow
-roommention=bold_red
-me=blue
-them=bold_green
-titlebar.unencrypted=bold_red
-titlebar.encrypted=bold_white
-titlebar.untrusted=bold_yellow
-titlebar.trusted=bold_white
-titlebar.online=bold_green
-titlebar.offline=bold_red
-titlebar.away=bold_cyan
-titlebar.xa=bold_cyan
-titlebar.dnd=bold_red
-titlebar.chat=bold_green
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
-roster.header=bold_yellow
-occupants.header=bold_yellow
-
-[ui]
-intype=true
-beep=false
-flash=false
-privileges=false
-presence=false
-wrap=false
-time=off
-statuses.muc=all
-statuses.chat=all
-statuses.console=all
-occupants=false
-roster=false
-otr.warn=false
diff --git a/themes/mono b/themes/mono
index 42beb174..8efd7369 100644
--- a/themes/mono
+++ b/themes/mono
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=white
-statusbar=white
 titlebar.text=black
 titlebar.brackets=black
+titlebar.unencrypted=black
+titlebar.encrypted=black
+titlebar.untrusted=black
+titlebar.trusted=black
+titlebar.online=black
+titlebar.offline=black
+titlebar.away=black
+titlebar.chat=black
+titlebar.dnd=black
+titlebar.xa=black
+statusbar=white
 statusbar.text=black
 statusbar.brackets=black
 statusbar.active=black
@@ -11,37 +21,30 @@ statusbar.new=black
 main.text=white
 main.text.me=white
 main.text.them=white
-input.text=white
-main.time=white
 main.splash=white
+main.time=white
+input.text=white
+subscribed=white
+unsubscribed=white
+otr.started.trusted=white
+otr.started.untrusted=white
+otr.ended=white
+otr.trusted=white
+otr.untrusted=white
 online=white
 away=white
 chat=white
 dnd=white
 xa=white
 offline=white
+incoming=white
 typing=white
 gone=white
 error=white
-incoming=white
 roominfo=white
 roommention=white
 me=white
 them=white
-titlebar.unencrypted=black
-titlebar.encrypted=black
-titlebar.untrusted=black
-titlebar.trusted=black
-titlebar.online=black
-titlebar.offline=black
-titlebar.away=black
-titlebar.xa=black
-titlebar.dnd=black
-titlebar.chat=black
-otr.started.trusted=white
-otr.started.untrusted=white
-otr.ended=white
-otr.trusted=white
-otr.untrusted=white
 roster.header=white
 occupants.header=white
+receipt.sent=white
\ No newline at end of file
diff --git a/themes/orange b/themes/orange
index cff5f58b..5fc6ddf2 100644
--- a/themes/orange
+++ b/themes/orange
@@ -3,6 +3,16 @@ bkgnd=yellow
 titlebar=red
 titlebar.text=black
 titlebar.brackets=blue
+titlebar.unencrypted=black
+titlebar.encrypted=white
+titlebar.untrusted=white
+titlebar.trusted=green
+titlebar.online=white
+titlebar.offline=black
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=black
+titlebar.xa=white
 statusbar=green
 statusbar.text=black
 statusbar.brackets=blue
@@ -11,32 +21,30 @@ statusbar.new=white
 main.text=black
 main.text.me=black
 main.text.them=green
-main.time=blue
 main.splash=blue
+main.time=blue
 input.text=black
 subscribed=blue
-unsubscribed=red
+unsubscribed=white
+otr.started.trusted=green
+otr.started.untrusted=white
+otr.ended=red
+otr.trusted=green
+otr.untrusted=white
 online=blue
 away=white
 chat=blue
 dnd=white
 xa=white
 offline=white
+incoming=blue
 typing=black
 gone=green
 error=red
-incoming=blue
 roominfo=blue
-me=blue
-them=green
-titlebar.unencrypted=black
-titlebar.encrypted=white
-titlebar.untrusted=white
-titlebar.trusted=green
-otr.started.trusted=green
-otr.started.untrusted=white
-otr.ended=red
-otr.trusted=green
-otr.untrusted=white
+roommention=blue
+me=black
+them=black
 roster.header=black
 occupants.header=black
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/original b/themes/original
index db0818c5..d1c0b26a 100644
--- a/themes/original
+++ b/themes/original
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=blue
-statusbar=blue
 titlebar.text=white
 titlebar.brackets=cyan
+titlebar.unencrypted=red
+titlebar.encrypted=white
+titlebar.untrusted=yellow
+titlebar.trusted=white
+titlebar.online=white
+titlebar.offline=white
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=white
+titlebar.xa=white
+statusbar=blue
 statusbar.text=white
 statusbar.brackets=cyan
 statusbar.active=cyan
@@ -11,39 +21,30 @@ statusbar.new=white
 main.text=white
 main.text.me=white
 main.text.them=white
-input.text=white
-main.time=white
 main.splash=cyan
+main.time=white
+input.text=white
 subscribed=green
 unsubscribed=red
+otr.started.trusted=green
+otr.started.untrusted=yellow
+otr.ended=red
+otr.trusted=green
+otr.untrusted=yellow
 online=green
 away=cyan
 chat=green
 dnd=red
 xa=cyan
 offline=red
+incoming=yellow
 typing=yellow
-gone=red
+gone=yellow
 error=red
-incoming=yellow
 roominfo=yellow
 roommention=yellow
 me=yellow
 them=green
-titlebar.unencrypted=red
-titlebar.encrypted=white
-titlebar.untrusted=yellow
-titlebar.trusted=white
-titlebar.online=white
-titlebar.offline=white
-titlebar.away=white
-titlebar.xa=white
-titlebar.dnd=white
-titlebar.chat=white
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
 roster.header=yellow
 occupants.header=yellow
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/original_bright b/themes/original_bright
index ffe0c12c..c584ff60 100644
--- a/themes/original_bright
+++ b/themes/original_bright
@@ -1,9 +1,19 @@
 [colours]
 bkgnd=default
 titlebar=blue
-statusbar=blue
 titlebar.text=bold_white
 titlebar.brackets=bold_cyan
+titlebar.unencrypted=bold_red
+titlebar.encrypted=bold_white
+titlebar.untrusted=bold_yellow
+titlebar.trusted=bold_white
+titlebar.online=bold_white
+titlebar.offline=bold_white
+titlebar.away=bold_white
+titlebar.chat=bold_white
+titlebar.dnd=bold_white
+titlebar.xa=bold_white
+statusbar=blue
 statusbar.text=bold_white
 statusbar.brackets=bold_cyan
 statusbar.active=bold_cyan
@@ -11,39 +21,30 @@ statusbar.new=bold_white
 main.text=bold_white
 main.text.me=bold_white
 main.text.them=bold_white
-input.text=bold_white
-main.time=bold_white
 main.splash=bold_cyan
+main.time=bold_white
+input.text=bold_white
 subscribed=bold_green
 unsubscribed=bold_red
+otr.started.trusted=bold_green
+otr.started.untrusted=bold_yellow
+otr.ended=bold_red
+otr.trusted=bold_green
+otr.untrusted=bold_yellow
 online=bold_green
 away=bold_cyan
 chat=bold_green
 dnd=bold_red
 xa=bold_cyan
 offline=bold_red
+incoming=bold_yellow
 typing=bold_yellow
-gone=bold_red
+gone=bold_yellow
 error=bold_red
-incoming=bold_yellow
 roominfo=bold_yellow
 roommention=bold_yellow
 me=bold_yellow
 them=bold_green
-titlebar.unencrypted=bold_red
-titlebar.encrypted=bold_white
-titlebar.untrusted=bold_yellow
-titlebar.trusted=bold_white
-titlebar.online=bold_white
-titlebar.offline=bold_white
-titlebar.away=bold_white
-titlebar.xa=bold_white
-titlebar.dnd=bold_white
-titlebar.chat=bold_white
-otr.started.trusted=bold_green
-otr.started.untrusted=bold_yellow
-otr.ended=bold_red
-otr.trusted=bold_green
-otr.untrusted=bold_yellow
 roster.header=bold_yellow
 occupants.header=bold_yellow
+receipt.sent=bold_red
diff --git a/themes/shade b/themes/shade
index 0b61a0f7..38d42860 100644
--- a/themes/shade
+++ b/themes/shade
@@ -1,36 +1,50 @@
 [colours]
 bkgnd=default
 titlebar=default
-statusbar=default
 titlebar.text=white
 titlebar.brackets=magenta
+titlebar.unencrypted=red
+titlebar.encrypted=green
+titlebar.untrusted=red
+titlebar.trusted=green
+titlebar.online=green
+titlebar.offline=red
+titlebar.away=green
+titlebar.chat=green
+titlebar.dnd=red
+titlebar.xa=green
+statusbar=default
 statusbar.text=magenta
 statusbar.brackets=magenta
 statusbar.active=white
 statusbar.new=green
 main.text=white
-input.text=white
-main.time=magenta
+main.text.me=white
+main.text.them=white
 main.splash=magenta
+main.time=magenta
+input.text=white
+subscribed=green
+unsubscribed=yallow
+otr.started.trusted=green
+otr.started.untrusted=red
+otr.ended=yellow
+otr.trusted=green
+otr.untrusted=red
 online=green
 away=yellow
 chat=green
 dnd=green
 xa=yellow
 offline=white
+incoming=yellow
 typing=green
 gone=red
 error=red
-incoming=yellow
 roominfo=green
-me=black
+roommention=green
+me=bold_black
 them=magenta
-titlebar.unencrypted=red
-titlebar.encrypted=green
-titlebar.untrusted=red
-titlebar.trusted=green
-otr.started.trusted=green
-otr.started.untrusted=red
-otr.ended=yellow
-otr.trusted=green
-otr.untrusted=red
+roster.header=magenta
+occupants.header=magenta
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/simple b/themes/simple
index 44c6977b..2885db12 100644
--- a/themes/simple
+++ b/themes/simple
@@ -1,67 +1,23 @@
-[colours]
-bkgnd=default
-titlebar=blue
-statusbar=blue
-titlebar.text=bold_white
-titlebar.brackets=white
-statusbar.text=bold_white
-statusbar.brackets=white
-statusbar.active=bold_cyan
-statusbar.new=bold_green
-main.text=white
-main.text.me=cyan
-main.text.them=bold_white
-input.text=bold_green
-main.time=yellow
-main.splash=bold_red
-online=bold_green
-away=bold_cyan
-chat=bold_white
-dnd=magenta
-xa=bold_blue
-offline=red
-typing=yellow
-gone=red
-error=red
-incoming=bold_yellow
-roominfo=yellow
-roommention=bold_red
-me=blue
-them=bold_green
-titlebar.unencrypted=bold_red
-titlebar.encrypted=bold_white
-titlebar.untrusted=bold_yellow
-titlebar.trusted=bold_white
-titlebar.online=bold_green
-titlebar.offline=bold_red
-titlebar.away=bold_cyan
-titlebar.xa=bold_cyan
-titlebar.dnd=bold_red
-titlebar.chat=bold_green
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
-roster.header=bold_yellow
-occupants.header=bold_yellow
-
 [ui]
-intype=false
 beep=false
 flash=false
-privileges=false
-presence=false
+splash=true
 wrap=true
 time=minutes
-statuses.muc=off
-statuses.chat=online
-statuses.console=off
+time.statusbar=off
+resource.title=false
+resource.message=false
+statuses.console=none
+statuses.chat=none
+statuses.muc=none
 occupants=true
 occupants.size=15
 roster=true
-roster.size=25
 roster.offline=false
 roster.resource=false
 roster.by=presence
+roster.size=25
+privileges=false
+presence=false
+intype=false
 otr.warn=false
diff --git a/themes/spawn b/themes/spawn
index 0b403905..b32862d3 100644
--- a/themes/spawn
+++ b/themes/spawn
@@ -1,37 +1,50 @@
 [colours]
 bkgnd=default
 titlebar=red
-statusbar=red
 titlebar.text=yellow
 titlebar.brackets=green
+titlebar.unencrypted=red
+titlebar.encrypted=green
+titlebar.untrusted=red
+titlebar.trusted=green
+titlebar.online=green
+titlebar.offline=red
+titlebar.away=green
+titlebar.chat=green
+titlebar.dnd=red
+titlebar.xa=green
+statusbar=red
 statusbar.text=yellow
 statusbar.brackets=green
 statusbar.active=white
 statusbar.new=green
 main.text=white
-input.text=green
-main.time=red
+main.text.me=white
+main.text.them=white
 main.splash=red
+main.time=red
+input.text=green
+subscribed=green
+unsubscribed=red
+otr.started.trusted=green
+otr.started.untrusted=red
+otr.ended=yellow
+otr.trusted=green
+otr.untrusted=red
 online=green
 away=yellow
 chat=green
 dnd=green
 xa=yellow
-offline=grey
+offline=bold_black
+incoming=yellow
 typing=green
 gone=red
 error=red
-incoming=yellow
 roominfo=green
 roommention=red
 me=green
 them=yellow
-titlebar.unencrypted=red
-titlebar.encrypted=green
-titlebar.untrusted=red
-titlebar.trusted=green
-otr.started.trusted=green
-otr.started.untrusted=red
-otr.ended=yellow
-otr.trusted=green
-otr.untrusted=red
+roster.header=white
+occupants.header=white
+receipt.sent=red
\ No newline at end of file
diff --git a/themes/whiteness b/themes/whiteness
index 597c9c83..30b5e0e7 100644
--- a/themes/whiteness
+++ b/themes/whiteness
@@ -1,36 +1,50 @@
 [colours]
 bkgnd=white
 titlebar=blue
-statusbar=blue
 titlebar.text=white
 titlebar.brackets=white
+titlebar.unencrypted=red
+titlebar.encrypted=white
+titlebar.untrusted=yellow
+titlebar.trusted=white
+titlebar.online=white
+titlebar.offline=red
+titlebar.away=white
+titlebar.chat=white
+titlebar.dnd=red
+titlebar.xa=white
+statusbar=blue
 statusbar.text=white
 statusbar.brackets=white
 statusbar.active=megenta
 statusbar.new=red
 main.text=black
-input.text=black
-main.time=black
+main.text.me=black
+main.text.them=black
 main.splash=black
+main.time=black
+input.text=black
+subscribed=green
+unsubscribed=red
+otr.started.trusted=green
+otr.started.untrusted=yellow
+otr.ended=red
+otr.trusted=green
+otr.untrusted=yellow
 online=green
 away=cyan
 chat=green
 dnd=red
 xa=cyan
 offline=red
+incoming=yellow
 typing=yellow
 gone=red
 error=red
-incoming=yellow
 roominfo=yellow
-me=yellow
-them=green
-titlebar.unencrypted=red
-titlebar.encrypted=white
-titlebar.untrusted=yellow
-titlebar.trusted=white
-otr.started.trusted=green
-otr.started.untrusted=yellow
-otr.ended=red
-otr.trusted=green
-otr.untrusted=yellow
+roommention=yellow
+me=black
+them=black
+roster.header=black
+occupants.header=black
+receipt.sent=red
\ No newline at end of file