about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG8
-rw-r--r--LICENSE.txt2
-rw-r--r--README.md2
-rw-r--r--apidocs/python/conf.py2
-rw-r--r--docs/profanity.12
-rw-r--r--profanity.spec2
-rw-r--r--src/command/cmd_ac.c207
-rw-r--r--src/command/cmd_ac.h2
-rw-r--r--src/command/cmd_defs.c79
-rw-r--r--src/command/cmd_defs.h2
-rw-r--r--src/command/cmd_funcs.c408
-rw-r--r--src/command/cmd_funcs.h4
-rw-r--r--src/common.c17
-rw-r--r--src/common.h2
-rw-r--r--src/config/account.c2
-rw-r--r--src/config/account.h2
-rw-r--r--src/config/accounts.c5
-rw-r--r--src/config/accounts.h2
-rw-r--r--src/config/conflists.c2
-rw-r--r--src/config/conflists.h2
-rw-r--r--src/config/files.c2
-rw-r--r--src/config/files.h2
-rw-r--r--src/config/preferences.c70
-rw-r--r--src/config/preferences.h15
-rw-r--r--src/config/scripts.c2
-rw-r--r--src/config/scripts.h2
-rw-r--r--src/config/theme.c19
-rw-r--r--src/config/theme.h2
-rw-r--r--src/config/tlscerts.c2
-rw-r--r--src/config/tlscerts.h2
-rw-r--r--src/event/client_events.c3
-rw-r--r--src/event/client_events.h2
-rw-r--r--src/event/server_events.c13
-rw-r--r--src/event/server_events.h2
-rw-r--r--src/log.c11
-rw-r--r--src/log.h2
-rw-r--r--src/main.c26
-rw-r--r--src/otr/otr.c2
-rw-r--r--src/otr/otr.h2
-rw-r--r--src/otr/otrlib.h2
-rw-r--r--src/otr/otrlibv3.c2
-rw-r--r--src/otr/otrlibv4.c2
-rw-r--r--src/pgp/gpg.c7
-rw-r--r--src/pgp/gpg.h2
-rw-r--r--src/plugins/api.c4
-rw-r--r--src/plugins/api.h2
-rw-r--r--src/plugins/autocompleters.c2
-rw-r--r--src/plugins/autocompleters.h2
-rw-r--r--src/plugins/c_api.c2
-rw-r--r--src/plugins/c_api.h2
-rw-r--r--src/plugins/c_plugins.c2
-rw-r--r--src/plugins/c_plugins.h2
-rw-r--r--src/plugins/callbacks.c2
-rw-r--r--src/plugins/callbacks.h2
-rw-r--r--src/plugins/disco.c2
-rw-r--r--src/plugins/disco.h2
-rw-r--r--src/plugins/plugins.c2
-rw-r--r--src/plugins/plugins.h2
-rw-r--r--src/plugins/profapi.c2
-rw-r--r--src/plugins/profapi.h2
-rw-r--r--src/plugins/python_api.c2
-rw-r--r--src/plugins/python_api.h2
-rw-r--r--src/plugins/python_plugins.c15
-rw-r--r--src/plugins/python_plugins.h5
-rw-r--r--src/plugins/settings.c2
-rw-r--r--src/plugins/settings.h2
-rw-r--r--src/plugins/themes.c2
-rw-r--r--src/plugins/themes.h2
-rw-r--r--src/profanity.c2
-rw-r--r--src/profanity.h2
-rw-r--r--src/tools/autocomplete.c2
-rw-r--r--src/tools/autocomplete.h2
-rw-r--r--src/tools/http_upload.c2
-rw-r--r--src/tools/http_upload.h2
-rw-r--r--src/tools/parser.c2
-rw-r--r--src/tools/parser.h2
-rw-r--r--src/tools/tinyurl.c2
-rw-r--r--src/tools/tinyurl.h2
-rw-r--r--src/ui/buffer.c2
-rw-r--r--src/ui/buffer.h2
-rw-r--r--src/ui/chatwin.c16
-rw-r--r--src/ui/console.c80
-rw-r--r--src/ui/core.c49
-rw-r--r--src/ui/inputwin.c28
-rw-r--r--src/ui/inputwin.h2
-rw-r--r--src/ui/mucconfwin.c2
-rw-r--r--src/ui/mucwin.c21
-rw-r--r--src/ui/notifier.c2
-rw-r--r--src/ui/occupantswin.c2
-rw-r--r--src/ui/privwin.c6
-rw-r--r--src/ui/rosterwin.c11
-rw-r--r--src/ui/screen.c2
-rw-r--r--src/ui/screen.h2
-rw-r--r--src/ui/statusbar.c714
-rw-r--r--src/ui/statusbar.h15
-rw-r--r--src/ui/titlebar.c2
-rw-r--r--src/ui/titlebar.h2
-rw-r--r--src/ui/tray.c2
-rw-r--r--src/ui/tray.h2
-rw-r--r--src/ui/ui.h10
-rw-r--r--src/ui/win_types.h4
-rw-r--r--src/ui/window.c56
-rw-r--r--src/ui/window.h2
-rw-r--r--src/ui/window_list.c43
-rw-r--r--src/ui/window_list.h4
-rw-r--r--src/ui/xmlwin.c2
-rw-r--r--src/xmpp/blocking.c2
-rw-r--r--src/xmpp/blocking.h2
-rw-r--r--src/xmpp/bookmark.c14
-rw-r--r--src/xmpp/bookmark.h2
-rw-r--r--src/xmpp/capabilities.c2
-rw-r--r--src/xmpp/capabilities.h2
-rw-r--r--src/xmpp/chat_session.c2
-rw-r--r--src/xmpp/chat_session.h2
-rw-r--r--src/xmpp/chat_state.c2
-rw-r--r--src/xmpp/chat_state.h2
-rw-r--r--src/xmpp/connection.c4
-rw-r--r--src/xmpp/connection.h2
-rw-r--r--src/xmpp/contact.c2
-rw-r--r--src/xmpp/contact.h2
-rw-r--r--src/xmpp/form.c2
-rw-r--r--src/xmpp/form.h2
-rw-r--r--src/xmpp/iq.c143
-rw-r--r--src/xmpp/iq.h2
-rw-r--r--src/xmpp/jid.c2
-rw-r--r--src/xmpp/jid.h2
-rw-r--r--src/xmpp/message.c2
-rw-r--r--src/xmpp/message.h2
-rw-r--r--src/xmpp/muc.c95
-rw-r--r--src/xmpp/muc.h7
-rw-r--r--src/xmpp/presence.c2
-rw-r--r--src/xmpp/presence.h2
-rw-r--r--src/xmpp/resource.c2
-rw-r--r--src/xmpp/resource.h2
-rw-r--r--src/xmpp/roster.c2
-rw-r--r--src/xmpp/roster.h2
-rw-r--r--src/xmpp/roster_list.c2
-rw-r--r--src/xmpp/roster_list.h2
-rw-r--r--src/xmpp/session.c4
-rw-r--r--src/xmpp/session.h2
-rw-r--r--src/xmpp/stanza.c2
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h6
-rw-r--r--tests/functionaltests/functionaltests.c9
-rw-r--r--tests/functionaltests/test_muc.c54
-rw-r--r--tests/functionaltests/test_muc.h2
-rw-r--r--tests/functionaltests/test_ping.c117
-rw-r--r--tests/functionaltests/test_ping.h7
-rw-r--r--tests/functionaltests/test_rooms.c12
-rw-r--r--tests/unittests/test_cmd_bookmark.c130
-rw-r--r--tests/unittests/test_cmd_bookmark.h5
-rw-r--r--tests/unittests/test_cmd_rooms.c34
-rw-r--r--tests/unittests/test_cmd_rooms.h3
-rw-r--r--tests/unittests/ui/stub_ui.c11
-rw-r--r--tests/unittests/unittests.c26
-rw-r--r--tests/unittests/xmpp/stub_xmpp.c4
-rw-r--r--themes/boothj511
-rw-r--r--themes/boothj5_slack6
158 files changed, 2152 insertions, 733 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b9d3d284..dc4e944a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,13 @@
 =====
 
 - Allow moving vertical window positions (/titlebar, /mainwin, /statusbar, /inputwin)
+- Allow loading/unloading all plugins (/plugins)
+- Allow installing plugins from directory (/plugins)
+- Theme option for status bar time (statusbar.time)
+- Case/accent insensitive autocompletion
+- Shift tab to select previous autocomplete suggestion
+- Allow searching help (/help search_all|search_any)
+- Support for Legacy SSL
 
 0.5.1
 =====
@@ -14,6 +21,7 @@
 - Allow plugins to complete file paths with prof.filepath_completer_add function
 - Add encryption settings functions to plugins api
 - Allow plugins to block message sending on pre message send hooks
+- Fix CVE-2017-5592 (incorrect implementation of Message Carbons allowing social engineering attacks)
 - Bug fixes: https://github.com/boothj5/profanity/milestone/15?closed=1
 
 0.5.0
diff --git a/LICENSE.txt b/LICENSE.txt
index d634a15d..fa781683 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,5 +1,5 @@
 Profanity
-Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+Copyright (C) 2012 - 2018 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/README.md b/README.md
index a0fd02e6..b242bab3 100644
--- a/README.md
+++ b/README.md
@@ -12,8 +12,6 @@ Links
 
 Homepage: http://www.profanity.im
 
-Chat room: profanitydev@conference.jabber.org
-
 Mailing List: https://groups.google.com/forum/#!forum/profanitydev
 
 Plugins repository: https://github.com/boothj5/profanity-plugins
diff --git a/apidocs/python/conf.py b/apidocs/python/conf.py
index c4793a1e..e14e98c6 100644
--- a/apidocs/python/conf.py
+++ b/apidocs/python/conf.py
@@ -48,7 +48,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'Profanity Python Plugins API'
-copyright = u'2016 - 2017, boothj5'
+copyright = u'2016 - 2018, boothj5'
 author = u'boothj5'
 
 # The version info for the project you're documenting, acts as replacement for
diff --git a/docs/profanity.1 b/docs/profanity.1
index 01b6c12e..eb0c887e 100644
--- a/docs/profanity.1
+++ b/docs/profanity.1
@@ -62,7 +62,7 @@ or by sending a mail directly to:
 .br
 .PP
 .SH LICENSE
-Copyright (C) 2012 \- 2017 James Booth <boothj5web@gmail.com>.
+Copyright (C) 2012 \- 2018 James Booth <boothj5web@gmail.com>.
 License GPLv3+: GNU GPL version 3 or later <https://www.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/profanity.spec b/profanity.spec
index fad76834..fdd5c33b 100644
--- a/profanity.spec
+++ b/profanity.spec
@@ -1,5 +1,5 @@
 Name:		profanity
-Version:	0.5.0
+Version:	0.5.1
 Release:	2%{?dist}
 Summary:	A console based XMPP client
 
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index e5ec6f11..f9d5a22a 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -1,7 +1,7 @@
 /*
  * cmd_ac.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -98,6 +98,8 @@ static char* _sendfile_autocomplete(ProfWin *window, const char *const input, gb
 static char* _blocked_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _tray_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _presence_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _rooms_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _statusbar_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 
 static char* _script_autocomplete_func(const char *const prefix, gboolean previous);
 
@@ -143,6 +145,7 @@ static Autocomplete roster_presence_ac;
 static Autocomplete roster_char_ac;
 static Autocomplete roster_remove_all_ac;
 static Autocomplete roster_room_ac;
+static Autocomplete roster_room_show_ac;
 static Autocomplete roster_room_position_ac;
 static Autocomplete roster_room_by_ac;
 static Autocomplete roster_room_order_ac;
@@ -160,6 +163,9 @@ static Autocomplete alias_ac;
 static Autocomplete aliases_ac;
 static Autocomplete join_property_ac;
 static Autocomplete room_ac;
+static Autocomplete rooms_all_ac;
+static Autocomplete rooms_list_ac;
+static Autocomplete rooms_cache_ac;
 static Autocomplete affiliation_ac;
 static Autocomplete role_ac;
 static Autocomplete privilege_cmd_ac;
@@ -194,6 +200,11 @@ static Autocomplete tray_ac;
 static Autocomplete presence_ac;
 static Autocomplete presence_setting_ac;
 static Autocomplete winpos_ac;
+static Autocomplete statusbar_ac;
+static Autocomplete statusbar_self_ac;
+static Autocomplete statusbar_chat_ac;
+static Autocomplete statusbar_room_ac;
+static Autocomplete statusbar_show_ac;
 
 void
 cmd_ac_init(void)
@@ -381,8 +392,6 @@ cmd_ac_init(void)
     wins_ac = autocomplete_new();
     autocomplete_add(wins_ac, "unread");
     autocomplete_add(wins_ac, "prune");
-    autocomplete_add(wins_ac, "tidy");
-    autocomplete_add(wins_ac, "autotidy");
     autocomplete_add(wins_ac, "swap");
 
     roster_ac = autocomplete_new();
@@ -469,6 +478,11 @@ cmd_ac_init(void)
     autocomplete_add(roster_room_ac, "order");
     autocomplete_add(roster_room_ac, "unread");
     autocomplete_add(roster_room_ac, "private");
+    autocomplete_add(roster_room_ac, "show");
+    autocomplete_add(roster_room_ac, "hide");
+
+    roster_room_show_ac = autocomplete_new();
+    autocomplete_add(roster_room_show_ac, "server");
 
     roster_room_by_ac = autocomplete_new();
     autocomplete_add(roster_room_by_ac, "service");
@@ -568,6 +582,7 @@ cmd_ac_init(void)
     tls_property_ac = autocomplete_new();
     autocomplete_add(tls_property_ac, "force");
     autocomplete_add(tls_property_ac, "allow");
+    autocomplete_add(tls_property_ac, "legacy");
     autocomplete_add(tls_property_ac, "disable");
 
     join_property_ac = autocomplete_new();
@@ -584,6 +599,20 @@ cmd_ac_init(void)
     autocomplete_add(room_ac, "destroy");
     autocomplete_add(room_ac, "config");
 
+    rooms_all_ac = autocomplete_new();
+    autocomplete_add(rooms_all_ac, "service");
+    autocomplete_add(rooms_all_ac, "filter");
+    autocomplete_add(rooms_all_ac, "cache");
+
+    rooms_list_ac = autocomplete_new();
+    autocomplete_add(rooms_list_ac, "service");
+    autocomplete_add(rooms_list_ac, "filter");
+
+    rooms_cache_ac = autocomplete_new();
+    autocomplete_add(rooms_cache_ac, "on");
+    autocomplete_add(rooms_cache_ac, "off");
+    autocomplete_add(rooms_cache_ac, "clear");
+
     affiliation_ac = autocomplete_new();
     autocomplete_add(affiliation_ac, "owner");
     autocomplete_add(affiliation_ac, "admin");
@@ -749,6 +778,35 @@ cmd_ac_init(void)
     winpos_ac = autocomplete_new();
     autocomplete_add(winpos_ac, "up");
     autocomplete_add(winpos_ac, "down");
+
+    statusbar_ac = autocomplete_new();
+    autocomplete_add(statusbar_ac, "up");
+    autocomplete_add(statusbar_ac, "down");
+    autocomplete_add(statusbar_ac, "show");
+    autocomplete_add(statusbar_ac, "hide");
+    autocomplete_add(statusbar_ac, "maxtabs");
+    autocomplete_add(statusbar_ac, "tablen");
+    autocomplete_add(statusbar_ac, "self");
+    autocomplete_add(statusbar_ac, "chat");
+    autocomplete_add(statusbar_ac, "room");
+
+    statusbar_self_ac = autocomplete_new();
+    autocomplete_add(statusbar_self_ac, "user");
+    autocomplete_add(statusbar_self_ac, "barejid");
+    autocomplete_add(statusbar_self_ac, "fulljid");
+    autocomplete_add(statusbar_self_ac, "off");
+
+    statusbar_chat_ac = autocomplete_new();
+    autocomplete_add(statusbar_chat_ac, "user");
+    autocomplete_add(statusbar_chat_ac, "jid");
+
+    statusbar_room_ac = autocomplete_new();
+    autocomplete_add(statusbar_room_ac, "room");
+    autocomplete_add(statusbar_room_ac, "jid");
+
+    statusbar_show_ac = autocomplete_new();
+    autocomplete_add(statusbar_show_ac, "name");
+    autocomplete_add(statusbar_show_ac, "number");
 }
 
 void
@@ -915,6 +973,7 @@ cmd_ac_reset(ProfWin *window)
     }
 
     muc_invites_reset_ac();
+    muc_confserver_reset_ac();
     accounts_reset_all_search();
     accounts_reset_enabled_search();
     tlscerts_reset_ac();
@@ -978,6 +1037,7 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(roster_count_ac);
     autocomplete_reset(roster_order_ac);
     autocomplete_reset(roster_room_ac);
+    autocomplete_reset(roster_room_show_ac);
     autocomplete_reset(roster_room_by_ac);
     autocomplete_reset(roster_unread_ac);
     autocomplete_reset(roster_room_position_ac);
@@ -997,6 +1057,9 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(aliases_ac);
     autocomplete_reset(join_property_ac);
     autocomplete_reset(room_ac);
+    autocomplete_reset(rooms_all_ac);
+    autocomplete_reset(rooms_list_ac);
+    autocomplete_reset(rooms_cache_ac);
     autocomplete_reset(affiliation_ac);
     autocomplete_reset(role_ac);
     autocomplete_reset(privilege_cmd_ac);
@@ -1025,6 +1088,11 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(presence_ac);
     autocomplete_reset(presence_setting_ac);
     autocomplete_reset(winpos_ac);
+    autocomplete_reset(statusbar_ac);
+    autocomplete_reset(statusbar_self_ac);
+    autocomplete_reset(statusbar_chat_ac);
+    autocomplete_reset(statusbar_room_ac);
+    autocomplete_reset(statusbar_show_ac);
 
     autocomplete_reset(script_ac);
     if (script_show_ac) {
@@ -1097,6 +1165,7 @@ cmd_ac_uninit(void)
     autocomplete_free(roster_count_ac);
     autocomplete_free(roster_order_ac);
     autocomplete_free(roster_room_ac);
+    autocomplete_free(roster_room_show_ac);
     autocomplete_free(roster_room_by_ac);
     autocomplete_free(roster_unread_ac);
     autocomplete_free(roster_room_position_ac);
@@ -1115,6 +1184,9 @@ cmd_ac_uninit(void)
     autocomplete_free(aliases_ac);
     autocomplete_free(join_property_ac);
     autocomplete_free(room_ac);
+    autocomplete_free(rooms_all_ac);
+    autocomplete_free(rooms_list_ac);
+    autocomplete_free(rooms_cache_ac);
     autocomplete_free(affiliation_ac);
     autocomplete_free(role_ac);
     autocomplete_free(privilege_cmd_ac);
@@ -1148,6 +1220,11 @@ cmd_ac_uninit(void)
     autocomplete_free(presence_ac);
     autocomplete_free(presence_setting_ac);
     autocomplete_free(winpos_ac);
+    autocomplete_free(statusbar_ac);
+    autocomplete_free(statusbar_self_ac);
+    autocomplete_free(statusbar_chat_ac);
+    autocomplete_free(statusbar_room_ac);
+    autocomplete_free(statusbar_show_ac);
 }
 
 char*
@@ -1267,7 +1344,7 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
 
     // autocomplete boolean settings
     gchar *boolean_choices[] = { "/beep", "/intype", "/states", "/outtype", "/flash", "/splash", "/chlog", "/grlog",
-        "/history", "/vercheck", "/privileges", "/wrap", "/winstidy", "/carbons", "/encwarn",
+        "/history", "/vercheck", "/privileges", "/wrap", "/carbons", "/encwarn",
         "/lastactivity" };
 
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
@@ -1335,8 +1412,8 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
         }
     }
 
-    gchar *cmds[] = { "/prefs", "/disco", "/room", "/autoping", "/titlebar", "/mainwin", "/statusbar", "/inputwin" };
-    Autocomplete completers[] = { prefs_ac, disco_ac, room_ac, autoping_ac, winpos_ac, winpos_ac, winpos_ac, winpos_ac };
+    gchar *cmds[] = { "/prefs", "/disco", "/room", "/autoping", "/titlebar", "/mainwin", "/inputwin" };
+    Autocomplete completers[] = { prefs_ac, disco_ac, room_ac, autoping_ac, winpos_ac, winpos_ac, winpos_ac };
 
     for (i = 0; i < ARRAY_SIZE(cmds); i++) {
         result = autocomplete_param_with_ac(input, cmds[i], completers[i], TRUE, previous);
@@ -1386,6 +1463,8 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/blocked",       _blocked_autocomplete);
     g_hash_table_insert(ac_funcs, "/tray",          _tray_autocomplete);
     g_hash_table_insert(ac_funcs, "/presence",      _presence_autocomplete);
+    g_hash_table_insert(ac_funcs, "/rooms",         _rooms_autocomplete);
+    g_hash_table_insert(ac_funcs, "/statusbar",     _statusbar_autocomplete);
 
     int len = strlen(input);
     char parsed[len+1];
@@ -1549,6 +1628,14 @@ _roster_autocomplete(ProfWin *window, const char *const input, gboolean previous
     if (result) {
         return result;
     }
+    result = autocomplete_param_with_ac(input, "/roster room show", roster_room_show_ac, TRUE, previous);
+    if (result) {
+        return result;
+    }
+    result = autocomplete_param_with_ac(input, "/roster room hide", roster_room_show_ac, TRUE, previous);
+    if (result) {
+        return result;
+    }
     result = autocomplete_param_with_func(input, "/roster count zero", prefs_autocomplete_boolean_choice, previous);
     if (result) {
         return result;
@@ -2589,11 +2676,6 @@ _wins_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_func(input, "/wins autotidy", prefs_autocomplete_boolean_choice, previous);
-    if (result) {
-        return result;
-    }
-
     result = autocomplete_param_with_ac(input, "/wins", wins_ac, TRUE, previous);
     if (result) {
         return result;
@@ -3073,3 +3155,106 @@ _presence_autocomplete(ProfWin *window, const char *const input, gboolean previo
     return NULL;
 }
 
+static char*
+_rooms_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+{
+    char *found = NULL;
+    gboolean result = FALSE;
+
+    gchar **args = parse_args(input, 0, 4, &result);
+
+    if (result) {
+        gboolean space_at_end = g_str_has_suffix(input, " ");
+        int num_args = g_strv_length(args);
+        if (num_args <= 1) {
+            found = autocomplete_param_with_ac(input, "/rooms", rooms_all_ac, TRUE, previous);
+            if (found) {
+                g_strfreev(args);
+                return found;
+            }
+        }
+        if ((num_args == 1 && g_strcmp0(args[0], "service") == 0 && space_at_end) ||
+                (num_args == 2 && g_strcmp0(args[0], "service") == 0 && !space_at_end)) {
+            found = autocomplete_param_with_func(input, "/rooms service", muc_confserver_find, previous);
+            if (found) {
+                g_strfreev(args);
+                return found;
+            }
+        }
+        if ((num_args == 1 && g_strcmp0(args[0], "cache") == 0 && space_at_end) ||
+                (num_args == 2 && g_strcmp0(args[0], "cache") == 0 && !space_at_end)) {
+            found = autocomplete_param_with_ac(input, "/rooms cache", rooms_cache_ac, TRUE, previous);
+            if (found) {
+                g_strfreev(args);
+                return found;
+            }
+        }
+        if ((num_args == 2 && space_at_end) || (num_args == 3 && !space_at_end)) {
+            GString *beginning = g_string_new("/rooms");
+            g_string_append_printf(beginning, " %s %s", args[0], args[1]);
+            found = autocomplete_param_with_ac(input, beginning->str, rooms_list_ac, TRUE, previous);
+            g_string_free(beginning, TRUE);
+            if (found) {
+                g_strfreev(args);
+                return found;
+            }
+        }
+        if ((num_args == 3 && g_strcmp0(args[2], "service") == 0 && space_at_end) ||
+                (num_args == 4 && g_strcmp0(args[2], "service") == 0 && !space_at_end)) {
+            GString *beginning = g_string_new("/rooms");
+            g_string_append_printf(beginning, " %s %s %s", args[0], args[1], args[2]);
+            found = autocomplete_param_with_func(input, beginning->str, muc_confserver_find, previous);
+            g_string_free(beginning, TRUE);
+            if (found) {
+                g_strfreev(args);
+                return found;
+            }
+        }
+        if ((num_args >= 2) && g_strcmp0(args[0], "cache") == 0) {
+            g_strfreev(args);
+            return NULL;
+        }
+    }
+
+    g_strfreev(args);
+
+    return NULL;
+}
+
+static char*
+_statusbar_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+{
+    char *found = NULL;
+
+    found = autocomplete_param_with_ac(input, "/statusbar", statusbar_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/statusbar show", statusbar_show_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/statusbar hide", statusbar_show_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/statusbar self", statusbar_self_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/statusbar chat", statusbar_chat_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/statusbar room", statusbar_room_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    return NULL;
+}
diff --git a/src/command/cmd_ac.h b/src/command/cmd_ac.h
index d56d0e30..d7b47eb5 100644
--- a/src/command/cmd_ac.h
+++ b/src/command/cmd_ac.h
@@ -1,7 +1,7 @@
 /*
  * cmd_ac.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index ae77bf53..418155c4 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -1,7 +1,7 @@
 /*
  * cmd_defs.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -158,7 +158,7 @@ static struct cmd_t command_defs[] =
             CMD_TAG_CONNECTION)
         CMD_SYN(
             "/connect [<account>]",
-            "/connect <account> [server <server>] [port <port>] [tls force|allow|disable]")
+            "/connect <account> [server <server>] [port <port>] [tls force|allow|legacy|disable]")
         CMD_DESC(
             "Login to a chat service. "
             "If no account is specified, the default is used if one is configured. "
@@ -169,6 +169,7 @@ static struct cmd_t command_defs[] =
             { "port <port>",       "The port to use if different to the default (5222, or 5223 for SSL)." },
             { "tls force",         "Force TLS connection, and fail if one cannot be established, this is default behaviour." },
             { "tls allow",         "Use TLS for the connection if it is available." },
+            { "tls legacy",        "Use legacy TLS for the connection. It means server doesn't support STARTTLS and TLS is forced just after TCP connection is established." },
             { "tls disable",       "Disable TLS for the connection." })
         CMD_EXAMPLES(
             "/connect",
@@ -286,6 +287,8 @@ static struct cmd_t command_defs[] =
             "/roster room by service|none",
             "/roster room order name|unread",
             "/roster room unread before|after|off",
+            "/roster room show server",
+            "/roster room hide server",
             "/roster private room|group|off",
             "/roster private char <char>|none",
             "/roster header char <char>|none",
@@ -351,6 +354,8 @@ static struct cmd_t command_defs[] =
             { "room unread before",         "Show unread message count before room." },
             { "room unread after",          "Show unread message count after room." },
             { "room unread off",            "Do not show unread message count for rooms." },
+            { "room show server",           "Show the conference server with room JIDs." },
+            { "room hide server",           "Do not show the conference server with room JIDs." },
             { "private room",               "Show room private chats with the room." },
             { "private group",              "Show room private chats as a separate roster group." },
             { "private off",                "Do not show room private chats." },
@@ -783,20 +788,31 @@ static struct cmd_t command_defs[] =
     },
 
     { "/rooms",
-        parse_args, 0, 1, NULL,
+        parse_args, 0, 4, NULL,
         CMD_NOSUBFUNCS
         CMD_MAINFUNC(cmd_rooms)
         CMD_TAGS(
             CMD_TAG_GROUPCHAT)
         CMD_SYN(
-            "/rooms [<service>]")
+            "/rooms",
+            "/rooms filter <text>",
+            "/rooms service <service>",
+            "/rooms service <service> filter <text>",
+            "/rooms cache on|off|clear")
         CMD_DESC(
             "List the chat rooms available at the specified conference service. "
-            "If no argument is supplied, the account preference 'muc.service' is used, 'conference.<domain-part>' by default.")
+            "If no argument is supplied, the account preference 'muc.service' is used, 'conference.<domain-part>' by default. "
+            "The filter argument only shows rooms that contain the provided text, case insensitive.")
         CMD_ARGS(
-            { "<service>", "The conference service to query." })
+            { "service <service>",  "The conference service to query." },
+            { "filter <text>",      "The text to filter results by."},
+            { "cache on|off",       "Enable or disable caching of rooms list response, enabled by default."},
+            { "cache clear",        "Clear the rooms response cache if enabled."})
         CMD_EXAMPLES(
-            "/rooms conference.jabber.org")
+            "/rooms",
+            "/rooms filter development",
+            "/rooms service conference.jabber.org",
+            "/rooms service conference.jabber.org filter \"News Room\"")
     },
 
     { "/bookmark",
@@ -946,18 +962,14 @@ static struct cmd_t command_defs[] =
         parse_args, 0, 3, NULL,
         CMD_SUBFUNCS(
             { "unread",     cmd_wins_unread },
-            { "tidy",       cmd_wins_tidy },
             { "prune",      cmd_wins_prune },
-            { "swap",       cmd_wins_swap },
-            { "autotidy",   cmd_wins_autotidy })
+            { "swap",       cmd_wins_swap })
         CMD_MAINFUNC(cmd_wins)
         CMD_TAGS(
             CMD_TAG_UI)
         CMD_SYN(
             "/wins",
             "/wins unread",
-            "/wins tidy",
-            "/wins autotidy on|off",
             "/wins prune",
             "/wins swap <source> <target>")
         CMD_DESC(
@@ -965,9 +977,7 @@ static struct cmd_t command_defs[] =
             "Passing no argument will list all currently active windows and information about their usage.")
         CMD_ARGS(
             { "unread",                 "List windows with unread messages." },
-            { "tidy",                   "Move windows so there are no gaps." },
-            { "autotidy on|off",        "Automatically remove gaps when closing windows." },
-            { "prune",                  "Close all windows with no unread messages, and then tidy so there are no gaps." },
+            { "prune",                  "Close all windows with no unread messages." },
             { "swap <source> <target>", "Swap windows, target may be an empty position." })
         CMD_NOEXAMPLES
     },
@@ -1342,20 +1352,40 @@ static struct cmd_t command_defs[] =
     },
 
     { "/statusbar",
-        parse_args, 1, 1, &cons_winpos_setting,
+        parse_args, 1, 2, &cons_statusbar_setting,
         CMD_NOSUBFUNCS
         CMD_MAINFUNC(cmd_statusbar)
         CMD_TAGS(
             CMD_TAG_UI)
         CMD_SYN(
+            "/statusbar show name|number",
+            "/statusbar hide name|number",
+            "/statusbar maxtabs <value>",
+            "/statusbar tablen <value>",
+            "/statusbar self user|barejid|fulljid|off",
+            "/statusbar chat user|jid",
+            "/statusbar room room|jid",
             "/statusbar up",
             "/statusbar down")
         CMD_DESC(
-            "Move the status bar.")
-        CMD_ARGS(
-            { "up", "Move the status bar up the screen." },
-            { "down", "Move the status bar down the screen." })
-        CMD_NOEXAMPLES
+            "Manage statusbar display preferences.")
+        CMD_ARGS(
+            { "maxtabs <value>",            "Set the maximum number of tabs to display, <value> must be between 0 and 10" },
+            { "tablen <value>",             "Set the maximum number of characters to show as the tab name, 0 sets to unlimited." },
+            { "show|hide name",             "Show or hide names in tabs." },
+            { "show|hide number",           "Show or hide numbers in tabs." },
+            { "self user|barejid|fulljid",  "Show account user name, barejid, fulljid as status bar title." },
+            { "self off",                   "Disable showing self as status bar title." },
+            { "chat user|jid",              "Show users name, or the fulljid if no nick is present for chat tabs." },
+            { "room room|jid",              "Show room name, or the fulljid for room tabs." },
+            { "up",                         "Move the status bar up the screen." },
+            { "down",                       "Move the status bar down the screen." })
+        CMD_EXAMPLES(
+            "/statusbar maxtabs 8",
+            "/statusbar tablen 5",
+            "/statusbar self user",
+            "/statusbar chat jid",
+            "/statusbar hide name")
     },
 
     { "/inputwin",
@@ -1984,7 +2014,7 @@ static struct cmd_t command_defs[] =
             "/account set <account> otr <policy>",
             "/account set <account> pgpkeyid <pgpkeyid>",
             "/account set <account> startscript <script>",
-            "/account set <account> tls force|allow|disable",
+            "/account set <account> tls force|allow|legacy|disable",
             "/account set <account> theme <theme>",
             "/account clear <account> password",
             "/account clear <account> eval_password",
@@ -2024,6 +2054,7 @@ static struct cmd_t command_defs[] =
             { "set <account> startscript <script>",     "Set the script to execute after connecting." },
             { "set <account> tls force",                "Force TLS connection, and fail if one cannot be established, this is default behaviour." },
             { "set <account> tls allow",                "Use TLS for the connection if it is available." },
+            { "set <account> tls legacy",               "Use legacy TLS for the connection. It means server doesn't support STARTTLS and TLS is forced just after TCP connection is established." },
             { "set <account> tls disable",              "Disable TLS for the connection." },
             { "set <account> <theme>",                  "Set the UI theme for the account." },
             { "clear <account> server",                 "Remove the server setting for this account." },
@@ -2384,7 +2415,7 @@ cmd_init(void)
 
     cmd_ac_init();
 
-    search_index = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+    search_index = g_hash_table_new_full(g_str_hash, g_str_equal, free, g_free);
 
     // load command defs into hash table
     commands = g_hash_table_new(g_str_hash, g_str_equal);
@@ -2396,7 +2427,7 @@ cmd_init(void)
         g_hash_table_insert(commands, pcmd->cmd, pcmd);
 
         // add to search index
-        g_hash_table_insert(search_index, strdup(pcmd->cmd), strdup(_cmd_index(pcmd)));
+        g_hash_table_insert(search_index, strdup(pcmd->cmd), _cmd_index(pcmd));
 
         // add to commands and help autocompleters
         cmd_ac_add_cmd(pcmd);
diff --git a/src/command/cmd_defs.h b/src/command/cmd_defs.h
index e0cadaf2..d318e7ed 100644
--- a/src/command/cmd_defs.h
+++ b/src/command/cmd_defs.h
@@ -1,7 +1,7 @@
 /*
  * cmd_defs.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 71c8816a..c8aa22b4 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -1,7 +1,7 @@
 /*
  * cmd_funcs.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -350,7 +350,8 @@ cmd_connect(ProfWin *window, const char *const command, gchar **args)
     if (tls_policy &&
             (g_strcmp0(tls_policy, "force") != 0) &&
             (g_strcmp0(tls_policy, "allow") != 0) &&
-            (g_strcmp0(tls_policy, "disable") != 0)) {
+            (g_strcmp0(tls_policy, "disable") != 0) &&
+            (g_strcmp0(tls_policy, "legacy") != 0)) {
         cons_bad_cmd_usage(command);
         cons_show("");
         return TRUE;
@@ -813,8 +814,9 @@ _account_set_tls(char *account_name, char *policy)
 {
     if ((g_strcmp0(policy, "force") != 0)
             && (g_strcmp0(policy, "allow") != 0)
-            && (g_strcmp0(policy, "disable") != 0)) {
-        cons_show("TLS policy must be one of: force, allow or disable.");
+            && (g_strcmp0(policy, "disable") != 0)
+            && (g_strcmp0(policy, "legacy") != 0)) {
+        cons_show("TLS policy must be one of: force, allow, legacy or disable.");
     } else {
         accounts_set_tls_policy(account_name, policy);
         cons_show("Updated TLS policy for account %s: %s", account_name, policy);
@@ -1251,17 +1253,6 @@ cmd_wins_unread(ProfWin *window, const char *const command, gchar **args)
 }
 
 gboolean
-cmd_wins_tidy(ProfWin *window, const char *const command, gchar **args)
-{
-    if (wins_tidy()) {
-        cons_show("Windows tidied.");
-    } else {
-        cons_show("No tidy needed.");
-    }
-    return TRUE;
-}
-
-gboolean
 cmd_wins_prune(ProfWin *window, const char *const command, gchar **args)
 {
     ui_prune_wins();
@@ -1278,38 +1269,34 @@ cmd_wins_swap(ProfWin *window, const char *const command, gchar **args)
 
     int source_win = atoi(args[1]);
     int target_win = atoi(args[2]);
+
     if ((source_win == 1) || (target_win == 1)) {
         cons_show("Cannot move console window.");
-    } else if (source_win == 10 || target_win == 10) {
+        return TRUE;
+    }
+
+    if (source_win == 10 || target_win == 10) {
         cons_show("Window 10 does not exist");
-    } else if (source_win != target_win) {
-        gboolean swapped = wins_swap(source_win, target_win);
-        if (swapped) {
-            cons_show("Swapped windows %d <-> %d", source_win, target_win);
-        } else {
-            cons_show("Window %d does not exist", source_win);
-        }
-    } else {
+        return TRUE;
+    }
+
+    if (source_win == target_win) {
         cons_show("Same source and target window supplied.");
+        return TRUE;
     }
 
-    return TRUE;
-}
+    if (wins_get_by_num(source_win) == NULL) {
+        cons_show("Window %d does not exist", source_win);
+        return TRUE;
+    }
 
-gboolean
-cmd_wins_autotidy(ProfWin *window, const char *const command, gchar **args)
-{
-    if (g_strcmp0(args[1], "on") == 0) {
-        cons_show("Window autotidy enabled");
-        prefs_set_boolean(PREF_WINS_AUTO_TIDY, TRUE);
-        wins_tidy();
-    } else if (g_strcmp0(args[1], "off") == 0) {
-        cons_show("Window autotidy disabled");
-        prefs_set_boolean(PREF_WINS_AUTO_TIDY, FALSE);
-    } else {
-        cons_bad_cmd_usage(command);
+    if (wins_get_by_num(target_win) == NULL) {
+        cons_show("Window %d does not exist", target_win);
+        return TRUE;
     }
 
+    wins_swap(source_win, target_win);
+    cons_show("Swapped windows %d <-> %d", source_win, target_win);
     return TRUE;
 }
 
@@ -1405,11 +1392,7 @@ cmd_close(ProfWin *window, const char *const command, gchar **args)
         // close the window
         ui_close_win(index);
         cons_show("Closed window %d", index);
-
-        // Tidy up the window list.
-        if (prefs_get_boolean(PREF_WINS_AUTO_TIDY)) {
-            wins_tidy();
-        }
+        wins_tidy();
 
         rosterwin_roster();
         return TRUE;
@@ -1440,11 +1423,7 @@ cmd_close(ProfWin *window, const char *const command, gchar **args)
         // close the window
         ui_close_win(index);
         cons_show("Closed window %s", args[0]);
-
-        // Tidy up the window list.
-        if (prefs_get_boolean(PREF_WINS_AUTO_TIDY)) {
-            wins_tidy();
-        }
+        wins_tidy();
 
         rosterwin_roster();
         return TRUE;
@@ -2093,7 +2072,7 @@ cmd_who(ProfWin *window, const char *const command, gchar **args)
     }
 
     if (window->type != WIN_CONSOLE && window->type != WIN_MUC) {
-        status_bar_new(1);
+        status_bar_new(1, WIN_CONSOLE, "console");
     }
 
     return TRUE;
@@ -2867,6 +2846,30 @@ cmd_roster(ProfWin *window, const char *const command, gchar **args)
                 cons_bad_cmd_usage(command);
                 return TRUE;
             }
+        } else if (g_strcmp0(args[1], "show") == 0) {
+            if (g_strcmp0(args[2], "server") == 0) {
+                cons_show("Roster room server enabled.");
+                prefs_set_boolean(PREF_ROSTER_ROOMS_SERVER, TRUE);
+                if (conn_status == JABBER_CONNECTED) {
+                    rosterwin_roster();
+                }
+                return TRUE;
+            } else {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
+        } else if (g_strcmp0(args[1], "hide") == 0) {
+            if (g_strcmp0(args[2], "server") == 0) {
+                cons_show("Roster room server disabled.");
+                prefs_set_boolean(PREF_ROSTER_ROOMS_SERVER, FALSE);
+                if (conn_status == JABBER_CONNECTED) {
+                    rosterwin_roster();
+                }
+                return TRUE;
+            } else {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
         } else {
             cons_bad_cmd_usage(command);
             return TRUE;
@@ -3916,6 +3919,7 @@ cmd_form(ProfWin *window, const char *const command, gchar **args)
         }
         ui_focus_win(new_current);
         wins_close_by_num(num);
+        wins_tidy();
     }
 
     return TRUE;
@@ -4367,18 +4371,102 @@ cmd_rooms(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    if (args[0]) {
-        iq_room_list_request(args[0]);
-        return TRUE;
+    gchar *service = NULL;
+    gchar *filter = NULL;
+    if (args[0] != NULL) {
+        if (g_strcmp0(args[0], "service") == 0) {
+            if (args[1] == NULL) {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                return TRUE;
+            }
+            service = g_strdup(args[1]);
+        } else if (g_strcmp0(args[0], "filter") == 0) {
+            if (args[1] == NULL) {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                return TRUE;
+            }
+            filter = g_strdup(args[1]);
+        } else if (g_strcmp0(args[0], "cache") == 0) {
+            if (g_strv_length(args) != 2) {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                return TRUE;
+            } else if (g_strcmp0(args[1], "on") == 0) {
+                prefs_set_boolean(PREF_ROOM_LIST_CACHE, TRUE);
+                cons_show("Rooms list cache enabled.");
+                return TRUE;
+            } else if (g_strcmp0(args[1], "off") == 0) {
+                prefs_set_boolean(PREF_ROOM_LIST_CACHE, FALSE);
+                cons_show("Rooms list cache disabled.");
+                return TRUE;
+            } else if (g_strcmp0(args[1], "clear") == 0) {
+                iq_rooms_cache_clear();
+                cons_show("Rooms list cache cleared.");
+                return TRUE;
+            } else {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                return TRUE;
+            }
+        } else {
+            cons_bad_cmd_usage(command);
+            cons_show("");
+            return TRUE;
+        }
+    }
+    if (g_strv_length(args) >=3 ) {
+        if (g_strcmp0(args[2], "service") == 0) {
+            if (args[3] == NULL) {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                g_free(service);
+                g_free(filter);
+                return TRUE;
+            }
+            g_free(service);
+            service = g_strdup(args[3]);
+        } else if (g_strcmp0(args[2], "filter") == 0) {
+            if (args[3] == NULL) {
+                cons_bad_cmd_usage(command);
+                cons_show("");
+                g_free(service);
+                g_free(filter);
+                return TRUE;
+            }
+            g_free(filter);
+            filter = g_strdup(args[3]);
+        } else {
+            cons_bad_cmd_usage(command);
+            cons_show("");
+            return TRUE;
+        }
     }
 
-    ProfAccount *account = accounts_get_account(session_get_account_name());
-    if (account->muc_service) {
-        iq_room_list_request(account->muc_service);
+    if (service == NULL) {
+        ProfAccount *account = accounts_get_account(session_get_account_name());
+        if (account->muc_service) {
+            service = g_strdup(account->muc_service);
+            account_free(account);
+        } else {
+            cons_show("Account MUC service property not found.");
+            account_free(account);
+            g_free(service);
+            g_free(filter);
+            return TRUE;
+        }
+    }
+
+    cons_show("");
+    if (filter) {
+        cons_show("Room list request sent: %s, filter: '%s'", service, filter);
     } else {
-        cons_show("Account MUC service property not found.");
+        cons_show("Room list request sent: %s", service);
     }
-    account_free(account);
+    iq_room_list_request(service, filter);
+
+    g_free(service);
 
     return TRUE;
 }
@@ -4390,11 +4478,15 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
 
     if (conn_status != JABBER_CONNECTED) {
         cons_show("You are not currently connected.");
+        cons_alert();
         return TRUE;
     }
 
+    int num_args = g_strv_length(args);
     gchar *cmd = args[0];
-    if (window->type == WIN_MUC && (cmd == NULL || g_strcmp0(cmd, "add") == 0)) {
+    if (window->type == WIN_MUC
+            && num_args < 2
+            && (cmd == NULL || g_strcmp0(cmd, "add") == 0)) {
         // default to current nickname, password, and autojoin "on"
         ProfMucWin *mucwin = (ProfMucWin*)window;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
@@ -4409,7 +4501,9 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    if (window->type == WIN_MUC && g_strcmp0(cmd, "remove") == 0) {
+    if (window->type == WIN_MUC
+            && num_args < 2
+            && g_strcmp0(cmd, "remove") == 0) {
         ProfMucWin *mucwin = (ProfMucWin*)window;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
         gboolean removed = bookmark_remove(mucwin->roomjid);
@@ -4423,6 +4517,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
 
     if (cmd == NULL) {
         cons_bad_cmd_usage(command);
+        cons_alert();
         return TRUE;
     }
 
@@ -4437,6 +4532,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
             cons_bad_cmd_usage(command);
             cons_show("");
         }
+        cons_alert();
         return TRUE;
     }
 
@@ -4451,11 +4547,13 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
     if (jid == NULL) {
         cons_bad_cmd_usage(command);
         cons_show("");
+        cons_alert();
         return TRUE;
     }
     if (strchr(jid, '@') == NULL) {
         cons_show("Invalid room, must be of the form room@domain.tld");
         cons_show("");
+        cons_alert();
         return TRUE;
     }
 
@@ -4466,6 +4564,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
         } else {
             cons_show("No bookmark exists for %s.", jid);
         }
+        cons_alert();
         return TRUE;
     }
 
@@ -4474,6 +4573,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
         if (!joined) {
             cons_show("No bookmark exists for %s.", jid);
         }
+        cons_alert();
         return TRUE;
     }
 
@@ -4484,6 +4584,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
     if (!parsed) {
         cons_bad_cmd_usage(command);
         cons_show("");
+        cons_alert();
         return TRUE;
     }
 
@@ -4493,6 +4594,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
         cons_bad_cmd_usage(command);
         cons_show("");
         options_destroy(options);
+        cons_alert();
         return TRUE;
     }
 
@@ -4507,6 +4609,7 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
             cons_show("Bookmark already exists, use /bookmark update to edit.");
         }
         options_destroy(options);
+        cons_alert();
         return TRUE;
     }
 
@@ -4518,11 +4621,13 @@ cmd_bookmark(ProfWin *window, const char *const command, gchar **args)
             cons_show("No bookmark exists for %s.", jid);
         }
         options_destroy(options);
+        cons_alert();
         return TRUE;
     }
 
     cons_bad_cmd_usage(command);
     options_destroy(options);
+    cons_alert();
 
     return TRUE;
 }
@@ -5665,6 +5770,177 @@ cmd_mainwin(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_statusbar(ProfWin *window, const char *const command, gchar **args)
 {
+    if (g_strcmp0(args[0], "show") == 0) {
+        if (g_strcmp0(args[1], "name") == 0) {
+            prefs_set_boolean(PREF_STATUSBAR_SHOW_NAME, TRUE);
+            cons_show("Enabled showing tab names.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "number") == 0) {
+            prefs_set_boolean(PREF_STATUSBAR_SHOW_NUMBER, TRUE);
+            cons_show("Enabled showing tab numbers.");
+            ui_resize();
+            return TRUE;
+        }
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "hide") == 0) {
+        if (g_strcmp0(args[1], "name") == 0) {
+            if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER) == FALSE) {
+                cons_show("Cannot disable both names and numbers in statusbar.");
+                cons_show("Use '/statusbar maxtabs 0' to hide tabs.");
+                return TRUE;
+            }
+            prefs_set_boolean(PREF_STATUSBAR_SHOW_NAME, FALSE);
+            cons_show("Disabled showing tab names.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "number") == 0) {
+            if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME) == FALSE) {
+                cons_show("Cannot disable both names and numbers in statusbar.");
+                cons_show("Use '/statusbar maxtabs 0' to hide tabs.");
+                return TRUE;
+            }
+            prefs_set_boolean(PREF_STATUSBAR_SHOW_NUMBER, FALSE);
+            cons_show("Disabled showing tab numbers.");
+            ui_resize();
+            return TRUE;
+        }
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "maxtabs") == 0) {
+        if (args[1] == NULL) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        char *value = args[1];
+        int intval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+        if (res) {
+            if (intval < 0 || intval > 10) {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
+
+            prefs_set_statusbartabs(intval);
+            if (intval == 0) {
+                cons_show("Status bar tabs disabled.");
+            } else {
+                cons_show("Status bar tabs set to %d.", intval);
+            }
+            ui_resize();
+            return TRUE;
+        } else {
+            cons_show(err_msg);
+            cons_bad_cmd_usage(command);
+            free(err_msg);
+            return TRUE;
+        }
+    }
+
+    if (g_strcmp0(args[0], "tablen") == 0) {
+        if (args[1] == NULL) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
+
+        char *value = args[1];
+        int intval = 0;
+        char *err_msg = NULL;
+        gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+        if (res) {
+            if (intval < 0) {
+                cons_bad_cmd_usage(command);
+                return TRUE;
+            }
+
+            prefs_set_statusbartablen(intval);
+            if (intval == 0) {
+                cons_show("Maximum tab length disabled.");
+            } else {
+                cons_show("Maximum tab length set to %d.", intval);
+            }
+            ui_resize();
+            return TRUE;
+        } else {
+            cons_show(err_msg);
+            cons_bad_cmd_usage(command);
+            free(err_msg);
+            return TRUE;
+        }
+    }
+
+    if (g_strcmp0(args[0], "self") == 0) {
+        if (g_strcmp0(args[1], "barejid") == 0) {
+            prefs_set_string(PREF_STATUSBAR_SELF, "barejid");
+            cons_show("Using barejid for statusbar title.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "fulljid") == 0) {
+            prefs_set_string(PREF_STATUSBAR_SELF, "fulljid");
+            cons_show("Using fulljid for statusbar title.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "user") == 0) {
+            prefs_set_string(PREF_STATUSBAR_SELF, "user");
+            cons_show("Using user for statusbar title.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "off") == 0) {
+            prefs_set_string(PREF_STATUSBAR_SELF, "off");
+            cons_show("Disabling statusbar title.");
+            ui_resize();
+            return TRUE;
+        }
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "chat") == 0) {
+        if (g_strcmp0(args[1], "jid") == 0) {
+            prefs_set_string(PREF_STATUSBAR_CHAT, "jid");
+            cons_show("Using jid for chat tabs.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "user") == 0) {
+            prefs_set_string(PREF_STATUSBAR_CHAT, "user");
+            cons_show("Using user for chat tabs.");
+            ui_resize();
+            return TRUE;
+        }
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (g_strcmp0(args[0], "room") == 0) {
+        if (g_strcmp0(args[1], "jid") == 0) {
+            prefs_set_string(PREF_STATUSBAR_ROOM, "jid");
+            cons_show("Using jid for room tabs.");
+            ui_resize();
+            return TRUE;
+        }
+        if (g_strcmp0(args[1], "room") == 0) {
+            prefs_set_string(PREF_STATUSBAR_ROOM, "room");
+            cons_show("Using room name for room tabs.");
+            ui_resize();
+            return TRUE;
+        }
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
     if (g_strcmp0(args[0], "up") == 0) {
         gboolean result = prefs_statusbar_pos_up();
         if (result) {
@@ -5863,6 +6139,16 @@ cmd_ping(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
+    if (args[0] == NULL && connection_supports(XMPP_FEATURE_PING) == FALSE) {
+        cons_show("Server does not support ping requests.");
+        return TRUE;
+    }
+
+    if (args[0] != NULL && caps_jid_has_feature(args[0], XMPP_FEATURE_PING) == FALSE) {
+        cons_show("%s does not support ping requests.", args[0]);
+        return TRUE;
+    }
+
     iq_send_ping(args[0]);
 
     if (args[0] == NULL) {
@@ -6451,11 +6737,11 @@ gboolean
 cmd_plugins_python_version(ProfWin *window, const char *const command, gchar **args)
 {
 #ifdef HAVE_PYTHON
-    const char *version = python_get_version();
+    const char *version = python_get_version_string();
     cons_show("Python version:");
     cons_show("%s", version);
 #else
-    cons_show("This build does not support pytyon plugins.");
+    cons_show("This build does not support python plugins.");
 #endif
     return TRUE;
 }
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index 2f0e0bac..0bbf338e 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -1,7 +1,7 @@
 /*
  * cmd_funcs.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -205,10 +205,8 @@ gboolean cmd_otr_answer(ProfWin *window, const char *const command, gchar **args
 
 gboolean cmd_wins(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_wins_unread(ProfWin *window, const char *const command, gchar **args);
-gboolean cmd_wins_tidy(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_wins_prune(ProfWin *window, const char *const command, gchar **args);
 gboolean cmd_wins_swap(ProfWin *window, const char *const command, gchar **args);
-gboolean cmd_wins_autotidy(ProfWin *window, const char *const command, gchar **args);
 
 gboolean cmd_form_field(ProfWin *window, char *tag, gchar **args);
 
diff --git a/src/common.c b/src/common.c
index d9c7d73f..a3893c06 100644
--- a/src/common.c
+++ b/src/common.c
@@ -1,7 +1,7 @@
 /*
  * common.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -467,11 +467,18 @@ prof_occurrences(const char *const needle, const char *const haystack, int offse
             gchar *needle_last_ch = g_utf8_offset_to_pointer(needle, g_utf8_strlen(needle, -1)- 1);
             int needle_last_ch_len = mblen(needle_last_ch, MB_CUR_MAX);
 
-            gchar *haystack_before_ch = g_utf8_prev_char(haystack_curr);
-            gchar *haystack_after_ch = g_utf8_next_char(haystack_curr + strlen(needle) - needle_last_ch_len);
+            gunichar before = 0;
+            gchar *haystack_before_ch = g_utf8_find_prev_char(haystack, haystack_curr);
+            if (haystack_before_ch) {
+                before = g_utf8_get_char(haystack_before_ch);
+            }
+
+            gunichar after = 0;
+            gchar *haystack_after_ch = g_utf8_find_next_char(haystack_curr + strlen(needle) - needle_last_ch_len, NULL);
+            if (haystack_after_ch) {
+                after = g_utf8_get_char(haystack_after_ch);
+            }
 
-            gunichar before = g_utf8_get_char(haystack_before_ch);
-            gunichar after = g_utf8_get_char(haystack_after_ch);
             if (!g_unichar_isalnum(before) && !g_unichar_isalnum(after)) {
                 *result = g_slist_append(*result, GINT_TO_POINTER(offset));
             }
diff --git a/src/common.h b/src/common.h
index 421c56cd..a53fdf9c 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,7 +1,7 @@
 /*
  * common.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/account.c b/src/config/account.c
index 8225b99d..705a5edf 100644
--- a/src/config/account.c
+++ b/src/config/account.c
@@ -1,7 +1,7 @@
 /*
  * account.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/account.h b/src/config/account.h
index 045732e0..1262e518 100644
--- a/src/config/account.h
+++ b/src/config/account.h
@@ -1,7 +1,7 @@
 /*
  * account.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/accounts.c b/src/config/accounts.c
index fb7b4a0e..de898dd7 100644
--- a/src/config/accounts.c
+++ b/src/config/accounts.c
@@ -1,7 +1,7 @@
 /*
  * accounts.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -291,7 +291,8 @@ accounts_get_account(const char *const name)
         gchar *tls_policy = g_key_file_get_string(accounts, name, "tls.policy", NULL);
         if (tls_policy && ((g_strcmp0(tls_policy, "force") != 0) &&
                 (g_strcmp0(tls_policy, "allow") != 0) &&
-                (g_strcmp0(tls_policy, "disable") != 0))) {
+                (g_strcmp0(tls_policy, "disable") != 0) &&
+                (g_strcmp0(tls_policy, "legacy") != 0))) {
             g_free(tls_policy);
             tls_policy = NULL;
         }
diff --git a/src/config/accounts.h b/src/config/accounts.h
index 51969ab8..c6a87c44 100644
--- a/src/config/accounts.h
+++ b/src/config/accounts.h
@@ -1,7 +1,7 @@
 /*
  * accounts.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/conflists.c b/src/config/conflists.c
index 36817005..0d368757 100644
--- a/src/config/conflists.c
+++ b/src/config/conflists.c
@@ -1,7 +1,7 @@
 /*
  * conflists.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/conflists.h b/src/config/conflists.h
index 3a5506a2..0e46fb00 100644
--- a/src/config/conflists.h
+++ b/src/config/conflists.h
@@ -1,7 +1,7 @@
 /*
  * conflists.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/files.c b/src/config/files.c
index 5322c49f..e8de2edb 100644
--- a/src/config/files.c
+++ b/src/config/files.c
@@ -1,7 +1,7 @@
 /*
  * files.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/files.h b/src/config/files.h
index 2ffff016..2eec9772 100644
--- a/src/config/files.h
+++ b/src/config/files.h
@@ -1,7 +1,7 @@
 /*
  * files.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/preferences.c b/src/config/preferences.c
index b86f710a..e62c552c 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -1,7 +1,7 @@
 /*
  * preferences.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -663,6 +663,40 @@ prefs_get_tray_timer(void)
     }
 }
 
+gint
+prefs_get_statusbartabs(void)
+{
+    if (!g_key_file_has_key(prefs, PREF_GROUP_UI, "statusbar.tabs", NULL)) {
+        return 10;
+    } else {
+        return g_key_file_get_integer(prefs, PREF_GROUP_UI, "statusbar.tabs", NULL);
+    }
+}
+
+void
+prefs_set_statusbartabs(gint value)
+{
+    g_key_file_set_integer(prefs, PREF_GROUP_UI, "statusbar.tabs", value);
+    _save_prefs();
+}
+
+gint
+prefs_get_statusbartablen(void)
+{
+    if (!g_key_file_has_key(prefs, PREF_GROUP_UI, "statusbar.tablen", NULL)) {
+        return 0;
+    } else {
+        return g_key_file_get_integer(prefs, PREF_GROUP_UI, "statusbar.tablen", NULL);
+    }
+}
+
+void
+prefs_set_statusbartablen(gint value)
+{
+    g_key_file_set_integer(prefs, PREF_GROUP_UI, "statusbar.tablen", value);
+    _save_prefs();
+}
+
 gchar**
 prefs_get_plugins(void)
 {
@@ -1530,7 +1564,6 @@ _get_group(preference_t pref)
         case PREF_MUC_PRIVILEGES:
         case PREF_PRESENCE:
         case PREF_WRAP:
-        case PREF_WINS_AUTO_TIDY:
         case PREF_TIME_CONSOLE:
         case PREF_TIME_CHAT:
         case PREF_TIME_MUC:
@@ -1560,6 +1593,7 @@ _get_group(preference_t pref)
         case PREF_ROSTER_ROOMS_BY:
         case PREF_ROSTER_ROOMS_ORDER:
         case PREF_ROSTER_ROOMS_UNREAD:
+        case PREF_ROSTER_ROOMS_SERVER:
         case PREF_ROSTER_PRIVATE:
         case PREF_RESOURCE_TITLE:
         case PREF_RESOURCE_MESSAGE:
@@ -1569,6 +1603,11 @@ _get_group(preference_t pref)
         case PREF_CONSOLE_MUC:
         case PREF_CONSOLE_PRIVATE:
         case PREF_CONSOLE_CHAT:
+        case PREF_STATUSBAR_SHOW_NAME:
+        case PREF_STATUSBAR_SHOW_NUMBER:
+        case PREF_STATUSBAR_SELF:
+        case PREF_STATUSBAR_CHAT:
+        case PREF_STATUSBAR_ROOM:
             return PREF_GROUP_UI;
         case PREF_STATES:
         case PREF_OUTTYPE:
@@ -1614,6 +1653,7 @@ _get_group(preference_t pref)
         case PREF_PGP_LOG:
             return PREF_GROUP_PGP;
         case PREF_BOOKMARK_INVITE:
+        case PREF_ROOM_LIST_CACHE:
             return PREF_GROUP_MUC;
         case PREF_PLUGINS_SOURCEPATH:
             return PREF_GROUP_PLUGINS;
@@ -1731,8 +1771,6 @@ _get_key(preference_t pref)
             return "presence";
         case PREF_WRAP:
             return "wrap";
-        case PREF_WINS_AUTO_TIDY:
-            return "wins.autotidy";
         case PREF_TIME_CONSOLE:
             return "time.console";
         case PREF_TIME_CHAT:
@@ -1791,6 +1829,8 @@ _get_key(preference_t pref)
             return "roster.rooms.order";
         case PREF_ROSTER_ROOMS_UNREAD:
             return "roster.rooms.unread";
+        case PREF_ROSTER_ROOMS_SERVER:
+            return "roster.rooms.server";
         case PREF_ROSTER_PRIVATE:
             return "roster.private";
         case PREF_RESOURCE_TITLE:
@@ -1819,6 +1859,18 @@ _get_key(preference_t pref)
             return "bookmark.invite";
         case PREF_PLUGINS_SOURCEPATH:
             return "sourcepath";
+        case PREF_ROOM_LIST_CACHE:
+            return "rooms.cache";
+        case PREF_STATUSBAR_SHOW_NAME:
+            return "statusbar.show.name";
+        case PREF_STATUSBAR_SHOW_NUMBER:
+            return "statusbar.show.number";
+        case PREF_STATUSBAR_SELF:
+            return "statusbar.self";
+        case PREF_STATUSBAR_CHAT:
+            return "statusbar.chat";
+        case PREF_STATUSBAR_ROOM:
+            return "statusbar.room";
         default:
             return NULL;
     }
@@ -1848,7 +1900,6 @@ _get_default_boolean(preference_t pref)
         case PREF_MUC_PRIVILEGES:
         case PREF_PRESENCE:
         case PREF_WRAP:
-        case PREF_WINS_AUTO_TIDY:
         case PREF_INPBLOCK_DYNAMIC:
         case PREF_RESOURCE_TITLE:
         case PREF_RESOURCE_MESSAGE:
@@ -1861,11 +1912,14 @@ _get_default_boolean(preference_t pref)
         case PREF_ROSTER_CONTACTS:
         case PREF_ROSTER_UNSUBSCRIBED:
         case PREF_ROSTER_ROOMS:
+        case PREF_ROSTER_ROOMS_SERVER:
         case PREF_TLS_SHOW:
         case PREF_LASTACTIVITY:
         case PREF_NOTIFY_MENTION_WHOLE_WORD:
         case PREF_TRAY_READ:
         case PREF_BOOKMARK_INVITE:
+        case PREF_ROOM_LIST_CACHE:
+        case PREF_STATUSBAR_SHOW_NUMBER:
             return TRUE;
         default:
             return FALSE;
@@ -1929,6 +1983,12 @@ _get_default_string(preference_t pref)
         case PREF_CONSOLE_PRIVATE:
         case PREF_CONSOLE_CHAT:
             return "all";
+        case PREF_STATUSBAR_SELF:
+            return "fulljid";
+        case PREF_STATUSBAR_CHAT:
+            return "user";
+        case PREF_STATUSBAR_ROOM:
+            return "room";
         default:
             return NULL;
     }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 134fba49..bafe4a1f 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -1,7 +1,7 @@
 /*
  * preferences.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -84,11 +84,11 @@ typedef enum {
     PREF_ROSTER_ROOMS_BY,
     PREF_ROSTER_ROOMS_ORDER,
     PREF_ROSTER_ROOMS_UNREAD,
+    PREF_ROSTER_ROOMS_SERVER,
     PREF_ROSTER_PRIVATE,
     PREF_MUC_PRIVILEGES,
     PREF_PRESENCE,
     PREF_WRAP,
-    PREF_WINS_AUTO_TIDY,
     PREF_TIME_CONSOLE,
     PREF_TIME_CHAT,
     PREF_TIME_MUC,
@@ -142,6 +142,12 @@ typedef enum {
     PREF_CONSOLE_CHAT,
     PREF_BOOKMARK_INVITE,
     PREF_PLUGINS_SOURCEPATH,
+    PREF_ROOM_LIST_CACHE,
+    PREF_STATUSBAR_SHOW_NAME,
+    PREF_STATUSBAR_SHOW_NUMBER,
+    PREF_STATUSBAR_SELF,
+    PREF_STATUSBAR_CHAT,
+    PREF_STATUSBAR_ROOM,
 } preference_t;
 
 typedef struct prof_alias_t {
@@ -186,6 +192,11 @@ gint prefs_get_autoping_timeout(void);
 gint prefs_get_inpblock(void);
 void prefs_set_inpblock(gint value);
 
+void prefs_set_statusbartabs(gint value);
+gint prefs_get_statusbartabs(void);
+void prefs_set_statusbartablen(gint value);
+gint prefs_get_statusbartablen(void);
+
 void prefs_set_occupants_size(gint value);
 gint prefs_get_occupants_size(void);
 void prefs_set_roster_size(gint value);
diff --git a/src/config/scripts.c b/src/config/scripts.c
index d093e8d9..5980dcaa 100644
--- a/src/config/scripts.c
+++ b/src/config/scripts.c
@@ -1,7 +1,7 @@
 /*
  * scripts.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/scripts.h b/src/config/scripts.h
index d7652138..508a43c2 100644
--- a/src/config/scripts.h
+++ b/src/config/scripts.h
@@ -1,7 +1,7 @@
 /*
  * scripts.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/theme.c b/src/config/theme.c
index 5a44e370..13f9cc2b 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -1,7 +1,7 @@
 /*
  * theme.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -384,7 +384,6 @@ _load_preferences(void)
     _set_boolean_preference("flash", PREF_FLASH);
     _set_boolean_preference("splash", PREF_SPLASH);
     _set_boolean_preference("wrap", PREF_WRAP);
-    _set_boolean_preference("wins.autotidy", PREF_WINS_AUTO_TIDY);
     _set_boolean_preference("resource.title", PREF_RESOURCE_TITLE);
     _set_boolean_preference("resource.message", PREF_RESOURCE_MESSAGE);
     _set_boolean_preference("occupants", PREF_OCCUPANTS);
@@ -402,11 +401,14 @@ _load_preferences(void)
     _set_boolean_preference("roster.contacts", PREF_ROSTER_CONTACTS);
     _set_boolean_preference("roster.unsubscribed", PREF_ROSTER_UNSUBSCRIBED);
     _set_boolean_preference("roster.rooms", PREF_ROSTER_ROOMS);
+    _set_boolean_preference("roster.rooms.server", PREF_ROSTER_ROOMS_SERVER);
     _set_boolean_preference("privileges", PREF_MUC_PRIVILEGES);
     _set_boolean_preference("presence", PREF_PRESENCE);
     _set_boolean_preference("intype", PREF_INTYPE);
     _set_boolean_preference("enc.warn", PREF_ENC_WARN);
     _set_boolean_preference("tls.show", PREF_TLS_SHOW);
+    _set_boolean_preference("statusbar.show.name", PREF_STATUSBAR_SHOW_NAME);
+    _set_boolean_preference("statusbar.show.nuumber", PREF_STATUSBAR_SHOW_NUMBER);
 
     _set_string_preference("time.console", PREF_TIME_CONSOLE);
     _set_string_preference("time.chat", PREF_TIME_CHAT);
@@ -431,6 +433,19 @@ _load_preferences(void)
     _set_string_preference("roster.rooms.by", PREF_ROSTER_ROOMS_BY);
     _set_string_preference("roster.private", PREF_ROSTER_PRIVATE);
     _set_string_preference("roster.count", PREF_ROSTER_COUNT);
+    _set_string_preference("statusbar.self", PREF_STATUSBAR_SELF);
+    _set_string_preference("statusbar.chat", PREF_STATUSBAR_CHAT);
+    _set_string_preference("statusbar.room", PREF_STATUSBAR_ROOM);
+
+    if (g_key_file_has_key(theme, "ui", "statusbar.tabs", NULL)) {
+        gint tabs_size = g_key_file_get_integer(theme, "ui", "statusbar.tabs", NULL);
+        prefs_set_statusbartabs(tabs_size);
+    }
+
+    if (g_key_file_has_key(theme, "ui", "statusbar.tablen", NULL)) {
+        gint tab_len = g_key_file_get_integer(theme, "ui", "statusbar.tablen", NULL);
+        prefs_set_statusbartablen(tab_len);
+    }
 
     if (g_key_file_has_key(theme, "ui", "occupants.size", NULL)) {
         gint occupants_size = g_key_file_get_integer(theme, "ui", "occupants.size", NULL);
diff --git a/src/config/theme.h b/src/config/theme.h
index 747eace1..c1d9870e 100644
--- a/src/config/theme.h
+++ b/src/config/theme.h
@@ -1,7 +1,7 @@
 /*
  * theme.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/tlscerts.c b/src/config/tlscerts.c
index 39733405..23f6575c 100644
--- a/src/config/tlscerts.c
+++ b/src/config/tlscerts.c
@@ -1,7 +1,7 @@
 /*
  * tlscerts.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/config/tlscerts.h b/src/config/tlscerts.h
index e1a1758f..2fd568ad 100644
--- a/src/config/tlscerts.h
+++ b/src/config/tlscerts.h
@@ -1,7 +1,7 @@
 /*
  * tlscerts.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/event/client_events.c b/src/event/client_events.c
index 2ed4ca8e..94f75ba5 100644
--- a/src/event/client_events.c
+++ b/src/event/client_events.c
@@ -1,7 +1,7 @@
 /*
  * client_events.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -88,6 +88,7 @@ cl_ev_disconnect(void)
     session_disconnect();
     roster_destroy();
     muc_invites_clear();
+    muc_confserver_clear();
     chat_sessions_clear();
     tlscerts_clear_current();
 #ifdef HAVE_LIBGPGME
diff --git a/src/event/client_events.h b/src/event/client_events.h
index 1c9733d2..098f2648 100644
--- a/src/event/client_events.h
+++ b/src/event/client_events.h
@@ -1,7 +1,7 @@
 /*
  * client_events.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/event/server_events.c b/src/event/server_events.c
index 57a681cc..5117d1f8 100644
--- a/src/event/server_events.c
+++ b/src/event/server_events.c
@@ -1,7 +1,7 @@
 /*
  * server_events.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -181,6 +181,7 @@ sv_ev_lost_connection(void)
 #endif
 
     muc_invites_clear();
+    muc_confserver_clear();
     chat_sessions_clear();
     ui_disconnected();
     roster_destroy();
@@ -281,7 +282,7 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
     // currently in groupchat window
     if (wins_is_current(window)) {
         is_current = TRUE;
-        status_bar_active(num);
+        status_bar_active(num, WIN_MUC, mucwin->roomjid);
 
         if ((g_strcmp0(mynick, nick) != 0) && (prefs_get_boolean(PREF_BEEP))) {
             beep();
@@ -289,7 +290,7 @@ sv_ev_room_message(const char *const room_jid, const char *const nick, const cha
 
     // not currently on groupchat window
     } else {
-        status_bar_new(num);
+        status_bar_new(num, WIN_MUC, mucwin->roomjid);
 
         if ((g_strcmp0(mynick, nick) != 0) && (prefs_get_boolean(PREF_FLASH))) {
             flash();
@@ -797,6 +798,12 @@ sv_ev_muc_self_online(const char *const room, const char *const nick, gboolean c
             ui_room_join(room, TRUE);
         }
 
+        Jid *jidp = jid_create(room);
+        if (jidp->domainpart) {
+            muc_confserver_add(jidp->domainpart);
+        }
+        jid_destroy(jidp);
+
         iq_room_info_request(room, FALSE);
 
         if (muc_invites_contain(room)) {
diff --git a/src/event/server_events.h b/src/event/server_events.h
index 75bb4379..bbbee8a2 100644
--- a/src/event/server_events.h
+++ b/src/event/server_events.h
@@ -1,7 +1,7 @@
 /*
  * server_events.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/log.c b/src/log.c
index 572999d6..886e6aac 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,7 +1,7 @@
 /*
  * log.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -228,7 +228,7 @@ _rotate_log_file(void)
     size_t len = strlen(log_file);
     char *log_file_new = malloc(len + 3);
 
-    strncpy(log_file_new, log_file, len);
+    memcpy(log_file_new, log_file, len);
     log_file_new[len] = '.';
     log_file_new[len+1] = '1';
     log_file_new[len+2] = 0;
@@ -360,7 +360,12 @@ _chat_log_chat(const char *const login, const char *const other, const char *con
         dated_log = _create_log(other, login);
         g_hash_table_insert(logs, strdup(other), dated_log);
 
-    // log exists but needs rolling
+    // log entry exists but file removed
+    } else if (!g_file_test(dated_log->filename, G_FILE_TEST_EXISTS)) {
+        dated_log = _create_log(other, login);
+        g_hash_table_replace(logs, strdup(other), dated_log);
+
+    // log file needs rolling
     } else if (_log_roll_needed(dated_log)) {
         dated_log = _create_log(other, login);
         g_hash_table_replace(logs, strdup(other), dated_log);
diff --git a/src/log.h b/src/log.h
index 5b25d349..a4c44ef6 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,7 +1,7 @@
 /*
  * log.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/main.c b/src/main.c
index a473ac42..91e17a9c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,7 +1,7 @@
 /*
  * main.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -41,6 +41,18 @@
 #include "gitversion.h"
 #endif
 
+#ifdef HAVE_LIBOTR
+#include "otr/otr.h"
+#endif
+
+#ifdef HAVE_LIBGPGME
+#include "pgp/gpg.h"
+#endif
+
+#ifdef HAVE_PYTHON
+#include "plugins/python_plugins.h"
+#endif
+
 #include "profanity.h"
 #include "common.h"
 #include "command/cmd_defs.h"
@@ -90,7 +102,7 @@ main(int argc, char **argv)
             g_print("Profanity, version %s\n", PACKAGE_VERSION);
         }
 
-        g_print("Copyright (C) 2012 - 2017 James Booth <%s>.\n", PACKAGE_BUGREPORT);
+        g_print("Copyright (C) 2012 - 2018 James Booth <%s>.\n", PACKAGE_BUGREPORT);
         g_print("License GPLv3+: GNU GPL version 3 or later <https://www.gnu.org/licenses/gpl.html>\n");
         g_print("\n");
         g_print("This is free software; you are free to change and redistribute it.\n");
@@ -113,13 +125,15 @@ main(int argc, char **argv)
         }
 
 #ifdef HAVE_LIBOTR
-        g_print("OTR support: Enabled\n");
+        char *otr_version = otr_libotr_version();
+        g_print("OTR support: Enabled (libotr %s)\n", otr_version);
 #else
         g_print("OTR support: Disabled\n");
 #endif
 
 #ifdef HAVE_LIBGPGME
-        g_print("PGP support: Enabled\n");
+        const char *pgp_version = p_gpg_libver();
+        g_print("PGP support: Enabled (libgpgme %s)\n", pgp_version);
 #else
         g_print("PGP support: Disabled\n");
 #endif
@@ -131,7 +145,9 @@ main(int argc, char **argv)
 #endif
 
 #ifdef HAVE_PYTHON
-        g_print("Python plugins: Enabled\n");
+        gchar *python_version = python_get_version_number();
+        g_print("Python plugins: Enabled (%s)\n", python_version);
+        g_free(python_version);
 #else
         g_print("Python plugins: Disabled\n");
 #endif
diff --git a/src/otr/otr.c b/src/otr/otr.c
index 65948493..e84f0ca2 100644
--- a/src/otr/otr.c
+++ b/src/otr/otr.c
@@ -1,7 +1,7 @@
 /*
  * otr.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/otr/otr.h b/src/otr/otr.h
index 067a6fdb..1ac99a75 100644
--- a/src/otr/otr.h
+++ b/src/otr/otr.h
@@ -1,7 +1,7 @@
 /*
  * otr.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/otr/otrlib.h b/src/otr/otrlib.h
index 99c1d1e2..ba1440c3 100644
--- a/src/otr/otrlib.h
+++ b/src/otr/otrlib.h
@@ -1,7 +1,7 @@
 /*
  * otrlib.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/otr/otrlibv3.c b/src/otr/otrlibv3.c
index 04fdfb19..c1328ecf 100644
--- a/src/otr/otrlibv3.c
+++ b/src/otr/otrlibv3.c
@@ -1,7 +1,7 @@
 /*
  * otrlibv3.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/otr/otrlibv4.c b/src/otr/otrlibv4.c
index 39c254dd..dfcb4529 100644
--- a/src/otr/otrlibv4.c
+++ b/src/otr/otrlibv4.c
@@ -1,7 +1,7 @@
 /*
  * otrlibv4.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/pgp/gpg.c b/src/pgp/gpg.c
index 051f99f0..bfebe8bd 100644
--- a/src/pgp/gpg.c
+++ b/src/pgp/gpg.c
@@ -1,7 +1,7 @@
 /*
  * gpg.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -56,7 +56,7 @@
 #define PGP_MESSAGE_HEADER "-----BEGIN PGP MESSAGE-----"
 #define PGP_MESSAGE_FOOTER "-----END PGP MESSAGE-----"
 
-static const char *libversion;
+static const char *libversion = NULL;
 static GHashTable *pubkeys;
 
 static gchar *pubsloc;
@@ -420,6 +420,9 @@ p_gpg_pubkeys(void)
 const char*
 p_gpg_libver(void)
 {
+    if (libversion == NULL) {
+        libversion = gpgme_check_version(NULL);
+    }
     return libversion;
 }
 
diff --git a/src/pgp/gpg.h b/src/pgp/gpg.h
index 8377b05d..4a7d27b1 100644
--- a/src/pgp/gpg.h
+++ b/src/pgp/gpg.h
@@ -1,7 +1,7 @@
 /*
  * gpg.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/api.c b/src/plugins/api.c
index 76751957..d30914dc 100644
--- a/src/plugins/api.c
+++ b/src/plugins/api.c
@@ -1,7 +1,7 @@
 /*
  * api.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -333,7 +333,7 @@ api_win_create(
     // set status bar active
     ProfPluginWin *pluginwin = wins_get_plugin(tag);
     int num = wins_get_num((ProfWin*)pluginwin);
-    status_bar_active(num);
+    status_bar_active(num, WIN_PLUGIN, pluginwin->tag);
 }
 
 int
diff --git a/src/plugins/api.h b/src/plugins/api.h
index 51a5ac0f..0f4881f0 100644
--- a/src/plugins/api.h
+++ b/src/plugins/api.h
@@ -1,7 +1,7 @@
 /*
  * api.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/autocompleters.c b/src/plugins/autocompleters.c
index 20abaddf..040fcf4d 100644
--- a/src/plugins/autocompleters.c
+++ b/src/plugins/autocompleters.c
@@ -1,7 +1,7 @@
 /*
  * autocompleters.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/autocompleters.h b/src/plugins/autocompleters.h
index 37539e27..edb076f4 100644
--- a/src/plugins/autocompleters.h
+++ b/src/plugins/autocompleters.h
@@ -1,7 +1,7 @@
 /*
  * autocompleters.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/c_api.c b/src/plugins/c_api.c
index 9d40b7c7..f3679d8f 100644
--- a/src/plugins/c_api.c
+++ b/src/plugins/c_api.c
@@ -1,7 +1,7 @@
 /*
  * c_api.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/c_api.h b/src/plugins/c_api.h
index 25a56555..8416ca20 100644
--- a/src/plugins/c_api.h
+++ b/src/plugins/c_api.h
@@ -1,7 +1,7 @@
 /*
  * c_api.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/c_plugins.c b/src/plugins/c_plugins.c
index c97c4fee..44010e95 100644
--- a/src/plugins/c_plugins.c
+++ b/src/plugins/c_plugins.c
@@ -1,7 +1,7 @@
 /*
  * c_plugins.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/c_plugins.h b/src/plugins/c_plugins.h
index a7d5f879..4098170a 100644
--- a/src/plugins/c_plugins.h
+++ b/src/plugins/c_plugins.h
@@ -1,7 +1,7 @@
 /*
  * c_plugins.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/callbacks.c b/src/plugins/callbacks.c
index 102fd679..d197a44b 100644
--- a/src/plugins/callbacks.c
+++ b/src/plugins/callbacks.c
@@ -1,7 +1,7 @@
 /*
  * callbacks.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/callbacks.h b/src/plugins/callbacks.h
index 40f0a064..23edd8eb 100644
--- a/src/plugins/callbacks.h
+++ b/src/plugins/callbacks.h
@@ -1,7 +1,7 @@
 /*
  * callbacks.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/disco.c b/src/plugins/disco.c
index a03d651f..a2889f9a 100644
--- a/src/plugins/disco.c
+++ b/src/plugins/disco.c
@@ -1,7 +1,7 @@
 /*
  * disco.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/disco.h b/src/plugins/disco.h
index 71912253..07c6a946 100644
--- a/src/plugins/disco.h
+++ b/src/plugins/disco.h
@@ -1,7 +1,7 @@
 /*
  * disco.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/plugins.c b/src/plugins/plugins.c
index 06b1417f..ced170fe 100644
--- a/src/plugins/plugins.c
+++ b/src/plugins/plugins.c
@@ -1,7 +1,7 @@
 /*
  * plugins.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/plugins.h b/src/plugins/plugins.h
index 0aa490f0..16d6874a 100644
--- a/src/plugins/plugins.h
+++ b/src/plugins/plugins.h
@@ -1,7 +1,7 @@
 /*
  * plugins.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/profapi.c b/src/plugins/profapi.c
index 782487cd..11b9d154 100644
--- a/src/plugins/profapi.c
+++ b/src/plugins/profapi.c
@@ -1,7 +1,7 @@
 /*
  * prof_api.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/profapi.h b/src/plugins/profapi.h
index 60f5f463..48d9f189 100644
--- a/src/plugins/profapi.h
+++ b/src/plugins/profapi.h
@@ -1,7 +1,7 @@
 /*
  * prof_api.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/python_api.c b/src/plugins/python_api.c
index aa707e06..086ccbfb 100644
--- a/src/plugins/python_api.c
+++ b/src/plugins/python_api.c
@@ -1,7 +1,7 @@
 /*
  * python_api.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/python_api.h b/src/plugins/python_api.h
index a5848997..8133c853 100644
--- a/src/plugins/python_api.h
+++ b/src/plugins/python_api.h
@@ -1,7 +1,7 @@
 /*
  * python_api.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/python_plugins.c b/src/plugins/python_plugins.c
index f8f35628..6f7c2158 100644
--- a/src/plugins/python_plugins.c
+++ b/src/plugins/python_plugins.c
@@ -1,7 +1,7 @@
 /*
  * python_plugins.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -74,11 +74,22 @@ _unref_module(PyObject *module)
 }
 
 const char*
-python_get_version(void)
+python_get_version_string(void)
 {
     return Py_GetVersion();
 }
 
+gchar*
+python_get_version_number(void)
+{
+    const char *version_str = Py_GetVersion();
+    gchar **split = g_strsplit(version_str, " ", 0);
+    gchar *version_number = g_strdup(split[0]);
+    g_strfreev(split);
+
+    return version_number;
+}
+
 void
 python_env_init(void)
 {
diff --git a/src/plugins/python_plugins.h b/src/plugins/python_plugins.h
index 778846b2..587d13d7 100644
--- a/src/plugins/python_plugins.h
+++ b/src/plugins/python_plugins.h
@@ -1,7 +1,7 @@
 /*
  * python_plugins.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -43,7 +43,8 @@ void python_check_error(void);
 void allow_python_threads();
 void disable_python_threads();
 
-const char* python_get_version(void);
+const char* python_get_version_string(void);
+gchar* python_get_version_number(void);
 
 void python_init_hook(ProfPlugin *plugin, const char *const version, const char *const status,
     const char *const account_name, const char *const fulljid);
diff --git a/src/plugins/settings.c b/src/plugins/settings.c
index 78d8cec0..e4286f22 100644
--- a/src/plugins/settings.c
+++ b/src/plugins/settings.c
@@ -1,7 +1,7 @@
 /*
  * settings.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/settings.h b/src/plugins/settings.h
index d2601fe8..3a81b2d1 100644
--- a/src/plugins/settings.h
+++ b/src/plugins/settings.h
@@ -1,7 +1,7 @@
 /*
  * settings.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/themes.c b/src/plugins/themes.c
index 0c5d33ee..85c26290 100644
--- a/src/plugins/themes.c
+++ b/src/plugins/themes.c
@@ -1,7 +1,7 @@
 /*
  * themes.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/plugins/themes.h b/src/plugins/themes.h
index 9bd6ee4a..4feb9f2e 100644
--- a/src/plugins/themes.h
+++ b/src/plugins/themes.h
@@ -1,7 +1,7 @@
 /*
  * themes.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/profanity.c b/src/profanity.c
index ace3fb2b..25bb5b42 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -1,7 +1,7 @@
 /*
  * profanity.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/profanity.h b/src/profanity.h
index 23dabdf6..378eeddb 100644
--- a/src/profanity.h
+++ b/src/profanity.h
@@ -1,7 +1,7 @@
 /*
  * profanity.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/autocomplete.c b/src/tools/autocomplete.c
index c4a843ba..00f29fe0 100644
--- a/src/tools/autocomplete.c
+++ b/src/tools/autocomplete.c
@@ -1,7 +1,7 @@
 /*
  * autocomplete.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/autocomplete.h b/src/tools/autocomplete.h
index f62309ba..64141b35 100644
--- a/src/tools/autocomplete.h
+++ b/src/tools/autocomplete.h
@@ -1,7 +1,7 @@
 /*
  * autocomplete.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c
index 6e5dd27c..fc4c60f3 100644
--- a/src/tools/http_upload.c
+++ b/src/tools/http_upload.c
@@ -1,7 +1,7 @@
 /*
  * http_upload.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/http_upload.h b/src/tools/http_upload.h
index ae8f8223..145207a4 100644
--- a/src/tools/http_upload.h
+++ b/src/tools/http_upload.h
@@ -1,7 +1,7 @@
 /*
  * http_upload.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/parser.c b/src/tools/parser.c
index 1e960235..74fef1e2 100644
--- a/src/tools/parser.c
+++ b/src/tools/parser.c
@@ -1,7 +1,7 @@
 /*
  * parser.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/parser.h b/src/tools/parser.h
index a60ee823..b099ce24 100644
--- a/src/tools/parser.h
+++ b/src/tools/parser.h
@@ -1,7 +1,7 @@
 /*
  * parser.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/tinyurl.c b/src/tools/tinyurl.c
index 1b94a936..36be4971 100644
--- a/src/tools/tinyurl.c
+++ b/src/tools/tinyurl.c
@@ -1,7 +1,7 @@
 /*
  * tinyurl.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/tools/tinyurl.h b/src/tools/tinyurl.h
index 9e99539b..37e849c4 100644
--- a/src/tools/tinyurl.h
+++ b/src/tools/tinyurl.h
@@ -1,7 +1,7 @@
 /*
  * tinyurl.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/buffer.c b/src/ui/buffer.c
index 102ba4b2..2d29ca65 100644
--- a/src/ui/buffer.c
+++ b/src/ui/buffer.c
@@ -1,7 +1,7 @@
 /*
  * buffer.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/buffer.h b/src/ui/buffer.h
index 8a14dd51..b16bcb51 100644
--- a/src/ui/buffer.h
+++ b/src/ui/buffer.h
@@ -1,7 +1,7 @@
 /*
  * buffer.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/chatwin.c b/src/ui/chatwin.c
index 3ca44dd3..31604db4 100644
--- a/src/ui/chatwin.c
+++ b/src/ui/chatwin.c
@@ -1,7 +1,7 @@
 /*
  * chatwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -105,7 +105,7 @@ chatwin_otr_secured(ProfChatWin *chatwin, gboolean trusted)
          title_bar_switch();
     } else {
         int num = wins_get_num(window);
-        status_bar_new(num);
+        status_bar_new(num, WIN_CHAT, chatwin->barejid);
 
         int ui_index = num;
         if (ui_index == 10) {
@@ -249,11 +249,11 @@ chatwin_incoming_msg(ProfChatWin *chatwin, const char *const resource, const cha
     if (wins_is_current(window)) {
         win_print_incoming(window, timestamp, display_name, plugin_message, enc_mode);
         title_bar_set_typing(FALSE);
-        status_bar_active(num);
+        status_bar_active(num, WIN_CHAT, chatwin->barejid);
 
     // not currently viewing chat window with sender
     } else {
-        status_bar_new(num);
+        status_bar_new(num, WIN_CHAT, chatwin->barejid);
         cons_show_incoming_message(display_name, num, chatwin->unread);
 
         if (prefs_get_boolean(PREF_FLASH)) {
@@ -324,9 +324,11 @@ chatwin_outgoing_carbon(ProfChatWin *chatwin, const char *const message, prof_en
         enc_char = prefs_get_pgp_char();
     }
 
-    win_print_outgoing((ProfWin*)chatwin, enc_char, "%s", message);
-    int num = wins_get_num((ProfWin*)chatwin);
-    status_bar_active(num);
+    ProfWin *window = (ProfWin*)chatwin;
+
+    win_print_outgoing(window, enc_char, "%s", message);
+    int num = wins_get_num(window);
+    status_bar_active(num, WIN_CHAT, chatwin->barejid);
 }
 
 void
diff --git a/src/ui/console.c b/src/ui/console.c
index cb5a10b0..9bead705 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1,7 +1,7 @@
 /*
  * console.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -412,7 +412,7 @@ cons_about(void)
         }
     }
 
-    win_println(console, THEME_DEFAULT, '-', "Copyright (C) 2012 - 2017 James Booth <%s>.", PACKAGE_BUGREPORT);
+    win_println(console, THEME_DEFAULT, '-', "Copyright (C) 2012 - 2018 James Booth <%s>.", PACKAGE_BUGREPORT);
     win_println(console, THEME_DEFAULT, '-', "License GPLv3+: GNU GPL version 3 or later <https://www.gnu.org/licenses/gpl.html>");
     win_println(console, THEME_DEFAULT, '-', "");
     win_println(console, THEME_DEFAULT, '-', "This is free software; you are free to change and redistribute it.");
@@ -684,8 +684,9 @@ cons_show_bookmarks(const GList *list)
             Bookmark *item = list->data;
 
             theme_item_t presence_colour = THEME_TEXT;
+            ProfWin *roomwin = (ProfWin*)wins_get_muc(item->barejid);
 
-            if (muc_active(item->barejid)) {
+            if (muc_active(item->barejid) && roomwin) {
                 presence_colour = THEME_ONLINE;
             }
             win_print(console, presence_colour, '-', "  %s", item->barejid);
@@ -698,12 +699,9 @@ cons_show_bookmarks(const GList *list)
             if (item->password) {
                 win_append(console, presence_colour, " (private)");
             }
-            if (muc_active(item->barejid)) {
-                ProfWin *roomwin = (ProfWin*)wins_get_muc(item->barejid);
-                if (roomwin) {
-                    int num = wins_get_num(roomwin);
-                    win_append(console, presence_colour, " (win %d)", num);
-                }
+            if (muc_active(item->barejid) && roomwin) {
+                int num = wins_get_num(roomwin);
+                win_append(console, presence_colour, " (win %d)", num);
             }
             win_newline(console);
             list = g_list_next(list);
@@ -1126,15 +1124,6 @@ cons_wrap_setting(void)
 }
 
 void
-cons_winstidy_setting(void)
-{
-    if (prefs_get_boolean(PREF_WINS_AUTO_TIDY))
-        cons_show("Window Auto Tidy (/wins)            : ON");
-    else
-        cons_show("Window Auto Tidy (/wins)            : OFF");
-}
-
-void
 cons_encwarn_setting(void)
 {
     if (prefs_get_boolean(PREF_ENC_WARN)) {
@@ -1248,6 +1237,16 @@ cons_occupants_setting(void)
 }
 
 void
+cons_rooms_cache_setting(void)
+{
+    if (prefs_get_boolean(PREF_ROOM_LIST_CACHE)) {
+        cons_show("Room list cache (/rooms cache)  : ON");
+    } else {
+        cons_show("Room list cache (/rooms cache)  : OFF");
+    }
+}
+
+void
 cons_autoconnect_setting(void)
 {
     char *pref_connect_account = prefs_get_string(PREF_CONNECT_ACCOUNT);
@@ -1523,7 +1522,6 @@ cons_show_ui_prefs(void)
     cons_splash_setting();
     cons_winpos_setting();
     cons_wrap_setting();
-    cons_winstidy_setting();
     cons_time_setting();
     cons_resource_setting();
     cons_vercheck_setting();
@@ -1536,6 +1534,7 @@ cons_show_ui_prefs(void)
     cons_presence_setting();
     cons_inpblock_setting();
     cons_tlsshow_setting();
+    cons_statusbar_setting();
 
     cons_alert();
 }
@@ -1740,6 +1739,46 @@ cons_inpblock_setting(void)
 }
 
 void
+cons_statusbar_setting(void)
+{
+    if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME)) {
+        cons_show("Show tab names (/statusbar)         : ON");
+    } else {
+        cons_show("Show tab names (/statusbar)         : OFF");
+    }
+    if (prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER)) {
+        cons_show("Show tab numbers (/statusbar)       : ON");
+    } else {
+        cons_show("Show tab numbers (/statusbar)       : OFF");
+    }
+
+    cons_show("Max tabs (/statusbar)               : %d", prefs_get_statusbartabs());
+
+    gint pref_len = prefs_get_statusbartablen();
+    if (pref_len == 0) {
+        cons_show("Max tab length (/statusbar)         : OFF");
+    } else {
+        cons_show("Max tab length (/statusbar)         : %d", pref_len);
+    }
+
+    char *pref_self = prefs_get_string(PREF_STATUSBAR_SELF);
+    if (g_strcmp0(pref_self, "off") == 0) {
+        cons_show("Self statusbar display (/statusbar) : OFF");
+    } else {
+        cons_show("Self statusbar display (/statusbar) : %s", pref_self);
+    }
+    prefs_free_string(pref_self);
+
+    char *pref_chat = prefs_get_string(PREF_STATUSBAR_CHAT);
+    cons_show("Chat tab display (/statusbar)       : %s", pref_chat);
+    prefs_free_string(pref_chat);
+
+    char *pref_room = prefs_get_string(PREF_STATUSBAR_ROOM);
+    cons_show("Room tab display (/statusbar)       : %s", pref_room);
+    prefs_free_string(pref_room);
+}
+
+void
 cons_winpos_setting(void)
 {
     ProfWinPlacement *placement = prefs_get_win_placement();
@@ -1906,6 +1945,7 @@ cons_show_connection_prefs(void)
     cons_reconnect_setting();
     cons_autoping_setting();
     cons_autoconnect_setting();
+    cons_rooms_cache_setting();
 
     cons_alert();
 }
@@ -2152,7 +2192,7 @@ cons_alert(void)
 {
     ProfWin *current = wins_get_current();
     if (current->type != WIN_CONSOLE) {
-        status_bar_new(1);
+        status_bar_new(1, WIN_CONSOLE, "console");
     }
 }
 
diff --git a/src/ui/core.c b/src/ui/core.c
index 539d78fd..5246d06a 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -1,7 +1,7 @@
 /*
  * core.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -102,8 +102,8 @@ ui_init(void)
     ui_load_colours();
     refresh();
     create_title_bar();
-    create_status_bar();
-    status_bar_active(1);
+    status_bar_init();
+    status_bar_active(1, WIN_CONSOLE, "console");
     create_input_window();
     wins_init();
     notifier_initialise();
@@ -137,7 +137,7 @@ ui_update(void)
         _ui_draw_term_title();
     }
     title_bar_update_virtual();
-    status_bar_update_virtual();
+    status_bar_draw();
     inp_put_back();
     doupdate();
 
@@ -183,6 +183,7 @@ ui_close(void)
     notifier_uninit();
     wins_destroy();
     inp_close();
+    status_bar_close();
     endwin();
 }
 
@@ -286,7 +287,7 @@ ui_contact_typing(const char *const barejid, const char *const resource)
             title_bar_set_typing(TRUE);
 
             int num = wins_get_num(window);
-            status_bar_active(num);
+            status_bar_active(num, WIN_CHAT, chatwin->barejid);
        }
     }
 
@@ -387,8 +388,7 @@ ui_handle_login_account_success(ProfAccount *account, gboolean secured)
     title_bar_set_connected(TRUE);
     title_bar_set_tls(secured);
 
-    status_bar_print_message(connection_get_fulljid());
-    status_bar_update_virtual();
+    status_bar_set_fulljid(connection_get_fulljid());
 }
 
 void
@@ -481,8 +481,7 @@ ui_disconnected(void)
     title_bar_set_connected(FALSE);
     title_bar_set_tls(FALSE);
     title_bar_set_presence(CONTACT_OFFLINE);
-    status_bar_clear_message();
-    status_bar_update_virtual();
+    status_bar_clear_fulljid();
     ui_hide_roster();
 }
 
@@ -672,7 +671,10 @@ ui_focus_win(ProfWin *window)
         title_bar_switch();
     }
     status_bar_current(i);
-    status_bar_active(i);
+
+    char *identifier = win_get_tab_identifier(window);
+    status_bar_active(i, window->type, identifier);
+    free(identifier);
 }
 
 void
@@ -689,7 +691,7 @@ ui_close_win(int index)
     wins_close_by_num(index);
     title_bar_console();
     status_bar_current(1);
-    status_bar_active(1);
+    status_bar_active(1, WIN_CONSOLE, "console");
 }
 
 void
@@ -737,17 +739,19 @@ ui_print_system_msg_from_recipient(const char *const barejid, const char *messag
     if (barejid == NULL || message == NULL)
         return;
 
-    ProfWin *window = (ProfWin*)wins_get_chat(barejid);
+    ProfChatWin *chatwin = wins_get_chat(barejid);
+    ProfWin *window = (ProfWin*)chatwin;
     if (window == NULL) {
         int num = 0;
         window = wins_new_chat(barejid);
         if (window) {
+            chatwin = (ProfChatWin*)window;
             num = wins_get_num(window);
-            status_bar_active(num);
+            status_bar_active(num, WIN_CHAT, chatwin->barejid);
         } else {
             num = 0;
             window = wins_get_console();
-            status_bar_active(1);
+            status_bar_active(1, WIN_CONSOLE, "console");
         }
     }
 
@@ -757,10 +761,11 @@ ui_print_system_msg_from_recipient(const char *const barejid, const char *messag
 void
 ui_room_join(const char *const roomjid, gboolean focus)
 {
-    ProfWin *window = (ProfWin*)wins_get_muc(roomjid);
-    if (!window) {
-        window = wins_new_muc(roomjid);
+    ProfMucWin *mucwin = wins_get_muc(roomjid);
+    if (mucwin == NULL) {
+        mucwin = (ProfMucWin*)wins_new_muc(roomjid);
     }
+    ProfWin *window = (ProfWin*)mucwin;
 
     char *nick = muc_nick(roomjid);
     win_print(window, THEME_ROOMINFO, '!', "-> You have joined the room as %s", nick);
@@ -780,7 +785,7 @@ ui_room_join(const char *const roomjid, gboolean focus)
         ui_focus_win(window);
     } else {
         int num = wins_get_num(window);
-        status_bar_active(num);
+        status_bar_active(num, WIN_MUC, mucwin->roomjid);
         ProfWin *console = wins_get_console();
         char *nick = muc_nick(roomjid);
         win_println(console, THEME_TYPING, '!', "-> Autojoined %s as %s (%d).", roomjid, nick, num);
@@ -968,15 +973,14 @@ ui_win_unread(int index)
 char*
 ui_ask_password(void)
 {
-    status_bar_get_password();
-    status_bar_update_virtual();
+    status_bar_set_prompt("Enter password:");
     return inp_get_password();
 }
 
 char*
 ui_get_line(void)
 {
-    status_bar_update_virtual();
+    status_bar_draw();
     return inp_get_line();
 }
 
@@ -999,8 +1003,7 @@ ui_ask_pgp_passphrase(const char *hint, int prev_fail)
 
     ui_update();
 
-    status_bar_get_password();
-    status_bar_update_virtual();
+    status_bar_set_prompt("Enter password:");
     return inp_get_password();
 }
 
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index e4da800b..654a4602 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -1,7 +1,7 @@
 /*
  * inputwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -87,7 +87,7 @@ 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 int _inp_edited(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);
@@ -251,7 +251,7 @@ inp_get_line(void)
         line = inp_readline();
         ui_update();
     }
-    status_bar_clear();
+    status_bar_clear_prompt();
     return line;
 }
 
@@ -269,7 +269,7 @@ inp_get_password(void)
         ui_update();
     }
     get_password = FALSE;
-    status_bar_clear();
+    status_bar_clear_prompt();
     return password;
 }
 
@@ -301,8 +301,24 @@ _inp_write(char *line, int offset)
 }
 
 static int
-_inp_printable(const wint_t ch)
+_inp_edited(const wint_t ch)
 {
+    // backspace
+    if (ch == 127) {
+        return 1;
+    }
+
+    // ctrl-w
+    if (ch == 23) {
+        return 1;
+    }
+
+    // enter
+    if (ch == 13) {
+        return 1;
+    }
+
+    // printable
     char bytes[MB_CUR_MAX+1];
     size_t utf_len = wcrtomb(bytes, ch, (mbstate_t*)NULL);
     bytes[utf_len] = '\0';
@@ -472,7 +488,7 @@ _inp_rl_getc(FILE *stream)
 
     shift_tab = FALSE;
 
-    if (_inp_printable(ch)) {
+    if (_inp_edited(ch)) {
         ProfWin *window = wins_get_current();
         cmd_ac_reset(window);
     }
diff --git a/src/ui/inputwin.h b/src/ui/inputwin.h
index 000c13c6..db36cf70 100644
--- a/src/ui/inputwin.h
+++ b/src/ui/inputwin.h
@@ -1,7 +1,7 @@
 /*
  * inputwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/mucconfwin.c b/src/ui/mucconfwin.c
index d181c769..7a658a1e 100644
--- a/src/ui/mucconfwin.c
+++ b/src/ui/mucconfwin.c
@@ -1,7 +1,7 @@
 /*
  * mucconfwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/mucwin.c b/src/ui/mucwin.c
index cb929b4a..f64a401e 100644
--- a/src/ui/mucwin.c
+++ b/src/ui/mucwin.c
@@ -1,7 +1,7 @@
 /*
  * mucwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -519,11 +519,11 @@ mucwin_requires_config(ProfMucWin *mucwin)
 
     // currently in groupchat window
     if (wins_is_current(window)) {
-        status_bar_active(num);
+        status_bar_active(num, WIN_MUC, mucwin->roomjid);
 
     // not currently on groupchat window
     } else {
-        status_bar_new(num);
+        status_bar_new(num, WIN_MUC, mucwin->roomjid);
     }
 }
 
@@ -533,8 +533,6 @@ mucwin_subject(ProfMucWin *mucwin, const char *const nick, const char *const sub
     assert(mucwin != NULL);
 
     ProfWin *window = (ProfWin*)mucwin;
-    int num = wins_get_num(window);
-
     if (subject) {
         if (nick) {
             win_print(window, THEME_ROOMINFO, '!', "*%s has set the room subject: ", nick);
@@ -550,15 +548,6 @@ mucwin_subject(ProfMucWin *mucwin, const char *const nick, const char *const sub
             win_println(window, THEME_ROOMINFO, '!', "Room subject cleared");
         }
     }
-
-    // currently in groupchat window
-    if (wins_is_current(window)) {
-        status_bar_active(num);
-
-    // not currently on groupchat window
-    } else {
-        status_bar_active(num);
-    }
 }
 
 void
@@ -583,11 +572,11 @@ mucwin_broadcast(ProfMucWin *mucwin, const char *const message)
 
     // currently in groupchat window
     if (wins_is_current(window)) {
-        status_bar_active(num);
+        status_bar_active(num, WIN_MUC, mucwin->roomjid);
 
     // not currently on groupchat window
     } else {
-        status_bar_new(num);
+        status_bar_new(num, WIN_MUC, mucwin->roomjid);
     }
 }
 
diff --git a/src/ui/notifier.c b/src/ui/notifier.c
index 7cf9ad02..452ae026 100644
--- a/src/ui/notifier.c
+++ b/src/ui/notifier.c
@@ -1,7 +1,7 @@
 /*
  * notifier.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/occupantswin.c b/src/ui/occupantswin.c
index 213c1f85..f6bb2197 100644
--- a/src/ui/occupantswin.c
+++ b/src/ui/occupantswin.c
@@ -1,7 +1,7 @@
 /*
  * occupantswin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/privwin.c b/src/ui/privwin.c
index 20f4b1cb..8de59683 100644
--- a/src/ui/privwin.c
+++ b/src/ui/privwin.c
@@ -1,7 +1,7 @@
 /*
  * privwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -62,11 +62,11 @@ privwin_incoming_msg(ProfPrivateWin *privatewin, const char *const message, GDat
     if (wins_is_current(window)) {
         win_print_incoming(window, timestamp, jidp->resourcepart, message, PROF_MSG_PLAIN);
         title_bar_set_typing(FALSE);
-        status_bar_active(num);
+        status_bar_active(num, WIN_PRIVATE, privatewin->fulljid);
 
     // not currently viewing chat window with sender
     } else {
-        status_bar_new(num);
+        status_bar_new(num, WIN_PRIVATE, privatewin->fulljid);
         cons_show_incoming_private_message(jidp->resourcepart, jidp->barejid, num, privatewin->unread);
         win_print_incoming(window, timestamp, jidp->resourcepart, message, PROF_MSG_PLAIN);
 
diff --git a/src/ui/rosterwin.c b/src/ui/rosterwin.c
index bbeaba21..3a38109d 100644
--- a/src/ui/rosterwin.c
+++ b/src/ui/rosterwin.c
@@ -1,7 +1,7 @@
 /*
  * rosterwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -762,7 +762,14 @@ _rosterwin_room(ProfLayoutSplit *layout, ProfMucWin *mucwin)
         g_string_append(msg, jidp->localpart);
         jid_destroy(jidp);
     } else {
-        g_string_append(msg, mucwin->roomjid);
+        gboolean show_server = prefs_get_boolean(PREF_ROSTER_ROOMS_SERVER);
+        if (show_server) {
+            g_string_append(msg, mucwin->roomjid);
+        } else {
+            Jid *jidp = jid_create(mucwin->roomjid);
+            g_string_append(msg, jidp->localpart);
+            jid_destroy(jidp);
+        }
     }
     prefs_free_string(roombypref);
     if ((g_strcmp0(unreadpos, "after") == 0) && mucwin->unread > 0) {
diff --git a/src/ui/screen.c b/src/ui/screen.c
index 519f3b1e..22548a75 100644
--- a/src/ui/screen.c
+++ b/src/ui/screen.c
@@ -1,7 +1,7 @@
 /*
  * screen.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/screen.h b/src/ui/screen.h
index 0ff94fd5..e382ab3e 100644
--- a/src/ui/screen.h
+++ b/src/ui/screen.h
@@ -1,7 +1,7 @@
 /*
  * screen.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/statusbar.c b/src/ui/statusbar.c
index 7998d7b4..ac1d7498 100644
--- a/src/ui/statusbar.c
+++ b/src/ui/statusbar.c
@@ -1,7 +1,7 @@
 /*
  * statusbar.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -50,151 +50,111 @@
 #include "ui/statusbar.h"
 #include "ui/inputwin.h"
 #include "ui/screen.h"
+#include "xmpp/roster_list.h"
+#include "xmpp/contact.h"
+
+typedef struct _status_bar_tab_t {
+    win_type_t window_type;
+    char *identifier;
+    gboolean highlight;
+} StatusBarTab;
+
+typedef struct _status_bar_t {
+    gchar *time;
+    char *prompt;
+    char *fulljid;
+    GHashTable *tabs;
+    int current_tab;
+} StatusBar;
 
-#define TIME_CHECK 60000000
-
-static WINDOW *status_bar;
-static char *message = NULL;
-//                          1  2  3  4  5  6  7  8  9  0  >
-static char _active[34] = "[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]";
-static char *bracket = "- -";
-static int is_active[12];
-static GHashTable *remaining_active;
-static int is_new[12];
-static GHashTable *remaining_new;
 static GTimeZone *tz;
-static GDateTime *last_time;
-static int current;
-
-static void _update_win_statuses(void);
-static void _mark_new(int num);
-static void _mark_active(int num);
-static void _mark_inactive(int num);
-static void _status_bar_draw(void);
+static StatusBar *statusbar;
+static WINDOW *statusbar_win;
+
+static int _status_bar_draw_time(int pos);
+static void _status_bar_draw_maintext(int pos);
+static int _status_bar_draw_bracket(gboolean current, int pos, char* ch);
+static int _status_bar_draw_extended_tabs(int pos);
+static int _status_bar_draw_tab(StatusBarTab *tab, int pos, int num);
+static void _destroy_tab(StatusBarTab *tab);
+static int _tabs_width(void);
+static char* _display_name(StatusBarTab *tab);
+static gboolean _extended_new(void);
 
 void
-create_status_bar(void)
+status_bar_init(void)
 {
-    int i;
-    int cols = getmaxx(stdscr);
-
-    is_active[1] = TRUE;
-    is_new[1] = FALSE;
-    for (i = 2; i < 12; i++) {
-        is_active[i] = FALSE;
-        is_new[i] = FALSE;
-    }
-    remaining_active = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
-    remaining_new = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
-    current = 1;
+    tz = g_time_zone_new_local();
 
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
+    statusbar = malloc(sizeof(StatusBar));
+    statusbar->time = NULL;
+    statusbar->prompt = NULL;
+    statusbar->fulljid = NULL;
+    statusbar->tabs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)_destroy_tab);
+    StatusBarTab *console = malloc(sizeof(StatusBarTab));
+    console->window_type = WIN_CONSOLE;
+    console->identifier = strdup("console");
+    g_hash_table_insert(statusbar->tabs, GINT_TO_POINTER(1), console);
+    statusbar->current_tab = 1;
 
     int row = screen_statusbar_row();
-    status_bar = newwin(1, cols, row, 0);
-    wbkgd(status_bar, theme_attrs(THEME_STATUS_TEXT));
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
-
-    tz = g_time_zone_new_local();
-
-    if (last_time) {
-        g_date_time_unref(last_time);
-    }
-    last_time = g_date_time_new_now(tz);
+    int cols = getmaxx(stdscr);
+    statusbar_win = newwin(1, cols, row, 0);
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
-status_bar_update_virtual(void)
+status_bar_close(void)
 {
-    _status_bar_draw();
+    if (tz) {
+        g_time_zone_unref(tz);
+    }
+    if (statusbar) {
+        if (statusbar->time) {
+            g_free(statusbar->time);
+        }
+        if (statusbar->prompt) {
+            free(statusbar->prompt);
+        }
+        if (statusbar->fulljid) {
+            free(statusbar->fulljid);
+        }
+        if (statusbar->tabs) {
+            g_hash_table_destroy(statusbar->tabs);
+        }
+        free(statusbar);
+    }
 }
 
 void
 status_bar_resize(void)
 {
     int cols = getmaxx(stdscr);
-
-    werase(status_bar);
-
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
-
+    werase(statusbar_win);
     int row = screen_statusbar_row();
-    mvwin(status_bar, row, 0);
-    wresize(status_bar, 1, cols);
-    wbkgd(status_bar, theme_attrs(THEME_STATUS_TEXT));
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
-
-    if (message) {
-        char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
-
-        gchar *date_fmt = NULL;
-        if (g_strcmp0(time_pref, "off") == 0) {
-            date_fmt = g_strdup("");
-        } else {
-            date_fmt = g_date_time_format(last_time, time_pref);
-        }
-        assert(date_fmt != NULL);
-        size_t len = strlen(date_fmt);
-        g_free(date_fmt);
-        if (g_strcmp0(time_pref, "off") != 0) {
-            /* 01234567890123456
-             *  [HH:MM]  message */
-            mvwprintw(status_bar, 0, 5 + len, message);
-        } else {
-            mvwprintw(status_bar, 0, 1, message);
-        }
-        prefs_free_string(time_pref);
-    }
-    if (last_time) {
-        g_date_time_unref(last_time);
-    }
-    last_time = g_date_time_new_now_local();
+    mvwin(statusbar_win, row, 0);
+    wresize(statusbar_win, 1, cols);
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
 status_bar_set_all_inactive(void)
 {
-    int i = 0;
-    for (i = 0; i < 12; i++) {
-        is_active[i] = FALSE;
-        is_new[i] = FALSE;
-        _mark_inactive(i);
-    }
-
-    g_hash_table_remove_all(remaining_active);
-    g_hash_table_remove_all(remaining_new);
-
-    _status_bar_draw();
+    g_hash_table_remove_all(statusbar->tabs);
 }
 
 void
 status_bar_current(int i)
 {
     if (i == 0) {
-        current = 10;
-    } else if (i > 10) {
-        current = 11;
+        statusbar->current_tab = 10;
     } else {
-        current = i;
+        statusbar->current_tab = i;
     }
-    int cols = getmaxx(stdscr);
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
@@ -205,281 +165,433 @@ status_bar_inactive(const int win)
         true_win = 10;
     }
 
-    // extra windows
-    if (true_win > 10) {
-        g_hash_table_remove(remaining_active, GINT_TO_POINTER(true_win));
-        g_hash_table_remove(remaining_new, GINT_TO_POINTER(true_win));
+    g_hash_table_remove(statusbar->tabs, GINT_TO_POINTER(true_win));
 
-        // still have new windows
-        if (g_hash_table_size(remaining_new) != 0) {
-            is_active[11] = TRUE;
-            is_new[11] = TRUE;
-            _mark_new(11);
-
-        // still have active windows
-        } else if (g_hash_table_size(remaining_active) != 0) {
-            is_active[11] = TRUE;
-            is_new[11] = FALSE;
-            _mark_active(11);
-
-        // no active or new windows
-        } else {
-            is_active[11] = FALSE;
-            is_new[11] = FALSE;
-            _mark_inactive(11);
-        }
+    status_bar_draw();
+}
 
-    // visible window indicators
-    } else {
-        is_active[true_win] = FALSE;
-        is_new[true_win] = FALSE;
-        _mark_inactive(true_win);
+void
+status_bar_active(const int win, win_type_t wintype, char *identifier)
+{
+    int true_win = win;
+    if (true_win == 0) {
+        true_win = 10;
     }
 
-    _status_bar_draw();
+    StatusBarTab *tab = malloc(sizeof(StatusBarTab));
+    tab->identifier = strdup(identifier);
+    tab->highlight = FALSE;
+    tab->window_type = wintype;
+    g_hash_table_replace(statusbar->tabs, GINT_TO_POINTER(true_win), tab);
+
+    status_bar_draw();
 }
 
 void
-status_bar_active(const int win)
+status_bar_new(const int win, win_type_t wintype, char* identifier)
 {
     int true_win = win;
     if (true_win == 0) {
         true_win = 10;
     }
 
-    // extra windows
-    if (true_win > 10) {
-        g_hash_table_add(remaining_active, GINT_TO_POINTER(true_win));
-        g_hash_table_remove(remaining_new, GINT_TO_POINTER(true_win));
+    StatusBarTab *tab = malloc(sizeof(StatusBarTab));
+    tab->identifier = strdup(identifier);
+    tab->highlight = TRUE;
+    tab->window_type = wintype;
+    g_hash_table_replace(statusbar->tabs, GINT_TO_POINTER(true_win), tab);
 
-        // still have new windows
-        if (g_hash_table_size(remaining_new) != 0) {
-            is_active[11] = TRUE;
-            is_new[11] = TRUE;
-            _mark_new(11);
-
-        // only active windows
-        } else {
-            is_active[11] = TRUE;
-            is_new[11] = FALSE;
-            _mark_active(11);
-        }
+    status_bar_draw();
+}
 
-    // visible window indicators
-    } else {
-        is_active[true_win] = TRUE;
-        is_new[true_win] = FALSE;
-        _mark_active(true_win);
+void
+status_bar_set_prompt(const char *const prompt)
+{
+    if (statusbar->prompt) {
+        free(statusbar->prompt);
+        statusbar->prompt = NULL;
     }
+    statusbar->prompt = strdup(prompt);
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
-status_bar_new(const int win)
+status_bar_clear_prompt(void)
 {
-    int true_win = win;
-    if (true_win == 0) {
-        true_win = 10;
+    if (statusbar->prompt) {
+        free(statusbar->prompt);
+        statusbar->prompt = NULL;
     }
 
-    if (true_win > 10) {
-        g_hash_table_add(remaining_active, GINT_TO_POINTER(true_win));
-        g_hash_table_add(remaining_new, GINT_TO_POINTER(true_win));
-
-        is_active[11] = TRUE;
-        is_new[11] = TRUE;
-        _mark_new(11);
+    status_bar_draw();
+}
 
-    } else {
-        is_active[true_win] = TRUE;
-        is_new[true_win] = TRUE;
-        _mark_new(true_win);
+void
+status_bar_set_fulljid(const char *const fulljid)
+{
+    if (statusbar->fulljid) {
+        free(statusbar->fulljid);
+        statusbar->fulljid = NULL;
     }
+    statusbar->fulljid = strdup(fulljid);
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
-status_bar_get_password(void)
+status_bar_clear_fulljid(void)
 {
-    status_bar_print_message("Enter password:");
+    if (statusbar->fulljid) {
+        free(statusbar->fulljid);
+        statusbar->fulljid = NULL;
+    }
 
-    _status_bar_draw();
+    status_bar_draw();
 }
 
 void
-status_bar_print_message(const char *const msg)
+status_bar_draw(void)
 {
-    werase(status_bar);
+    werase(statusbar_win);
+    wbkgd(statusbar_win, theme_attrs(THEME_STATUS_TEXT));
 
-    if (message) {
-        free(message);
+    int pos = 1;
+
+    pos = _status_bar_draw_time(pos);
+
+    _status_bar_draw_maintext(pos);
+
+    pos = getmaxx(stdscr) - _tabs_width();
+    if (pos < 0) {
+        pos = 0;
+    }
+    gint max_tabs = prefs_get_statusbartabs();
+    int i = 1;
+    for (i = 1; i <= max_tabs; i++) {
+        StatusBarTab *tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
+        if (tab) {
+            pos = _status_bar_draw_tab(tab, pos, i);
+        }
     }
-    message = strdup(msg);
 
-    char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
-    gchar *date_fmt = NULL;
-    if (g_strcmp0(time_pref, "off") == 0) {
-        date_fmt = g_strdup("");
-    } else {
-        date_fmt = g_date_time_format(last_time, time_pref);
+    pos = _status_bar_draw_extended_tabs(pos);
+
+    wnoutrefresh(statusbar_win);
+    inp_put_back();
+}
+
+static gboolean
+_extended_new(void)
+{
+    gint max_tabs = prefs_get_statusbartabs();
+    int tabs_count = g_hash_table_size(statusbar->tabs);
+    if (tabs_count <= max_tabs) {
+        return FALSE;
     }
 
-    assert(date_fmt != NULL);
-    size_t len = strlen(date_fmt);
-    g_free(date_fmt);
-    if (g_strcmp0(time_pref, "off") != 0) {
-        mvwprintw(status_bar, 0, 5 + len, message);
-    } else {
-        mvwprintw(status_bar, 0, 1, message);
+    int i = 0;
+    for (i = max_tabs + 1; i <= tabs_count; i++) {
+        StatusBarTab *tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
+        if (tab && tab->highlight) {
+            return TRUE;
+        }
     }
-    prefs_free_string(time_pref);
 
-    int cols = getmaxx(stdscr);
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
+    return FALSE;
+}
+
+static int
+_status_bar_draw_extended_tabs(int pos)
+{
+    gint max_tabs = prefs_get_statusbartabs();
+    if (max_tabs == 0) {
+        return pos;
+    }
 
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
+    if (g_hash_table_size(statusbar->tabs) > max_tabs) {
+        gboolean is_current = statusbar->current_tab > max_tabs;
 
-    _status_bar_draw();
+        pos = _status_bar_draw_bracket(is_current, pos, "[");
+
+        int status_attrs = theme_attrs(THEME_STATUS_ACTIVE);
+        if (_extended_new()) {
+            status_attrs = theme_attrs(THEME_STATUS_NEW);
+        }
+        wattron(statusbar_win, status_attrs);
+        mvwprintw(statusbar_win, 0, pos, ">");
+        wattroff(statusbar_win, status_attrs);
+        pos++;
+
+        pos = _status_bar_draw_bracket(is_current, pos, "]");
+    }
+
+    return pos;
 }
 
-void
-status_bar_clear(void)
+static int
+_status_bar_draw_tab(StatusBarTab *tab, int pos, int num)
 {
-    if (message) {
-        free(message);
-        message = NULL;
+    int display_num = num == 10 ? 0 : num;
+    gboolean is_current = num == statusbar->current_tab;
+
+    gboolean show_number = prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER);
+    gboolean show_name = prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME);
+
+    pos = _status_bar_draw_bracket(is_current, pos, "[");
+
+    int status_attrs = 0;
+    if (tab->highlight) {
+        status_attrs = theme_attrs(THEME_STATUS_NEW);
+    } else {
+        status_attrs = theme_attrs(THEME_STATUS_ACTIVE);
+    }
+    wattron(statusbar_win, status_attrs);
+    if (show_number) {
+        mvwprintw(statusbar_win, 0, pos, "%d", display_num);
+        pos++;
+    }
+    if (show_number && show_name) {
+        mvwprintw(statusbar_win, 0, pos, ":");
+        pos++;
     }
+    if (show_name) {
+        char *display_name = _display_name(tab);
+        mvwprintw(statusbar_win, 0, pos, display_name);
+        pos += utf8_display_len(display_name);
+        free(display_name);
+    }
+    wattroff(statusbar_win, status_attrs);
 
-    werase(status_bar);
+    pos = _status_bar_draw_bracket(is_current, pos, "]");
 
-    int cols = getmaxx(stdscr);
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
+    return pos;
+}
 
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
+static int
+_status_bar_draw_bracket(gboolean current, int pos, char* ch)
+{
+    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
+    wattron(statusbar_win, bracket_attrs);
+    if (current) {
+        mvwprintw(statusbar_win, 0, pos, "-");
+    } else {
+        mvwprintw(statusbar_win, 0, pos, ch);
+    }
+    wattroff(statusbar_win, bracket_attrs);
+    pos++;
 
-    _status_bar_draw();
+    return pos;
 }
 
-void
-status_bar_clear_message(void)
+static int
+_status_bar_draw_time(int pos)
 {
-    if (message) {
-        free(message);
-        message = NULL;
+    char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
+    if (g_strcmp0(time_pref, "off") == 0) {
+        prefs_free_string(time_pref);
+        return pos;
     }
 
-    werase(status_bar);
+    if (statusbar->time) {
+        g_free(statusbar->time);
+        statusbar->time = NULL;
+    }
+
+    GDateTime *datetime = g_date_time_new_now(tz);
+    statusbar->time  = g_date_time_format(datetime, time_pref);
+    assert(statusbar->time != NULL);
+    g_date_time_unref(datetime);
 
-    int cols = getmaxx(stdscr);
     int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
+    int time_attrs = theme_attrs(THEME_STATUS_TIME);
 
-    wattron(status_bar, bracket_attrs);
-    mvwprintw(status_bar, 0, cols - 34, _active);
-    mvwprintw(status_bar, 0, cols - 34 + ((current - 1) * 3), bracket);
-    wattroff(status_bar, bracket_attrs);
+    size_t len = strlen(statusbar->time);
+    wattron(statusbar_win, bracket_attrs);
+    mvwaddch(statusbar_win, 0, pos, '[');
+    pos++;
+    wattroff(statusbar_win, bracket_attrs);
+    wattron(statusbar_win, time_attrs);
+    mvwprintw(statusbar_win, 0, pos, statusbar->time);
+    pos += len;
+    wattroff(statusbar_win, time_attrs);
+    wattron(statusbar_win, bracket_attrs);
+    mvwaddch(statusbar_win, 0, pos, ']');
+    wattroff(statusbar_win, bracket_attrs);
+    pos += 2;
 
-    _status_bar_draw();
+    prefs_free_string(time_pref);
+
+    return pos;
 }
 
 static void
-_update_win_statuses(void)
+_status_bar_draw_maintext(int pos)
 {
-    int i;
-    for(i = 1; i < 12; i++) {
-        if (is_new[i]) {
-            _mark_new(i);
+    if (statusbar->prompt) {
+        mvwprintw(statusbar_win, 0, pos, statusbar->prompt);
+        return;
+    }
+
+    if (statusbar->fulljid) {
+        char *pref = prefs_get_string(PREF_STATUSBAR_SELF);
+        if (g_strcmp0(pref, "off") == 0) {
+            return;
         }
-        else if (is_active[i]) {
-            _mark_active(i);
+        if (g_strcmp0(pref, "user") == 0) {
+            Jid *jidp = jid_create(statusbar->fulljid);
+            mvwprintw(statusbar_win, 0, pos, jidp->localpart);
+            jid_destroy(jidp);
+            return;
         }
-        else {
-            _mark_inactive(i);
+        if (g_strcmp0(pref, "barejid") == 0) {
+            Jid *jidp = jid_create(statusbar->fulljid);
+            mvwprintw(statusbar_win, 0, pos, jidp->barejid);
+            jid_destroy(jidp);
+            return;
         }
+        mvwprintw(statusbar_win, 0, pos, statusbar->fulljid);
     }
 }
 
 static void
-_mark_new(int num)
+_destroy_tab(StatusBarTab *tab)
 {
-    int active_pos = 1 + ((num-1) * 3);
-    int cols = getmaxx(stdscr);
-    int status_attrs = theme_attrs(THEME_STATUS_NEW);
-    wattron(status_bar, status_attrs);
-    wattron(status_bar, A_BLINK);
-    if (num == 10) {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, "0");
-    } else if (num > 10) {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, ">");
-    } else {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, "%d", num);
+    if (tab) {
+        if (tab->identifier) {
+            free(tab->identifier);
+        }
+        free(tab);
     }
-    wattroff(status_bar, status_attrs);
-    wattroff(status_bar, A_BLINK);
 }
 
-static void
-_mark_active(int num)
+static int
+_tabs_width(void)
 {
-    int active_pos = 1 + ((num-1) * 3);
-    int cols = getmaxx(stdscr);
-    int status_attrs = theme_attrs(THEME_STATUS_ACTIVE);
-    wattron(status_bar, status_attrs);
-    if (num == 10) {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, "0");
-    } else if (num > 10) {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, ">");
-    } else {
-        mvwprintw(status_bar, 0, cols - 34 + active_pos, "%d", num);
+    gboolean show_number = prefs_get_boolean(PREF_STATUSBAR_SHOW_NUMBER);
+    gboolean show_name = prefs_get_boolean(PREF_STATUSBAR_SHOW_NAME);
+    gint max_tabs = prefs_get_statusbartabs();
+
+    if (show_name && show_number) {
+        int width = g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1;
+        int i = 0;
+        for (i = 1; i <= max_tabs; i++) {
+            StatusBarTab *tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
+            if (tab) {
+                char *display_name = _display_name(tab);
+                width += utf8_display_len(display_name);
+                width += 4;
+                free(display_name);
+            }
+        }
+        return width;
     }
-    wattroff(status_bar, status_attrs);
-}
 
-static void
-_mark_inactive(int num)
-{
-    int active_pos = 1 + ((num-1) * 3);
-    int cols = getmaxx(stdscr);
-    mvwaddch(status_bar, 0, cols - 34 + active_pos, ' ');
+    if (show_name && !show_number) {
+        int width = g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1;
+        int i = 0;
+        for (i = 1; i <= max_tabs; i++) {
+            StatusBarTab *tab = g_hash_table_lookup(statusbar->tabs, GINT_TO_POINTER(i));
+            if (tab) {
+                char *display_name = _display_name(tab);
+                width += utf8_display_len(display_name);
+                width += 2;
+                free(display_name);
+            }
+        }
+        return width;
+    }
+
+    if (g_hash_table_size(statusbar->tabs) > max_tabs) {
+        return max_tabs * 3 + (g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1);
+    }
+    return g_hash_table_size(statusbar->tabs) * 3 + (g_hash_table_size(statusbar->tabs) > max_tabs ? 4 : 1);
 }
 
-static void
-_status_bar_draw(void)
+static char*
+_display_name(StatusBarTab *tab)
 {
-    if (last_time) {
-        g_date_time_unref(last_time);
+    char *fullname = NULL;
+
+    if (tab->window_type == WIN_CONSOLE) {
+        fullname = strdup("console");
+    } else if (tab->window_type == WIN_XML) {
+        fullname = strdup("xmlconsole");
+    } else if (tab->window_type == WIN_PLUGIN) {
+        fullname = strdup(tab->identifier);
+    } else if (tab->window_type == WIN_CHAT) {
+        PContact contact = roster_get_contact(tab->identifier);
+        if (contact && p_contact_name(contact)) {
+            fullname = strdup(p_contact_name(contact));
+        } else {
+            char *pref = prefs_get_string(PREF_STATUSBAR_CHAT);
+            if (g_strcmp0("user", pref) == 0) {
+                Jid *jidp = jid_create(tab->identifier);
+                char *user = strdup(jidp->localpart);
+                jid_destroy(jidp);
+                fullname = user;
+            } else {
+                fullname = strdup(tab->identifier);
+            }
+        }
+    } else if (tab->window_type == WIN_MUC) {
+        char *pref = prefs_get_string(PREF_STATUSBAR_ROOM);
+        if (g_strcmp0("room", pref) == 0) {
+            Jid *jidp = jid_create(tab->identifier);
+            char *room = strdup(jidp->localpart);
+            jid_destroy(jidp);
+            fullname = room;
+        } else {
+            fullname = strdup(tab->identifier);
+        }
+    } else if (tab->window_type == WIN_MUC_CONFIG) {
+        char *pref = prefs_get_string(PREF_STATUSBAR_ROOM);
+        GString *display_str = g_string_new("");
+        if (g_strcmp0("room", pref) == 0) {
+            Jid *jidp = jid_create(tab->identifier);
+            g_string_append(display_str, jidp->localpart);
+            jid_destroy(jidp);
+        } else {
+            g_string_append(display_str, tab->identifier);
+        }
+        g_string_append(display_str, " conf");
+        char *result = strdup(display_str->str);
+        g_string_free(display_str, TRUE);
+        fullname = result;
+    } else if (tab->window_type == WIN_PRIVATE) {
+        char *pref = prefs_get_string(PREF_STATUSBAR_ROOM);
+        if (g_strcmp0("room", pref) == 0) {
+            GString *display_str = g_string_new("");
+            Jid *jidp = jid_create(tab->identifier);
+            g_string_append(display_str, jidp->localpart);
+            g_string_append(display_str, "/");
+            g_string_append(display_str, jidp->resourcepart);
+            jid_destroy(jidp);
+            char *result = strdup(display_str->str);
+            g_string_free(display_str, TRUE);
+            fullname = result;
+        } else {
+            fullname = strdup(tab->identifier);
+        }
+    } else {
+        fullname = strdup("window");
     }
-    last_time = g_date_time_new_now(tz);
 
-    int bracket_attrs = theme_attrs(THEME_STATUS_BRACKET);
-    int time_attrs = theme_attrs(THEME_STATUS_TIME);
+    gint tablen = prefs_get_statusbartablen();
+    if (tablen == 0) {
+        return fullname;
+    }
 
-    char *time_pref = prefs_get_string(PREF_TIME_STATUSBAR);
-    if (g_strcmp0(time_pref, "off") != 0) {
-        gchar *date_fmt = g_date_time_format(last_time, time_pref);
-        assert(date_fmt != NULL);
-        size_t len = strlen(date_fmt);
-        wattron(status_bar, bracket_attrs);
-        mvwaddch(status_bar, 0, 1, '[');
-        wattroff(status_bar, bracket_attrs);
-        wattron(status_bar, time_attrs);
-        mvwprintw(status_bar, 0, 2, date_fmt);
-        wattroff(status_bar, time_attrs);
-        wattron(status_bar, bracket_attrs);
-        mvwaddch(status_bar, 0, 2 + len, ']');
-        wattroff(status_bar, bracket_attrs);
-        g_free(date_fmt);
+    int namelen = utf8_display_len(fullname);
+    if (namelen < tablen) {
+        return fullname;
     }
-    prefs_free_string(time_pref);
 
-    _update_win_statuses();
-    wnoutrefresh(status_bar);
-    inp_put_back();
+    gchar *trimmed = g_utf8_substring(fullname, 0, tablen);
+    free(fullname);
+    char *trimmedname = strdup(trimmed);
+    g_free(trimmed);
+
+    return trimmedname;
+
 }
diff --git a/src/ui/statusbar.h b/src/ui/statusbar.h
index ce863b75..de8b51cc 100644
--- a/src/ui/statusbar.h
+++ b/src/ui/statusbar.h
@@ -1,7 +1,7 @@
 /*
  * statusbar.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -35,13 +35,14 @@
 #ifndef UI_STATUSBAR_H
 #define UI_STATUSBAR_H
 
-void create_status_bar(void);
-void status_bar_update_virtual(void);
+void status_bar_init(void);
+void status_bar_draw(void);
+void status_bar_close(void);
 void status_bar_resize(void);
-void status_bar_clear(void);
-void status_bar_clear_message(void);
-void status_bar_get_password(void);
-void status_bar_print_message(const char *const msg);
+void status_bar_set_prompt(const char *const prompt);
+void status_bar_clear_prompt(void);
+void status_bar_set_fulljid(const char *const fulljid);
+void status_bar_clear_fulljid(void);
 void status_bar_current(int i);
 
 #endif
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index 3b170558..4f5383c2 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -1,7 +1,7 @@
 /*
  * titlebar.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/titlebar.h b/src/ui/titlebar.h
index dda517a2..0aed4b2b 100644
--- a/src/ui/titlebar.h
+++ b/src/ui/titlebar.h
@@ -1,7 +1,7 @@
 /*
  * titlebar.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/tray.c b/src/ui/tray.c
index 36817004..ecb0843b 100644
--- a/src/ui/tray.c
+++ b/src/ui/tray.c
@@ -1,7 +1,7 @@
 /*
  * tray.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/tray.h b/src/ui/tray.h
index 4efe988a..c1e6ba77 100644
--- a/src/ui/tray.h
+++ b/src/ui/tray.h
@@ -1,7 +1,7 @@
 /*
  * tray.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/ui.h b/src/ui/ui.h
index c2c6a969..d344f855 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -1,7 +1,7 @@
 /*
  * ui.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -297,7 +297,6 @@ void cons_occupants_setting(void);
 void cons_roster_setting(void);
 void cons_presence_setting(void);
 void cons_wrap_setting(void);
-void cons_winstidy_setting(void);
 void cons_time_setting(void);
 void cons_wintitle_setting(void);
 void cons_notify_setting(void);
@@ -316,7 +315,9 @@ void cons_autoaway_setting(void);
 void cons_reconnect_setting(void);
 void cons_autoping_setting(void);
 void cons_autoconnect_setting(void);
+void cons_room_cache_setting(void);
 void cons_inpblock_setting(void);
+void cons_statusbar_setting(void);
 void cons_winpos_setting(void);
 void cons_show_contact_online(PContact contact, Resource *resource, GDateTime *last_activity);
 void cons_show_contact_offline(PContact contact, char *resource, char *status);
@@ -330,8 +331,8 @@ void title_bar_set_presence(contact_presence_t presence);
 
 // status bar
 void status_bar_inactive(const int win);
-void status_bar_active(const int win);
-void status_bar_new(const int win);
+void status_bar_active(const int win, win_type_t wintype, char *identifier);
+void status_bar_new(const int win, win_type_t wintype, char *identifier);
 void status_bar_set_all_inactive(void);
 
 // roster window
@@ -374,6 +375,7 @@ void win_show_occupant_info(ProfWin *window, const char *const room, Occupant *o
 void win_show_contact(ProfWin *window, PContact contact);
 void win_show_info(ProfWin *window, PContact contact);
 void win_clear(ProfWin *window);
+char* win_get_tab_identifier(ProfWin *window);
 char* win_to_string(ProfWin *window);
 
 // desktop notifications
diff --git a/src/ui/win_types.h b/src/ui/win_types.h
index 87ee0b40..7fa75b34 100644
--- a/src/ui/win_types.h
+++ b/src/ui/win_types.h
@@ -1,7 +1,7 @@
 /*
  * win_types.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -194,7 +194,7 @@ typedef struct prof_xml_win_t {
 } ProfXMLWin;
 
 typedef struct prof_plugin_win_t {
-    ProfWin super;
+    ProfWin window;
     char *tag;
     char *plugin_name;
     unsigned long memcheck;
diff --git a/src/ui/window.c b/src/ui/window.c
index aea4b60c..5543707d 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -1,7 +1,7 @@
 /*
  * window.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -162,7 +162,7 @@ win_create_muc(const char *const roomjid)
     int cols = getmaxx(stdscr);
 
     new_win->window.type = WIN_MUC;
-
+    new_win->window.layout = _win_create_simple_layout();
     ProfLayoutSplit *layout = malloc(sizeof(ProfLayoutSplit));
     layout->base.type = LAYOUT_SPLIT;
 
@@ -208,7 +208,6 @@ win_create_muc_config(const char *const roomjid, DataForm *form)
     ProfMucConfWin *new_win = malloc(sizeof(ProfMucConfWin));
     new_win->window.type = WIN_MUC_CONFIG;
     new_win->window.layout = _win_create_simple_layout();
-
     new_win->roomjid = strdup(roomjid);
     new_win->form = form;
 
@@ -223,7 +222,6 @@ win_create_private(const char *const fulljid)
     ProfPrivateWin *new_win = malloc(sizeof(ProfPrivateWin));
     new_win->window.type = WIN_PRIVATE;
     new_win->window.layout = _win_create_simple_layout();
-
     new_win->fulljid = strdup(fulljid);
     new_win->unread = 0;
     new_win->occupant_offline = FALSE;
@@ -250,15 +248,15 @@ ProfWin*
 win_create_plugin(const char *const plugin_name, const char *const tag)
 {
     ProfPluginWin *new_win = malloc(sizeof(ProfPluginWin));
-    new_win->super.type = WIN_PLUGIN;
-    new_win->super.layout = _win_create_simple_layout();
+    new_win->window.type = WIN_PLUGIN;
+    new_win->window.layout = _win_create_simple_layout();
 
     new_win->tag = strdup(tag);
     new_win->plugin_name = strdup(plugin_name);
 
     new_win->memcheck = PROFPLUGINWIN_MEMCHECK;
 
-    return &new_win->super;
+    return &new_win->window;
 }
 
 char*
@@ -321,6 +319,50 @@ win_get_title(ProfWin *window)
 }
 
 char*
+win_get_tab_identifier(ProfWin *window)
+{
+    assert(window != NULL);
+
+    switch (window->type) {
+        case WIN_CONSOLE:
+        {
+            return strdup("console");
+        }
+        case WIN_CHAT:
+        {
+            ProfChatWin *chatwin = (ProfChatWin*)window;
+            return strdup(chatwin->barejid);
+        }
+        case WIN_MUC:
+        {
+            ProfMucWin *mucwin = (ProfMucWin*)window;
+            return strdup(mucwin->roomjid);
+        }
+        case WIN_MUC_CONFIG:
+        {
+            ProfMucConfWin *mucconfwin = (ProfMucConfWin*)window;
+            return strdup(mucconfwin->roomjid);
+        }
+        case WIN_PRIVATE:
+        {
+            ProfPrivateWin *privwin = (ProfPrivateWin*)window;
+            return strdup(privwin->fulljid);
+        }
+        case WIN_PLUGIN:
+        {
+            ProfPluginWin *pluginwin = (ProfPluginWin*)window;
+            return strdup(pluginwin->tag);
+        }
+        case WIN_XML:
+        {
+            return strdup("xmlconsole");
+        }
+        default:
+            return strdup("UNKNOWN");
+    }
+}
+
+char*
 win_to_string(ProfWin *window)
 {
     assert(window != NULL);
diff --git a/src/ui/window.h b/src/ui/window.h
index 074e5855..62f737de 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -1,7 +1,7 @@
 /*
  * window.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/ui/window_list.c b/src/ui/window_list.c
index 2d19ab32..798f4e41 100644
--- a/src/ui/window_list.c
+++ b/src/ui/window_list.c
@@ -1,7 +1,7 @@
 /*
  * window_list.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -45,7 +45,6 @@
 #include "config/theme.h"
 #include "plugins/plugins.h"
 #include "ui/ui.h"
-#include "ui/statusbar.h"
 #include "ui/window_list.h"
 #include "xmpp/xmpp.h"
 #include "xmpp/roster_list.h"
@@ -239,9 +238,7 @@ wins_close_plugin(char *tag)
     int index = wins_get_num(toclose);
     ui_close_win(index);
 
-    if (prefs_get_boolean(PREF_WINS_AUTO_TIDY)) {
-        wins_tidy();
-    }
+    wins_tidy();
 }
 
 GList*
@@ -847,7 +844,7 @@ wins_lost_connection(void)
     g_list_free(values);
 }
 
-gboolean
+void
 wins_swap(int source_win, int target_win)
 {
     ProfWin *source = g_hash_table_lookup(windows, GINT_TO_POINTER(source_win));
@@ -857,20 +854,21 @@ wins_swap(int source_win, int target_win)
         ProfWin *target = g_hash_table_lookup(windows, GINT_TO_POINTER(target_win));
 
         // target window empty
-        if (!target) {
+        if (target == NULL) {
             g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
             g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
             status_bar_inactive(source_win);
+            char *identifier = win_get_tab_identifier(source);
             if (win_unread(source) > 0) {
-                status_bar_new(target_win);
+                status_bar_new(target_win, source->type, identifier);
             } else {
-                status_bar_active(target_win);
+                status_bar_active(target_win, source->type, identifier);
             }
+            free(identifier);
             if (wins_get_current_num() == source_win) {
                 wins_set_current_by_num(target_win);
                 ui_focus_win(console);
             }
-            return TRUE;
 
         // target window occupied
         } else {
@@ -878,23 +876,24 @@ wins_swap(int source_win, int target_win)
             g_hash_table_steal(windows, GINT_TO_POINTER(target_win));
             g_hash_table_insert(windows, GINT_TO_POINTER(source_win), target);
             g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
+            char *source_identifier = win_get_tab_identifier(source);
+            char *target_identifier = win_get_tab_identifier(target);
             if (win_unread(source) > 0) {
-                status_bar_new(target_win);
+                status_bar_new(target_win, source->type, source_identifier);
             } else {
-                status_bar_active(target_win);
+                status_bar_active(target_win, source->type, source_identifier);
             }
             if (win_unread(target) > 0) {
-                status_bar_new(source_win);
+                status_bar_new(source_win, target->type, target_identifier);
             } else {
-                status_bar_active(source_win);
+                status_bar_active(source_win, target->type, target_identifier);
             }
+            free(source_identifier);
+            free(target_identifier);
             if ((wins_get_current_num() == source_win) || (wins_get_current_num() == target_win)) {
                 ui_focus_win(console);
             }
-            return TRUE;
         }
-    } else {
-        return FALSE;
     }
 }
 
@@ -999,22 +998,24 @@ wins_tidy(void)
         GList *curr = keys;
         while (curr) {
             ProfWin *window = g_hash_table_lookup(windows, curr->data);
+            char *identifier = win_get_tab_identifier(window);
             g_hash_table_steal(windows, curr->data);
             if (num == 10) {
                 g_hash_table_insert(new_windows, GINT_TO_POINTER(0), window);
                 if (win_unread(window) > 0) {
-                    status_bar_new(0);
+                    status_bar_new(0, window->type, identifier);
                 } else {
-                    status_bar_active(0);
+                    status_bar_active(0, window->type, identifier);
                 }
             } else {
                 g_hash_table_insert(new_windows, GINT_TO_POINTER(num), window);
                 if (win_unread(window) > 0) {
-                    status_bar_new(num);
+                    status_bar_new(num, window->type, identifier);
                 } else {
-                    status_bar_active(num);
+                    status_bar_active(num, window->type, identifier);
                 }
             }
+            free(identifier);
             num++;
             curr = g_list_next(curr);
         }
diff --git a/src/ui/window_list.h b/src/ui/window_list.h
index dc1f3be4..68e72739 100644
--- a/src/ui/window_list.h
+++ b/src/ui/window_list.h
@@ -1,7 +1,7 @@
 /*
  * window_list.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -87,7 +87,7 @@ gboolean wins_tidy(void);
 GSList* wins_create_summary(gboolean unread);
 void wins_destroy(void);
 GList* wins_get_nums(void);
-gboolean wins_swap(int source_win, int target_win);
+void wins_swap(int source_win, int target_win);
 void wins_hide_subwin(ProfWin *window);
 void wins_show_subwin(ProfWin *window);
 
diff --git a/src/ui/xmlwin.c b/src/ui/xmlwin.c
index e845b2e0..c76e91cf 100644
--- a/src/ui/xmlwin.c
+++ b/src/ui/xmlwin.c
@@ -1,7 +1,7 @@
 /*
  * xmlwin.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/blocking.c b/src/xmpp/blocking.c
index 2a8fb316..5d1c52b2 100644
--- a/src/xmpp/blocking.c
+++ b/src/xmpp/blocking.c
@@ -1,7 +1,7 @@
 /*
  * blocking.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/blocking.h b/src/xmpp/blocking.h
index 1e3b6961..61acb631 100644
--- a/src/xmpp/blocking.h
+++ b/src/xmpp/blocking.h
@@ -1,7 +1,7 @@
 /*
  * blocking.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/bookmark.c b/src/xmpp/bookmark.c
index 99b2e20d..65009224 100644
--- a/src/xmpp/bookmark.c
+++ b/src/xmpp/bookmark.c
@@ -1,7 +1,7 @@
 /*
  * bookmark.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -98,6 +98,12 @@ bookmark_add(const char *jid, const char *nick, const char *password, const char
 {
     assert(jid != NULL);
 
+    Jid *jidp = jid_create(jid);
+    if (jidp->domainpart) {
+        muc_confserver_add(jidp->domainpart);
+    }
+    jid_destroy(jidp);
+
     if (g_hash_table_contains(bookmarks, jid)) {
         return FALSE;
     }
@@ -297,6 +303,12 @@ _bookmark_result_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
             sv_ev_bookmark_autojoin(bookmark);
         }
 
+        Jid *jidp = jid_create(barejid);
+        if (jidp->domainpart) {
+            muc_confserver_add(jidp->domainpart);
+        }
+        jid_destroy(jidp);
+
         child = xmpp_stanza_get_next(child);
     }
 
diff --git a/src/xmpp/bookmark.h b/src/xmpp/bookmark.h
index c9dfa214..f6c72c27 100644
--- a/src/xmpp/bookmark.h
+++ b/src/xmpp/bookmark.h
@@ -1,7 +1,7 @@
 /*
  * bookmark.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index 263680a6..2152f869 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -1,7 +1,7 @@
 /*
  * capabilities.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h
index c1b820fb..21ffe0b4 100644
--- a/src/xmpp/capabilities.h
+++ b/src/xmpp/capabilities.h
@@ -1,7 +1,7 @@
 /*
  * capabilities.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/chat_session.c b/src/xmpp/chat_session.c
index 6e6d871a..799a9c47 100644
--- a/src/xmpp/chat_session.c
+++ b/src/xmpp/chat_session.c
@@ -1,7 +1,7 @@
 /*
  * chat_session.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/chat_session.h b/src/xmpp/chat_session.h
index be3b1ce6..d8de0330 100644
--- a/src/xmpp/chat_session.h
+++ b/src/xmpp/chat_session.h
@@ -1,7 +1,7 @@
 /*
  * chat_session.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/chat_state.c b/src/xmpp/chat_state.c
index cb1b9f69..9659eb8b 100644
--- a/src/xmpp/chat_state.c
+++ b/src/xmpp/chat_state.c
@@ -1,7 +1,7 @@
 /*
  * chat_state.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/chat_state.h b/src/xmpp/chat_state.h
index 9255cb04..51698cd1 100644
--- a/src/xmpp/chat_state.h
+++ b/src/xmpp/chat_state.h
@@ -1,7 +1,7 @@
 /*
  * chat_state.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 438e1c8a..6c5e7323 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -1,7 +1,7 @@
 /*
  * connection.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -152,6 +152,8 @@ connection_connect(const char *const jid, const char *const passwd, const char *
         xmpp_conn_set_flags(conn.xmpp_conn, XMPP_CONN_FLAG_MANDATORY_TLS);
     } else if (g_strcmp0(tls_policy, "disable") == 0) {
         xmpp_conn_set_flags(conn.xmpp_conn, XMPP_CONN_FLAG_DISABLE_TLS);
+    } else if (g_strcmp0(tls_policy, "legacy") == 0) {
+        xmpp_conn_set_flags(conn.xmpp_conn, XMPP_CONN_FLAG_LEGACY_SSL);
     }
 
 #ifdef HAVE_LIBMESODE
diff --git a/src/xmpp/connection.h b/src/xmpp/connection.h
index ea3d08e7..b0ff7cec 100644
--- a/src/xmpp/connection.h
+++ b/src/xmpp/connection.h
@@ -1,7 +1,7 @@
 /*
  * connection.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/contact.c b/src/xmpp/contact.c
index 0fbff9d5..a43372f3 100644
--- a/src/xmpp/contact.c
+++ b/src/xmpp/contact.c
@@ -1,7 +1,7 @@
 /*
  * contact.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/contact.h b/src/xmpp/contact.h
index 73849351..170df2ca 100644
--- a/src/xmpp/contact.h
+++ b/src/xmpp/contact.h
@@ -1,7 +1,7 @@
 /*
  * contact.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/form.c b/src/xmpp/form.c
index e1bd4a93..6e26411a 100644
--- a/src/xmpp/form.c
+++ b/src/xmpp/form.c
@@ -1,7 +1,7 @@
 /*
  * form.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/form.h b/src/xmpp/form.h
index aa446017..21314ce2 100644
--- a/src/xmpp/form.h
+++ b/src/xmpp/form.h
@@ -1,7 +1,7 @@
 /*
  * form.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index edbf3557..59cff0db 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -1,7 +1,7 @@
 /*
  * iq.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -119,6 +119,7 @@ static int _caps_response_id_handler(xmpp_stanza_t *const stanza, void *const us
 static int _caps_response_for_jid_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 static int _caps_response_legacy_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 static int _auto_pong_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
+static int _room_list_id_handler(xmpp_stanza_t *const stanza, void *const userdata);
 
 static void _iq_free_room_data(ProfRoomInfoData *roominfo);
 static void _iq_free_affiliation_set(ProfPrivilegeSet *affiliation_set);
@@ -126,9 +127,13 @@ static void _iq_free_affiliation_set(ProfPrivilegeSet *affiliation_set);
 // scheduled
 static int _autoping_timed_send(xmpp_conn_t *const conn, void *const userdata);
 
+static void _identity_destroy(DiscoIdentity *identity);
+static void _item_destroy(DiscoItem *item);
+
 static gboolean autoping_wait = FALSE;
 static GTimer *autoping_time = NULL;
 static GHashTable *id_handlers;
+static GHashTable *rooms_cache = NULL;
 
 static int
 _iq_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata)
@@ -232,6 +237,7 @@ iq_handlers_init(void)
         g_hash_table_destroy(id_handlers);
     }
     id_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);
+    rooms_cache = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)xmpp_stanza_release);
 }
 
 void
@@ -293,10 +299,30 @@ iq_set_autoping(const int seconds)
 }
 
 void
-iq_room_list_request(gchar *conferencejid)
+iq_rooms_cache_clear(void)
 {
+    if (rooms_cache) {
+        g_hash_table_remove_all(rooms_cache);
+    }
+}
+
+void
+iq_room_list_request(gchar *conferencejid, gchar *filter)
+{
+    if (g_hash_table_contains(rooms_cache, conferencejid)) {
+        log_debug("Rooms request cached for: %s", conferencejid);
+        _room_list_id_handler(g_hash_table_lookup(rooms_cache, conferencejid), filter);
+        return;
+    }
+
+    log_debug("Rooms request not cached for: %s", conferencejid);
+
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, "confreq", conferencejid);
+    char *id = create_unique_id("confreq");
+    xmpp_stanza_t *iq = stanza_create_disco_items_iq(ctx, id, conferencejid);
+
+    iq_id_handler_add(id, _room_list_id_handler, NULL, filter);
+
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
 }
@@ -891,6 +917,101 @@ _caps_response_legacy_id_handler(xmpp_stanza_t *const stanza, void *const userda
 }
 
 static int
+_room_list_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    gchar *filter = (gchar*)userdata;
+    const char *id = xmpp_stanza_get_id(stanza);
+    const char *from = xmpp_stanza_get_from(stanza);
+
+    if (prefs_get_boolean(PREF_ROOM_LIST_CACHE) && !g_hash_table_contains(rooms_cache, from)) {
+        g_hash_table_insert(rooms_cache, strdup(from), xmpp_stanza_copy(stanza));
+    }
+
+    log_debug("Response to query: %s", id);
+
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+    if (query == NULL) {
+        g_free(filter);
+        return 0;
+    }
+
+    cons_show("");
+    if (filter) {
+        cons_show("Rooms list response received: %s, filter: %s", from, filter);
+    } else {
+        cons_show("Rooms list response received: %s", from);
+    }
+    xmpp_stanza_t *child = xmpp_stanza_get_children(query);
+    if (child == NULL) {
+        cons_show("  No rooms found.");
+        g_free(filter);
+        return 0;
+    }
+
+    GPatternSpec *glob = NULL;
+    if (filter != NULL) {
+        gchar *filter_lower = g_utf8_strdown(filter, -1);
+        GString *glob_str = g_string_new("*");
+        g_string_append(glob_str, filter_lower);
+        g_free(filter_lower);
+        g_string_append(glob_str, "*");
+        glob = g_pattern_spec_new(glob_str->str);
+        g_string_free(glob_str, TRUE);
+    }
+
+    gboolean matched = FALSE;
+    while (child) {
+        const char *stanza_name = xmpp_stanza_get_name(child);
+        if (stanza_name && (g_strcmp0(stanza_name, STANZA_NAME_ITEM) == 0)) {
+            const char *item_jid = xmpp_stanza_get_attribute(child, STANZA_ATTR_JID);
+            gchar *item_jid_lower = NULL;
+            if (item_jid) {
+                Jid *jidp = jid_create(item_jid);
+                if (jidp && jidp->localpart) {
+                    item_jid_lower = g_utf8_strdown(jidp->localpart, -1);
+                }
+                jid_destroy(jidp);
+            }
+            const char *item_name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
+            gchar *item_name_lower = NULL;
+            if (item_name) {
+                item_name_lower = g_utf8_strdown(item_name, -1);
+            }
+            if ((item_jid_lower) && ((glob == NULL) ||
+                ((g_pattern_match(glob, strlen(item_jid_lower), item_jid_lower, NULL)) ||
+                (item_name_lower && g_pattern_match(glob, strlen(item_name_lower), item_name_lower, NULL))))) {
+
+                if (glob) {
+                    matched = TRUE;
+                }
+                GString *item = g_string_new(item_jid);
+                if (item_name) {
+                    g_string_append(item, " (");
+                    g_string_append(item, item_name);
+                    g_string_append(item, ")");
+                }
+                cons_show("  %s", item->str);
+                g_string_free(item, TRUE);
+            }
+            g_free(item_jid_lower);
+            g_free(item_name_lower);
+        }
+        child = xmpp_stanza_get_next(child);
+    }
+
+    if (glob && matched == FALSE) {
+        cons_show("  No rooms found matching filter: %s", filter);
+    }
+
+    if (glob) {
+        g_pattern_spec_free(glob);
+    }
+    g_free(filter);
+
+    return 0;
+}
+
+static int
 _enable_carbons_id_handler(xmpp_stanza_t *const stanza, void *const userdata)
 {
     const char *type = xmpp_stanza_get_type(stanza);
@@ -967,6 +1088,15 @@ _autoping_timed_send(xmpp_conn_t *const conn, void *const userdata)
         return 1;
     }
 
+    if (connection_supports(XMPP_FEATURE_PING) == FALSE) {
+        log_warning("Server doesn't advertise %s feature, disabling autoping.", XMPP_FEATURE_PING);
+        prefs_set_autoping(0);
+        cons_show_error("Server ping not supported, autoping disabled.");
+        xmpp_conn_t *conn = connection_get_conn();
+        xmpp_timed_handler_delete(conn, _autoping_timed_send);
+        return 1;
+    }
+
     if (autoping_wait) {
         log_debug("Autoping: Existing ping already in progress, aborting");
         return 1;
@@ -1983,8 +2113,7 @@ _disco_items_result_handler(xmpp_stanza_t *const stanza)
     const char *from = xmpp_stanza_get_from(stanza);
     GSList *items = NULL;
 
-    if ((g_strcmp0(id, "confreq") != 0) &&
-            (g_strcmp0(id, "discoitemsreq") != 0) &&
+    if ((g_strcmp0(id, "discoitemsreq") != 0) &&
             (g_strcmp0(id, "discoitemsreq_onconnect") != 0)) {
         return;
     }
@@ -2021,9 +2150,7 @@ _disco_items_result_handler(xmpp_stanza_t *const stanza)
         child = xmpp_stanza_get_next(child);
     }
 
-    if (g_strcmp0(id, "confreq") == 0) {
-        cons_show_room_list(items, from);
-    } else if (g_strcmp0(id, "discoitemsreq") == 0) {
+    if (g_strcmp0(id, "discoitemsreq") == 0) {
         cons_show_disco_items(items, from);
     } else if (g_strcmp0(id, "discoitemsreq_onconnect") == 0) {
         connection_set_disco_items(items);
diff --git a/src/xmpp/iq.h b/src/xmpp/iq.h
index e56d2f59..b06568b6 100644
--- a/src/xmpp/iq.h
+++ b/src/xmpp/iq.h
@@ -1,7 +1,7 @@
 /*
  * iq.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/jid.c b/src/xmpp/jid.c
index ac4cdcdb..0af54c72 100644
--- a/src/xmpp/jid.c
+++ b/src/xmpp/jid.c
@@ -1,7 +1,7 @@
 /*
  * jid.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/jid.h b/src/xmpp/jid.h
index 0ce0ec5b..e4e4d272 100644
--- a/src/xmpp/jid.h
+++ b/src/xmpp/jid.h
@@ -1,7 +1,7 @@
 /*
  * jid.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 4afbe4b1..9f665892 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -1,7 +1,7 @@
 /*
  * message.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/message.h b/src/xmpp/message.h
index b8ba039e..47bb17ed 100644
--- a/src/xmpp/message.h
+++ b/src/xmpp/message.h
@@ -1,7 +1,7 @@
 /*
  * message.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/muc.c b/src/xmpp/muc.c
index 69cacba1..9f2f9f61 100644
--- a/src/xmpp/muc.c
+++ b/src/xmpp/muc.c
@@ -1,7 +1,7 @@
 /*
  * muc.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -69,6 +69,7 @@ typedef struct _muc_room_t {
 GHashTable *rooms = NULL;
 GHashTable *invite_passwords = NULL;
 Autocomplete invite_ac;
+Autocomplete confservers_ac;
 
 static void _free_room(ChatRoom *room);
 static gint _compare_occupants(Occupant *a, Occupant *b);
@@ -84,6 +85,7 @@ void
 muc_init(void)
 {
     invite_ac = autocomplete_new();
+    confservers_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);
 }
@@ -92,6 +94,7 @@ void
 muc_close(void)
 {
     autocomplete_free(invite_ac);
+    autocomplete_free(confservers_ac);
     g_hash_table_destroy(rooms);
     g_hash_table_destroy(invite_passwords);
     rooms = NULL;
@@ -99,6 +102,12 @@ muc_close(void)
 }
 
 void
+muc_confserver_add(const char *const server)
+{
+    autocomplete_add(confservers_ac, server);
+}
+
+void
 muc_invites_add(const char *const room, const char *const password)
 {
     autocomplete_add(invite_ac, room);
@@ -156,12 +165,24 @@ muc_invites_reset_ac(void)
     autocomplete_reset(invite_ac);
 }
 
+void
+muc_confserver_reset_ac(void)
+{
+    autocomplete_reset(confservers_ac);
+}
+
 char*
 muc_invites_find(const char *const search_str, gboolean previous)
 {
     return autocomplete_complete(invite_ac, search_str, TRUE, previous);
 }
 
+char*
+muc_confserver_find(const char *const search_str, gboolean previous)
+{
+    return autocomplete_complete(confservers_ac, search_str, TRUE, previous);
+}
+
 void
 muc_invites_clear(void)
 {
@@ -172,6 +193,12 @@ muc_invites_clear(void)
 }
 
 void
+muc_confserver_clear(void)
+{
+    autocomplete_clear(confservers_ac);
+}
+
+void
 muc_join(const char *const room, const char *const nick, const char *const password, gboolean autojoin)
 {
     ChatRoom *new_room = malloc(sizeof(ChatRoom));
@@ -665,43 +692,47 @@ muc_roster_nick_change_complete(const char *const room, const char *const nick)
 char*
 muc_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
-    if (window->type == WIN_MUC) {
-        ProfMucWin *mucwin = (ProfMucWin*)window;
-        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
-        ChatRoom *chat_room = g_hash_table_lookup(rooms, mucwin->roomjid);
+    if (window->type != WIN_MUC) {
+        return NULL;
+    }
 
-        if (chat_room && chat_room->nick_ac) {
-            const char * search_str = NULL;
+    ProfMucWin *mucwin = (ProfMucWin*)window;
+    assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+    ChatRoom *chat_room = g_hash_table_lookup(rooms, mucwin->roomjid);
+    if (chat_room == NULL || chat_room->nick_ac == NULL) {
+        return NULL;
+    }
 
-            gchar *last_space = g_strrstr(input, " ");
-            if (!last_space) {
-                search_str = input;
-                if (!chat_room->autocomplete_prefix) {
-                    chat_room->autocomplete_prefix = strdup("");
-                }
-            } else {
-                search_str = last_space+1;
-                if (!chat_room->autocomplete_prefix) {
-                    chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
-                }
-            }
+    const char * search_str = NULL;
 
-            char *result = autocomplete_complete(chat_room->nick_ac, search_str, FALSE, previous);
-            if (result) {
-                GString *replace_with = g_string_new(chat_room->autocomplete_prefix);
-                g_string_append(replace_with, result);
-                if (!last_space || (*(last_space+1) == '\0')) {
-                    g_string_append(replace_with, ": ");
-                }
-                g_free(result);
-                result = replace_with->str;
-                g_string_free(replace_with, FALSE);
-                return result;
-            }
+    gchar *last_space = g_strrstr(input, " ");
+    if (!last_space) {
+        search_str = input;
+        if (!chat_room->autocomplete_prefix) {
+            chat_room->autocomplete_prefix = strdup("");
+        }
+    } else {
+        search_str = last_space+1;
+        if (!chat_room->autocomplete_prefix) {
+            chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
         }
     }
 
-    return NULL;
+    char *result = autocomplete_complete(chat_room->nick_ac, search_str, FALSE, previous);
+    if (result == NULL) {
+        return NULL;
+    }
+
+    GString *replace_with = g_string_new(chat_room->autocomplete_prefix);
+    g_string_append(replace_with, result);
+
+    if (strlen(chat_room->autocomplete_prefix) == 0) {
+        g_string_append(replace_with, ": ");
+    }
+    g_free(result);
+    result = replace_with->str;
+    g_string_free(replace_with, FALSE);
+    return result;
 }
 
 void
diff --git a/src/xmpp/muc.h b/src/xmpp/muc.h
index 6c6408f3..e50e2a87 100644
--- a/src/xmpp/muc.h
+++ b/src/xmpp/muc.h
@@ -1,7 +1,7 @@
 /*
  * muc.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -117,6 +117,11 @@ GSList* muc_occupants_by_affiliation(const char *const room, muc_affiliation_t a
 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_confserver_add(const char *const server);
+void muc_confserver_reset_ac(void);
+char* muc_confserver_find(const char *const search_str, gboolean previous);
+void muc_confserver_clear(void);
+
 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);
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 97e3d461..8efb60e0 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -1,7 +1,7 @@
 /*
  * presence.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/presence.h b/src/xmpp/presence.h
index 4415be1b..6045f72c 100644
--- a/src/xmpp/presence.h
+++ b/src/xmpp/presence.h
@@ -1,7 +1,7 @@
 /*
  * presence.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/resource.c b/src/xmpp/resource.c
index 154f8490..734de3a0 100644
--- a/src/xmpp/resource.c
+++ b/src/xmpp/resource.c
@@ -1,7 +1,7 @@
 /*
  * resource.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/resource.h b/src/xmpp/resource.h
index 8acc5207..9376a51c 100644
--- a/src/xmpp/resource.h
+++ b/src/xmpp/resource.h
@@ -1,7 +1,7 @@
 /*
  * resource.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index ba152410..e7d9cfb2 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -1,7 +1,7 @@
 /*
  * roster.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/roster.h b/src/xmpp/roster.h
index 806c7316..15614377 100644
--- a/src/xmpp/roster.h
+++ b/src/xmpp/roster.h
@@ -1,7 +1,7 @@
 /*
  * roster.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/roster_list.c b/src/xmpp/roster_list.c
index 839ea0b8..a2c5653d 100644
--- a/src/xmpp/roster_list.c
+++ b/src/xmpp/roster_list.c
@@ -1,7 +1,7 @@
 /*
  * roster_list.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/roster_list.h b/src/xmpp/roster_list.h
index 4e6b1d47..a0b01625 100644
--- a/src/xmpp/roster_list.h
+++ b/src/xmpp/roster_list.h
@@ -1,7 +1,7 @@
 /*
  * roster_list.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/session.c b/src/xmpp/session.c
index 633af31f..e06b03f1 100644
--- a/src/xmpp/session.c
+++ b/src/xmpp/session.c
@@ -1,7 +1,7 @@
 /*
  * session.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -219,6 +219,8 @@ session_disconnect(void)
 
         accounts_set_last_activity(session_get_account_name());
 
+        iq_rooms_cache_clear();
+
         connection_disconnect();
 
         _session_free_saved_account();
diff --git a/src/xmpp/session.h b/src/xmpp/session.h
index a8c6ab83..a3413e9e 100644
--- a/src/xmpp/session.h
+++ b/src/xmpp/session.h
@@ -1,7 +1,7 @@
 /*
  * session.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 4dd0a2d4..2f690828 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -1,7 +1,7 @@
 /*
  * stanza.c
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 5da1c2c3..bd161616 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -1,7 +1,7 @@
 /*
  * stanza.h
  *
- * Copyright (C) 2012 - 20172017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 2befccd2..d4a29196 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -1,7 +1,7 @@
 /*
  * xmpp.h
  *
- * Copyright (C) 2012 - 2017 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2012 - 2018 James Booth <boothj5@gmail.com>
  *
  * This file is part of Profanity.
  *
@@ -55,6 +55,7 @@
 #define JABBER_PRIORITY_MIN -128
 #define JABBER_PRIORITY_MAX 127
 
+#define XMPP_FEATURE_PING "urn:xmpp:ping"
 #define XMPP_FEATURE_BLOCKING "urn:xmpp:blocking"
 #define XMPP_FEATURE_RECEIPTS "urn:xmpp:receipts"
 #define XMPP_FEATURE_LASTACTIVITY "jabber:iq:last"
@@ -160,7 +161,8 @@ gboolean presence_sub_request_exists(const char *const bare_jid);
 void iq_enable_carbons(void);
 void iq_disable_carbons(void);
 void iq_send_software_version(const char *const fulljid);
-void iq_room_list_request(gchar *conferencejid);
+void iq_rooms_cache_clear(void);
+void iq_room_list_request(gchar *conferencejid, gchar *filter);
 void iq_disco_info_request(gchar *jid);
 void iq_disco_items_request(gchar *jid);
 void iq_last_activity_request(gchar *jid);
diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c
index 1e01071d..5e8656b7 100644
--- a/tests/functionaltests/functionaltests.c
+++ b/tests/functionaltests/functionaltests.c
@@ -35,8 +35,11 @@ int main(int argc, char* argv[]) {
         PROF_FUNC_TEST(connect_bad_password),
         PROF_FUNC_TEST(connect_shows_presence_updates),
 
-        PROF_FUNC_TEST(ping_multiple),
-        PROF_FUNC_TEST(ping_responds),
+        PROF_FUNC_TEST(ping_server),
+        PROF_FUNC_TEST(ping_server_not_supported),
+        PROF_FUNC_TEST(ping_responds_to_server_request),
+        PROF_FUNC_TEST(ping_jid),
+        PROF_FUNC_TEST(ping_jid_not_supported),
 
         PROF_FUNC_TEST(rooms_query),
 
@@ -98,6 +101,8 @@ int main(int argc, char* argv[]) {
         PROF_FUNC_TEST(shows_history_message),
         PROF_FUNC_TEST(shows_occupant_join),
         PROF_FUNC_TEST(shows_message),
+        PROF_FUNC_TEST(shows_me_message_from_occupant),
+        PROF_FUNC_TEST(shows_me_message_from_self),
         PROF_FUNC_TEST(shows_all_messages_in_console_when_window_not_focussed),
         PROF_FUNC_TEST(shows_first_message_in_console_when_window_not_focussed),
         PROF_FUNC_TEST(shows_no_message_in_console_when_window_not_focussed),
diff --git a/tests/functionaltests/test_muc.c b/tests/functionaltests/test_muc.c
index e7fc8dcb..3aac9988 100644
--- a/tests/functionaltests/test_muc.c
+++ b/tests/functionaltests/test_muc.c
@@ -209,6 +209,60 @@ shows_message(void **state)
 }
 
 void
+shows_me_message_from_occupant(void **state)
+{
+    prof_connect();
+
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
+            "<x xmlns='http://jabber.org/protocol/muc#user'>"
+                "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
+            "</x>"
+            "<status code='110'/>"
+        "</presence>"
+    );
+
+    prof_input("/join testroom@conference.localhost");
+    assert_true(prof_output_exact("-> You have joined the room as stabber, role: participant, affiliation: none"));
+
+    stbbr_send(
+        "<message type='groupchat' to='stabber@localhost/profanity' from='testroom@conference.localhost/testoccupant'>"
+            "<body>/me did something</body>"
+        "</message>"
+    );
+
+    assert_true(prof_output_exact("*testoccupant did something"));
+}
+
+void
+shows_me_message_from_self(void **state)
+{
+    prof_connect();
+
+    stbbr_for_id("prof_join_4",
+        "<presence id='prof_join_4' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+            "<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://www.profanity.im' ver='*'/>"
+            "<x xmlns='http://jabber.org/protocol/muc#user'>"
+                "<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
+            "</x>"
+            "<status code='110'/>"
+        "</presence>"
+    );
+
+    prof_input("/join testroom@conference.localhost");
+    assert_true(prof_output_exact("-> You have joined the room as stabber, role: participant, affiliation: none"));
+
+    stbbr_send(
+        "<message type='groupchat' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
+            "<body>/me did something</body>"
+        "</message>"
+    );
+
+    assert_true(prof_output_exact("*stabber did something"));
+}
+
+void
 shows_all_messages_in_console_when_window_not_focussed(void **state)
 {
     prof_connect();
diff --git a/tests/functionaltests/test_muc.h b/tests/functionaltests/test_muc.h
index 4f3c4f5d..1636bd05 100644
--- a/tests/functionaltests/test_muc.h
+++ b/tests/functionaltests/test_muc.h
@@ -7,6 +7,8 @@ void shows_subject_on_join(void **state);
 void shows_history_message(void **state);
 void shows_occupant_join(void **state);
 void shows_message(void **state);
+void shows_me_message_from_occupant(void **state);
+void shows_me_message_from_self(void **state);
 void shows_all_messages_in_console_when_window_not_focussed(void **state);
 void shows_first_message_in_console_when_window_not_focussed(void **state);
 void shows_no_message_in_console_when_window_not_focussed(void **state);
diff --git a/tests/functionaltests/test_ping.c b/tests/functionaltests/test_ping.c
index ecbbfee1..5bb937b6 100644
--- a/tests/functionaltests/test_ping.c
+++ b/tests/functionaltests/test_ping.c
@@ -12,8 +12,17 @@
 #include "proftest.h"
 
 void
-ping_multiple(void **state)
+ping_server(void **state)
 {
+    stbbr_for_id("prof_disco_info_onconnect_2",
+        "<iq id='prof_disco_info_onconnect_2' to='stabber@localhost/profanity' type='result' from='localhost'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info'>"
+                "<identity category='server' type='im' name='Prosody'/>"
+                "<feature var='urn:xmpp:ping'/>"
+            "</query>"
+        "</iq>"
+    );
+
     stbbr_for_id("prof_ping_4",
         "<iq id='prof_ping_4' type='result' to='stabber@localhost/profanity'/>"
     );
@@ -41,7 +50,24 @@ ping_multiple(void **state)
 }
 
 void
-ping_responds(void **state)
+ping_server_not_supported(void **state)
+{
+    stbbr_for_id("prof_disco_info_onconnect_2",
+        "<iq id='prof_disco_info_onconnect_2' to='stabber@localhost/profanity' type='result' from='localhost'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info'>"
+                "<identity category='server' type='im' name='Stabber'/>"
+            "</query>"
+        "</iq>"
+    );
+
+    prof_connect();
+
+    prof_input("/ping");
+    assert_true(prof_output_exact("Server does not support ping requests."));
+}
+
+void
+ping_responds_to_server_request(void **state)
 {
     prof_connect();
 
@@ -55,3 +81,90 @@ ping_responds(void **state)
         "<iq id='pingtest1' type='result' from='stabber@localhost/profanity' to='localhost'/>"
     ));
 }
+
+void ping_jid(void **state)
+{
+    stbbr_for_id("prof_caps_4",
+        "<iq id='prof_caps_4' to='stabber@localhost/profanity' type='result' from='buddy1@localhost/mobile'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info' node='http://www.profanity.im#LpT2xs3nun7jC2sq4gg3WRDQFZ4='>"
+                "<identity category='client' type='console' name='Profanity0.6.0'/>"
+                "<feature var='urn:xmpp:ping'/>"
+                "<feature var='http://jabber.org/protocol/disco#info'/>"
+                "<feature var='http://jabber.org/protocol/caps'/>"
+            "</query>"
+        "</iq>"
+    );
+
+    prof_connect();
+
+    stbbr_send(
+        "<presence to='stabber@localhost' from='buddy1@localhost/mobile'>"
+            "<priority>10</priority>"
+            "<status>I'm here</status>"
+            "<c "
+                "hash='sha-1' "
+                "xmlns='http://jabber.org/protocol/caps' "
+                "node='http://www.profanity.im' "
+                "ver='LpT2xs3nun7jC2sq4gg3WRDQFZ4='"
+            "/>"
+        "</presence>"
+    );
+    assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\""));
+
+    assert_true(stbbr_received(
+        "<iq id='prof_caps_4' to='buddy1@localhost/mobile' type='get'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info' node='http://www.profanity.im#LpT2xs3nun7jC2sq4gg3WRDQFZ4='/>"
+        "</iq>"
+    ));
+
+    stbbr_for_id("prof_ping_5",
+        "<iq from='buddy1@localhost/mobile' to='stabber@localhost' id='prof_ping_5' type='result'/>"
+    );
+
+    prof_input("/ping buddy1@localhost/mobile");
+
+    assert_true(stbbr_received(
+        "<iq id='prof_ping_5' type='get' to='buddy1@localhost/mobile'>"
+            "<ping xmlns='urn:xmpp:ping'/>"
+        "</iq>"
+    ));
+    assert_true(prof_output_exact("Ping response from buddy1@localhost/mobile"));
+}
+
+void ping_jid_not_supported(void **state)
+{
+    stbbr_for_id("prof_caps_4",
+        "<iq id='prof_caps_4' to='stabber@localhost/profanity' type='result' from='buddy1@localhost/mobile'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info' node='http://www.profanity.im#LpT2xs3nun7jC2sq4gg3WRDQFZ4='>"
+                "<identity category='client' type='console' name='Profanity0.6.0'/>"
+                "<feature var='http://jabber.org/protocol/disco#info'/>"
+                "<feature var='http://jabber.org/protocol/caps'/>"
+            "</query>"
+        "</iq>"
+    );
+
+    prof_connect();
+
+    stbbr_send(
+        "<presence to='stabber@localhost' from='buddy1@localhost/mobile'>"
+            "<priority>10</priority>"
+            "<status>I'm here</status>"
+            "<c "
+                "hash='sha-1' "
+                "xmlns='http://jabber.org/protocol/caps' "
+                "node='http://www.profanity.im' "
+                "ver='LpT2xs3nun7jC2sq4gg3WRDQFZ4='"
+            "/>"
+        "</presence>"
+    );
+    assert_true(prof_output_exact("Buddy1 (mobile) is online, \"I'm here\""));
+
+    assert_true(stbbr_received(
+        "<iq id='prof_caps_4' to='buddy1@localhost/mobile' type='get'>"
+            "<query xmlns='http://jabber.org/protocol/disco#info' node='http://www.profanity.im#LpT2xs3nun7jC2sq4gg3WRDQFZ4='/>"
+        "</iq>"
+    ));
+
+    prof_input("/ping buddy1@localhost/mobile");
+    assert_true(prof_output_exact("buddy1@localhost/mobile does not support ping requests."));
+}
diff --git a/tests/functionaltests/test_ping.h b/tests/functionaltests/test_ping.h
index a222a486..1f2eeb91 100644
--- a/tests/functionaltests/test_ping.h
+++ b/tests/functionaltests/test_ping.h
@@ -1,2 +1,5 @@
-void ping_multiple(void **state);
-void ping_responds(void **state);
+void ping_server(void **state);
+void ping_server_not_supported(void **state);
+void ping_responds_to_server_request(void **state);
+void ping_jid(void **state);
+void ping_jid_not_supported(void **state);
diff --git a/tests/functionaltests/test_rooms.c b/tests/functionaltests/test_rooms.c
index c0103279..49b1d892 100644
--- a/tests/functionaltests/test_rooms.c
+++ b/tests/functionaltests/test_rooms.c
@@ -14,8 +14,8 @@
 void
 rooms_query(void **state)
 {
-    stbbr_for_id("confreq",
-        "<iq id='confreq' type='result' to='stabber@localhost/profanity' from='conference.localhost'>"
+    stbbr_for_id("prof_confreq_4",
+        "<iq id='prof_confreq_4' type='result' to='stabber@localhost/profanity' from='conference.localhost'>"
             "<query xmlns='http://jabber.org/protocol/disco#items'>"
                 "<item jid='chatroom@conference.localhost' name='A chat room'/>"
                 "<item jid='hangout@conference.localhost' name='Another chat room'/>"
@@ -25,13 +25,13 @@ rooms_query(void **state)
 
     prof_connect();
 
-    prof_input("/rooms conference.localhost");
+    prof_input("/rooms service conference.localhost");
 
-    assert_true(prof_output_exact("chatroom@conference.localhost, (A chat room)"));
-    assert_true(prof_output_exact("hangout@conference.localhost, (Another chat room)"));
+    assert_true(prof_output_exact("chatroom@conference.localhost (A chat room)"));
+    assert_true(prof_output_exact("hangout@conference.localhost (Another chat room)"));
 
     assert_true(stbbr_last_received(
-        "<iq id='confreq' to='conference.localhost' type='get'>"
+        "<iq id='prof_confreq_4' to='conference.localhost' type='get'>"
             "<query xmlns='http://jabber.org/protocol/disco#items'/>"
         "</iq>"
     ));
diff --git a/tests/unittests/test_cmd_bookmark.c b/tests/unittests/test_cmd_bookmark.c
index 901d31f8..fb1f7996 100644
--- a/tests/unittests/test_cmd_bookmark.c
+++ b/tests/unittests/test_cmd_bookmark.c
@@ -24,10 +24,12 @@
 
 static void test_with_connection_status(jabber_conn_status_t status)
 {
+    ProfWin window;
+    window.type = WIN_CONSOLE;
     will_return(connection_get_status, status);
     expect_cons_show("You are not currently connected.");
 
-    gboolean result = cmd_bookmark(NULL, CMD_BOOKMARK, NULL);
+    gboolean result = cmd_bookmark(&window, CMD_BOOKMARK, NULL);
     assert_true(result);
 }
 
@@ -165,6 +167,85 @@ void cmd_bookmark_add_adds_bookmark_with_jid(void **state)
     assert_true(result);
 }
 
+void cmd_bookmark_uses_roomjid_in_room(void **state)
+{
+    muc_init();
+
+    gchar *args[] = { NULL };
+    ProfMucWin muc_win;
+    muc_win.window.type = WIN_MUC;
+    muc_win.memcheck = PROFMUCWIN_MEMCHECK;
+    muc_win.roomjid = "room@conf.server";
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+
+    expect_string(bookmark_add, jid, muc_win.roomjid);
+    expect_any(bookmark_add, nick);
+    expect_any(bookmark_add, password);
+    expect_any(bookmark_add, autojoin_str);
+    will_return(bookmark_add, TRUE);
+
+    expect_win_println("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(&muc_win, CMD_BOOKMARK, args);
+    assert_true(result);
+
+    muc_close();
+}
+
+void cmd_bookmark_add_uses_roomjid_in_room(void **state)
+{
+    muc_init();
+
+    gchar *args[] = { "add", NULL };
+    ProfMucWin muc_win;
+    muc_win.window.type = WIN_MUC;
+    muc_win.memcheck = PROFMUCWIN_MEMCHECK;
+    muc_win.roomjid = "room@conf.server";
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+
+    expect_string(bookmark_add, jid, muc_win.roomjid);
+    expect_any(bookmark_add, nick);
+    expect_any(bookmark_add, password);
+    expect_any(bookmark_add, autojoin_str);
+    will_return(bookmark_add, TRUE);
+
+    expect_win_println("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(&muc_win, CMD_BOOKMARK, args);
+    assert_true(result);
+
+    muc_close();
+}
+
+void cmd_bookmark_add_uses_supplied_jid_in_room(void **state)
+{
+    muc_init();
+
+    char *jid = "room1@conf.server";
+    gchar *args[] = { "add", jid, NULL };
+    ProfMucWin muc_win;
+    muc_win.window.type = WIN_MUC;
+    muc_win.memcheck = PROFMUCWIN_MEMCHECK;
+    muc_win.roomjid = "room2@conf.server";
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+
+    expect_string(bookmark_add, jid, jid);
+    expect_any(bookmark_add, nick);
+    expect_any(bookmark_add, password);
+    expect_any(bookmark_add, autojoin_str);
+    will_return(bookmark_add, TRUE);
+
+    expect_cons_show("Bookmark added for room1@conf.server.");
+
+    gboolean result = cmd_bookmark(&muc_win, CMD_BOOKMARK, args);
+    assert_true(result);
+
+    muc_close();
+}
+
 void cmd_bookmark_add_adds_bookmark_with_jid_nick(void **state)
 {
     char *jid = "room@conf.server";
@@ -265,3 +346,50 @@ void cmd_bookmark_remove_shows_message_when_no_bookmark(void **state)
     gboolean result = cmd_bookmark(&window, CMD_BOOKMARK, args);
     assert_true(result);
 }
+
+void cmd_bookmark_remove_uses_roomjid_in_room(void **state)
+{
+    muc_init();
+
+    gchar *args[] = { "remove", NULL };
+    ProfMucWin muc_win;
+    muc_win.window.type = WIN_MUC;
+    muc_win.memcheck = PROFMUCWIN_MEMCHECK;
+    muc_win.roomjid = "room@conf.server";
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+
+    expect_string(bookmark_remove, jid, muc_win.roomjid);
+    will_return(bookmark_remove, TRUE);
+
+    expect_win_println("Bookmark removed for room@conf.server.");
+
+    gboolean result = cmd_bookmark(&muc_win, CMD_BOOKMARK, args);
+    assert_true(result);
+
+    muc_close();
+}
+
+void cmd_bookmark_remove_uses_supplied_jid_in_room(void **state)
+{
+    muc_init();
+
+    char *jid = "room1@conf.server";
+    gchar *args[] = { "remove", jid, NULL };
+    ProfMucWin muc_win;
+    muc_win.window.type = WIN_MUC;
+    muc_win.memcheck = PROFMUCWIN_MEMCHECK;
+    muc_win.roomjid = "room2@conf.server";
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+
+    expect_string(bookmark_remove, jid, jid);
+    will_return(bookmark_remove, TRUE);
+
+    expect_cons_show("Bookmark removed for room1@conf.server.");
+
+    gboolean result = cmd_bookmark(&muc_win, CMD_BOOKMARK, args);
+    assert_true(result);
+
+    muc_close();
+}
diff --git a/tests/unittests/test_cmd_bookmark.h b/tests/unittests/test_cmd_bookmark.h
index abbdbe98..195882b5 100644
--- a/tests/unittests/test_cmd_bookmark.h
+++ b/tests/unittests/test_cmd_bookmark.h
@@ -6,6 +6,11 @@ void cmd_bookmark_shows_usage_when_no_args(void **state);
 void cmd_bookmark_list_shows_bookmarks(void **state);
 void cmd_bookmark_add_shows_message_when_invalid_jid(void **state);
 void cmd_bookmark_add_adds_bookmark_with_jid(void **state);
+void cmd_bookmark_uses_roomjid_in_room(void **state);
+void cmd_bookmark_add_uses_roomjid_in_room(void **state);
+void cmd_bookmark_add_uses_supplied_jid_in_room(void **state);
+void cmd_bookmark_remove_uses_roomjid_in_room(void **state);
+void cmd_bookmark_remove_uses_supplied_jid_in_room(void **state);
 void cmd_bookmark_add_adds_bookmark_with_jid_nick(void **state);
 void cmd_bookmark_add_adds_bookmark_with_jid_autojoin(void **state);
 void cmd_bookmark_add_adds_bookmark_with_jid_nick_autojoin(void **state);
diff --git a/tests/unittests/test_cmd_rooms.c b/tests/unittests/test_cmd_rooms.c
index 2cb6be6b..b04e5618 100644
--- a/tests/unittests/test_cmd_rooms.c
+++ b/tests/unittests/test_cmd_rooms.c
@@ -53,19 +53,49 @@ void cmd_rooms_uses_account_default_when_no_arg(void **state)
     expect_any(accounts_get_account, name);
     will_return(accounts_get_account, account);
 
+    expect_cons_show("");
+    expect_cons_show("Room list request sent: default_conf_server");
+
     expect_string(iq_room_list_request, conferencejid, "default_conf_server");
+    expect_any(iq_room_list_request, filter);
 
     gboolean result = cmd_rooms(NULL, CMD_ROOMS, args);
     assert_true(result);
 }
 
-void cmd_rooms_arg_used_when_passed(void **state)
+void cmd_rooms_service_arg_used_when_passed(void **state)
 {
-    gchar *args[] = { "conf_server_arg" };
+    gchar *args[] = { "service", "conf_server_arg", NULL };
 
     will_return(connection_get_status, JABBER_CONNECTED);
 
+    expect_cons_show("");
+    expect_cons_show("Room list request sent: conf_server_arg");
+
     expect_string(iq_room_list_request, conferencejid, "conf_server_arg");
+    expect_any(iq_room_list_request, filter);
+
+    gboolean result = cmd_rooms(NULL, CMD_ROOMS, args);
+    assert_true(result);
+}
+
+void cmd_rooms_filter_arg_used_when_passed(void **state)
+{
+    gchar *args[] = { "filter", "text", NULL };
+
+    ProfAccount *account = account_new("testaccount", NULL, NULL, NULL, TRUE, NULL, 0, NULL, NULL, NULL,
+        0, 0, 0, 0, 0, strdup("default_conf_server"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    will_return(connection_get_status, JABBER_CONNECTED);
+    will_return(session_get_account_name, "account_name");
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_cons_show("");
+    expect_cons_show("Room list request sent: default_conf_server, filter: 'text'");
+
+    expect_any(iq_room_list_request, conferencejid);
+    expect_string(iq_room_list_request, filter, "text");
 
     gboolean result = cmd_rooms(NULL, CMD_ROOMS, args);
     assert_true(result);
diff --git a/tests/unittests/test_cmd_rooms.h b/tests/unittests/test_cmd_rooms.h
index 1b13cf07..4d656948 100644
--- a/tests/unittests/test_cmd_rooms.h
+++ b/tests/unittests/test_cmd_rooms.h
@@ -3,4 +3,5 @@ void cmd_rooms_shows_message_when_disconnecting(void **state);
 void cmd_rooms_shows_message_when_connecting(void **state);
 void cmd_rooms_shows_message_when_undefined(void **state);
 void cmd_rooms_uses_account_default_when_no_arg(void **state);
-void cmd_rooms_arg_used_when_passed(void **state);
+void cmd_rooms_service_arg_used_when_passed(void **state);
+void cmd_rooms_filter_arg_used_when_passed(void **state);
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index f2d8c5d7..420653e1 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -445,8 +445,10 @@ void cons_autoaway_setting(void) {}
 void cons_reconnect_setting(void) {}
 void cons_autoping_setting(void) {}
 void cons_autoconnect_setting(void) {}
+void cons_rooms_cache_setting(void) {}
 void cons_inpblock_setting(void) {}
 void cons_winpos_setting(void) {}
+void cons_statusbar_setting(void) {}
 void cons_tray_setting(void) {}
 
 void cons_show_contact_online(PContact contact, Resource *resource, GDateTime *last_activity)
@@ -465,8 +467,8 @@ void title_bar_set_presence(contact_presence_t presence) {}
 
 // status bar
 void status_bar_inactive(const int win) {}
-void status_bar_active(const int win) {}
-void status_bar_new(const int win) {}
+void status_bar_active(const int win, win_type_t type, char *identifier) {}
+void status_bar_new(const int win, win_type_t type, char *identifier) {}
 void status_bar_set_all_inactive(void) {}
 
 // roster window
@@ -505,6 +507,11 @@ ProfWin* win_create_plugin(const char *const plugin_name, const char * const tag
     return NULL;
 }
 
+char* win_get_tab_identifier(ProfWin *window)
+{
+    return NULL;
+}
+
 void win_update_virtual(ProfWin *window) {}
 void win_free(ProfWin *window) {}
 gboolean win_notify_remind(ProfWin *window)
diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c
index 2938b9ff..4e3e149e 100644
--- a/tests/unittests/unittests.c
+++ b/tests/unittests/unittests.c
@@ -6,7 +6,9 @@
 #include <setjmp.h>
 #include <cmocka.h>
 #include <sys/stat.h>
+#include <stdlib.h>
 #include <locale.h>
+#include <langinfo.h>
 
 #include "config.h"
 #include "xmpp/chat_session.h"
@@ -38,7 +40,21 @@
 #include "test_plugins_disco.h"
 
 int main(int argc, char* argv[]) {
-    setlocale(LC_ALL, "");
+    setlocale(LC_ALL, "en_GB.UTF-8");
+    char *codeset = nl_langinfo(CODESET);
+    char *lang = getenv("LANG");
+
+    printf("Charset information:\n");
+
+    if (lang) {
+        printf("  LANG:       %s\n", lang);
+    }
+    if (codeset) {
+        printf("  CODESET:    %s\n", codeset);
+    }
+    printf("  MB_CUR_MAX: %d\n", MB_CUR_MAX);
+    printf("  MB_LEN_MAX: %d\n", MB_LEN_MAX);
+
     const UnitTest all_tests[] = {
 
         unit_test(replace_one_substr),
@@ -297,7 +313,8 @@ int main(int argc, char* argv[]) {
         unit_test(cmd_rooms_shows_message_when_disconnecting),
         unit_test(cmd_rooms_shows_message_when_connecting),
         unit_test(cmd_rooms_uses_account_default_when_no_arg),
-        unit_test(cmd_rooms_arg_used_when_passed),
+        unit_test(cmd_rooms_service_arg_used_when_passed),
+        unit_test(cmd_rooms_filter_arg_used_when_passed),
 
         unit_test(cmd_account_shows_usage_when_not_connected_and_no_args),
         unit_test(cmd_account_shows_account_when_connected_and_no_args),
@@ -476,6 +493,11 @@ int main(int argc, char* argv[]) {
         unit_test(cmd_bookmark_list_shows_bookmarks),
         unit_test(cmd_bookmark_add_shows_message_when_invalid_jid),
         unit_test(cmd_bookmark_add_adds_bookmark_with_jid),
+        unit_test(cmd_bookmark_uses_roomjid_in_room),
+        unit_test(cmd_bookmark_add_uses_roomjid_in_room),
+        unit_test(cmd_bookmark_add_uses_supplied_jid_in_room),
+        unit_test(cmd_bookmark_remove_uses_roomjid_in_room),
+        unit_test(cmd_bookmark_remove_uses_supplied_jid_in_room),
         unit_test(cmd_bookmark_add_adds_bookmark_with_jid_nick),
         unit_test(cmd_bookmark_add_adds_bookmark_with_jid_autojoin),
         unit_test(cmd_bookmark_add_adds_bookmark_with_jid_nick_autojoin),
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index 630c0b19..45db6649 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -172,9 +172,10 @@ void iq_disable_carbons() {};
 void iq_enable_carbons() {};
 void iq_send_software_version(const char * const fulljid) {}
 
-void iq_room_list_request(gchar *conferencejid)
+void iq_room_list_request(gchar *conferencejid, gchar *filter)
 {
     check_expected(conferencejid);
+    check_expected(filter);
 }
 
 void iq_disco_info_request(gchar *jid) {}
@@ -203,6 +204,7 @@ void iq_room_role_set(const char * const room, const char * const nick, char *ro
 void iq_room_role_list(const char * const room, char *role) {}
 void iq_last_activity_request(gchar *jid) {}
 void iq_autoping_check(void) {}
+void iq_rooms_cache_clear(void) {}
 
 // caps functions
 void caps_add_feature(char *feature) {}
diff --git a/themes/boothj5 b/themes/boothj5
index fd38f419..80566b5e 100644
--- a/themes/boothj5
+++ b/themes/boothj5
@@ -102,13 +102,12 @@ statuses.muc=none
 roster=true
 roster.offline=false
 roster.empty=false
-roster.by=group
+roster.by=none
 roster.order=presence
 roster.unread=after
 roster.priority=false
 roster.size=25
 roster.wrap=true
-roster.header.char=@
 roster.contact.indent=1
 roster.resource=true
 roster.resource.char=/
@@ -124,6 +123,7 @@ roster.rooms.order=name
 roster.rooms.unread=after
 roster.rooms.pos=last
 roster.rooms.by=none
+roster.rooms.server=false
 roster.rooms.private.char=/
 roster.private=room
 roster.count=unread
@@ -142,4 +142,11 @@ titlebar.position=1
 mainwin.position=2
 statusbar.position=3
 inputwin.position=4
+statusbar.self=user
+statusbar.chat=user
+statusbar.room=room
+statusbar.tabs=10
+statusbar.tablen=7
+statusbar.show.name=true
+statusbar.show.number=true
 
diff --git a/themes/boothj5_slack b/themes/boothj5_slack
index 5dd435d1..792fdef2 100644
--- a/themes/boothj5_slack
+++ b/themes/boothj5_slack
@@ -106,9 +106,8 @@ roster.by=none
 roster.order=presence
 roster.unread=after
 roster.priority=false
-roster.size=25
+roster.size=15
 roster.wrap=true
-roster.header.char=@
 roster.contact.indent=1
 roster.resource=false
 roster.presence=false
@@ -119,7 +118,8 @@ roster.rooms=true
 roster.rooms.order=name
 roster.rooms.unread=after
 roster.rooms.pos=first
-roster.rooms.by=service
+roster.rooms.by=none
+roster.rooms.server=false
 roster.rooms.private.char=/
 roster.private=room
 roster.count=unread