about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile.am8
-rw-r--r--src/command/command.c589
-rw-r--r--src/command/command.h13
-rw-r--r--src/command/commands.c6
-rw-r--r--src/command/history.c81
-rw-r--r--src/command/history.h40
-rw-r--r--src/common.c24
-rw-r--r--src/common.h3
-rw-r--r--src/config/accounts.c4
-rw-r--r--src/config/accounts.h4
-rw-r--r--src/config/preferences.c2
-rw-r--r--src/config/preferences.h2
-rw-r--r--src/config/theme.c2
-rw-r--r--src/contact.c2
-rw-r--r--src/main.c13
-rw-r--r--src/muc.c16
-rw-r--r--src/muc.h4
-rw-r--r--src/profanity.c118
-rw-r--r--src/roster_list.c8
-rw-r--r--src/roster_list.h8
-rw-r--r--src/tools/autocomplete.c54
-rw-r--r--src/tools/autocomplete.h10
-rw-r--r--src/tools/parser.c4
-rw-r--r--src/tools/parser.h4
-rw-r--r--src/ui/console.c2
-rw-r--r--src/ui/core.c182
-rw-r--r--src/ui/inputwin.c220
-rw-r--r--src/ui/inputwin.h5
-rw-r--r--src/ui/notifier.c78
-rw-r--r--src/ui/ui.h9
-rw-r--r--src/ui/window.c111
-rw-r--r--src/ui/window.h3
-rw-r--r--src/xmpp/bookmark.c2
-rw-r--r--src/xmpp/presence.c2
-rw-r--r--src/xmpp/xmpp.h4
-rw-r--r--tests/test_common.c43
-rw-r--r--tests/test_common.h6
-rw-r--r--tests/testsuite.c6
-rw-r--r--tests/ui/stub_ui.c9
-rw-r--r--tests/xmpp/stub_xmpp.c4
41 files changed, 866 insertions, 843 deletions
diff --git a/.gitignore b/.gitignore
index be11f141..7f15f3c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -64,3 +64,7 @@ profanity.workspace
 m4/
 test.sh
 clean-test.sh
+callgrind.out.*
+gen_docs.sh
+main_fragment.html
+toc_fragment.html
diff --git a/Makefile.am b/Makefile.am
index 9d44c003..d9fa9729 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -21,9 +21,9 @@ core_sources = \
 	src/ui/windows.c src/ui/windows.h \
 	src/ui/rosterwin.c src/ui/occupantswin.c \
 	src/ui/buffer.c src/ui/buffer.h \
-	src/command/command.h src/command/command.c src/command/history.c \
+	src/command/command.h src/command/command.c \
 	src/command/commands.h src/command/commands.c \
-	src/command/history.h src/tools/parser.c \
+	src/tools/parser.c \
 	src/tools/parser.h \
 	src/tools/p_sha1.h src/tools/p_sha1.c \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
@@ -44,9 +44,9 @@ tests_sources = \
 	src/roster_list.c src/roster_list.h \
 	src/xmpp/xmpp.h src/xmpp/form.c \
 	src/ui/ui.h \
-	src/command/command.h src/command/command.c src/command/history.c \
+	src/command/command.h src/command/command.c \
 	src/command/commands.h src/command/commands.c \
-	src/command/history.h src/tools/parser.c \
+	src/tools/parser.c \
 	src/tools/parser.h \
 	src/tools/p_sha1.h src/tools/p_sha1.c \
 	src/tools/autocomplete.c src/tools/autocomplete.h \
diff --git a/src/command/command.c b/src/command/command.c
index f18191bc..4a8c73e9 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -36,6 +36,7 @@
 #include <errno.h>
 #include <limits.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 
 #include <glib.h>
@@ -45,7 +46,6 @@
 #include "chat_session.h"
 #include "command/command.h"
 #include "command/commands.h"
-#include "command/history.h"
 #include "common.h"
 #include "config/accounts.h"
 #include "config/preferences.h"
@@ -70,35 +70,40 @@
 
 typedef char*(*autocompleter)(char*, int*);
 
-static void _cmd_complete_parameters(char *input, int *size);
-
-static char * _sub_autocomplete(char *input, int *size);
-static char * _notify_autocomplete(char *input, int *size);
-static char * _theme_autocomplete(char *input, int *size);
-static char * _autoaway_autocomplete(char *input, int *size);
-static char * _autoconnect_autocomplete(char *input, int *size);
-static char * _account_autocomplete(char *input, int *size);
-static char * _who_autocomplete(char *input, int *size);
-static char * _roster_autocomplete(char *input, int *size);
-static char * _group_autocomplete(char *input, int *size);
-static char * _bookmark_autocomplete(char *input, int *size);
-static char * _otr_autocomplete(char *input, int *size);
-static char * _connect_autocomplete(char *input, int *size);
-static char * _statuses_autocomplete(char *input, int *size);
-static char * _alias_autocomplete(char *input, int *size);
-static char * _join_autocomplete(char *input, int *size);
-static char * _log_autocomplete(char *input, int *size);
-static char * _form_autocomplete(char *input, int *size);
-static char * _form_field_autocomplete(char *input, int *size);
-static char * _occupants_autocomplete(char *input, int *size);
-static char * _kick_autocomplete(char *input, int *size);
-static char * _ban_autocomplete(char *input, int *size);
-static char * _affiliation_autocomplete(char *input, int *size);
-static char * _role_autocomplete(char *input, int *size);
-static char * _resource_autocomplete(char *input, int *size);
-static char * _titlebar_autocomplete(char *input, int *size);
-static char * _inpblock_autocomplete(char *input, int *size);
-static char * _strip_quotes_from_names(char *input, int *size);
+static gboolean _cmd_execute(const char * const command, const char * const inp);
+static gboolean _cmd_execute_default(const char * inp);
+static gboolean _cmd_execute_alias(const char * const inp, gboolean *ran);
+
+static char * _cmd_complete_parameters(const char * const input);
+
+static char * _strip_quotes_from_names(const char * const input);
+
+static char * _sub_autocomplete(const char * const input);
+static char * _notify_autocomplete(const char * const input);
+static char * _theme_autocomplete(const char * const input);
+static char * _autoaway_autocomplete(const char * const input);
+static char * _autoconnect_autocomplete(const char * const input);
+static char * _account_autocomplete(const char * const input);
+static char * _who_autocomplete(const char * const input);
+static char * _roster_autocomplete(const char * const input);
+static char * _group_autocomplete(const char * const input);
+static char * _bookmark_autocomplete(const char * const input);
+static char * _otr_autocomplete(const char * const input);
+static char * _connect_autocomplete(const char * const input);
+static char * _statuses_autocomplete(const char * const input);
+static char * _alias_autocomplete(const char * const input);
+static char * _join_autocomplete(const char * const input);
+static char * _log_autocomplete(const char * const input);
+static char * _form_autocomplete(const char * const input);
+static char * _form_field_autocomplete(const char * const input);
+static char * _occupants_autocomplete(const char * const input);
+static char * _kick_autocomplete(const char * const input);
+static char * _ban_autocomplete(const char * const input);
+static char * _affiliation_autocomplete(const char * const input);
+static char * _role_autocomplete(const char * const input);
+static char * _resource_autocomplete(const char * const input);
+static char * _titlebar_autocomplete(const char * const input);
+static char * _inpblock_autocomplete(const char * const input);
 
 GHashTable *commands = NULL;
 
@@ -613,7 +618,7 @@ static struct cmd_t command_defs[] =
         { "/wrap on|off", "Word wrapping.",
         { "/wrap on|off",
           "------------",
-          "Enable or disable word wrapping.",
+          "Enable or disable word wrapping in the main window.",
           NULL } } },
 
     { "/time",
@@ -1139,7 +1144,7 @@ cmd_init(void)
     autocomplete_add(help_ac, "basic");
     autocomplete_add(help_ac, "chatting");
     autocomplete_add(help_ac, "groupchat");
-    autocomplete_add(help_ac, "presence");
+    autocomplete_add(help_ac, "presences");
     autocomplete_add(help_ac, "contacts");
     autocomplete_add(help_ac, "service");
     autocomplete_add(help_ac, "settings");
@@ -1464,10 +1469,6 @@ cmd_init(void)
     autocomplete_add(time_ac, "seconds");
     autocomplete_add(time_ac, "off");
 
-    time_ac = autocomplete_new();
-    autocomplete_add(time_ac, "minutes");
-    autocomplete_add(time_ac, "seconds");
-
     resource_ac = autocomplete_new();
     autocomplete_add(resource_ac, "set");
     autocomplete_add(resource_ac, "off");
@@ -1477,8 +1478,6 @@ cmd_init(void)
     inpblock_ac = autocomplete_new();
     autocomplete_add(inpblock_ac, "timeout");
     autocomplete_add(inpblock_ac, "dynamic");
-
-    cmd_history_init();
 }
 
 void
@@ -1614,30 +1613,26 @@ cmd_alias_remove(char *value)
 }
 
 // Command autocompletion functions
-void
-cmd_autocomplete(char *input, int *size)
+char*
+cmd_autocomplete(const char * const input)
 {
     // autocomplete command
-    if ((strncmp(input, "/", 1) == 0) && (!str_contains(input, *size, ' '))) {
-        int i = 0;
+    if ((strncmp(input, "/", 1) == 0) && (!str_contains(input, strlen(input), ' '))) {
         char *found = NULL;
-        char inp_cpy[*size];
-        for(i = 0; i < *size; i++) {
-            inp_cpy[i] = input[i];
-        }
-        inp_cpy[i] = '\0';
-        found = autocomplete_complete(commands_ac, inp_cpy, TRUE);
+        found = autocomplete_complete(commands_ac, input, TRUE);
         if (found != NULL) {
-            char *auto_msg = strdup(found);
-            ui_replace_input(input, auto_msg, size);
-            free(auto_msg);
-            free(found);
+            return found;
         }
 
     // autocomplete parameters
     } else {
-        _cmd_complete_parameters(input, size);
+        char *found = _cmd_complete_parameters(input);
+        if (found) {
+            return found;
+        }
     }
+
+    return NULL;
 }
 
 void
@@ -1729,10 +1724,54 @@ cmd_reset_autocomplete()
     bookmark_autocomplete_reset();
 }
 
+/*
+ * Take a line of input and process it, return TRUE if profanity is to
+ * continue, FALSE otherwise
+ */
+gboolean
+cmd_process_input(char *inp)
+{
+    log_debug("Input received: %s", inp);
+    gboolean result = FALSE;
+    g_strstrip(inp);
+
+    // add line to history if something typed
+    if (strlen(inp) > 0) {
+        ui_inp_history_append(inp);
+    }
+
+    // just carry on if no input
+    if (strlen(inp) == 0) {
+        result = TRUE;
+
+    // handle command if input starts with a '/'
+    } else if (inp[0] == '/') {
+        char *inp_cpy = strdup(inp);
+        char *command = strtok(inp_cpy, " ");
+        result = _cmd_execute(command, inp);
+        free(inp_cpy);
+
+    // call a default handler if input didn't start with '/'
+    } else {
+        result = _cmd_execute_default(inp);
+    }
+
+    return result;
+}
+
 // Command execution
 
-gboolean
-cmd_execute(const char * const command, const char * const inp)
+void
+cmd_execute_connect(const char * const account)
+{
+    GString *command = g_string_new("/connect ");
+    g_string_append(command, account);
+    cmd_process_input(command->str);
+    g_string_free(command, TRUE);
+}
+
+static gboolean
+_cmd_execute(const char * const command, const char * const inp)
 {
     if (g_str_has_prefix(command, "/field") && ui_current_win_type() == WIN_MUC_CONFIG) {
         gboolean result = FALSE;
@@ -1766,17 +1805,17 @@ cmd_execute(const char * const command, const char * const inp)
         }
     } else {
         gboolean ran_alias = FALSE;
-        gboolean alias_result = cmd_execute_alias(inp, &ran_alias);
+        gboolean alias_result = _cmd_execute_alias(inp, &ran_alias);
         if (!ran_alias) {
-            return cmd_execute_default(inp);
+            return _cmd_execute_default(inp);
         } else {
             return alias_result;
         }
     }
 }
 
-gboolean
-cmd_execute_alias(const char * const inp, gboolean *ran)
+static gboolean
+_cmd_execute_alias(const char * const inp, gboolean *ran)
 {
     if (inp[0] != '/') {
         ran = FALSE;
@@ -1787,7 +1826,7 @@ cmd_execute_alias(const char * const inp, gboolean *ran)
         free(alias);
         if (value != NULL) {
             *ran = TRUE;
-            return process_input(value);
+            return cmd_process_input(value);
         } else {
             *ran = FALSE;
             return TRUE;
@@ -1795,8 +1834,8 @@ cmd_execute_alias(const char * const inp, gboolean *ran)
     }
 }
 
-gboolean
-cmd_execute_default(const char * inp)
+static gboolean
+_cmd_execute_default(const char * inp)
 {
     jabber_conn_status_t status = jabber_get_connection_status();
 
@@ -1905,8 +1944,8 @@ cmd_execute_default(const char * inp)
     return TRUE;
 }
 
-static void
-_cmd_complete_parameters(char *input, int *size)
+static char *
+_cmd_complete_parameters(const char * const input)
 {
     int i;
     char *result = NULL;
@@ -1917,12 +1956,9 @@ _cmd_complete_parameters(char *input, int *size)
         "/vercheck", "/privileges", "/presence", "/wrap" };
 
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
-        result = autocomplete_param_with_func(input, size, boolean_choices[i],
-            prefs_autocomplete_boolean_choice);
-        if (result != NULL) {
-            ui_replace_input(input, result, size);
-            g_free(result);
-            return;
+        result = autocomplete_param_with_func(input, boolean_choices[i], prefs_autocomplete_boolean_choice);
+        if (result) {
+            return result;
         }
     }
 
@@ -1930,64 +1966,54 @@ _cmd_complete_parameters(char *input, int *size)
     if (ui_current_win_type() == WIN_MUC) {
         ProfMucWin *mucwin = wins_get_current_muc();
         Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
-        if (nick_ac != NULL) {
+        if (nick_ac) {
             gchar *nick_choices[] = { "/msg", "/info", "/caps", "/status", "/software" } ;
 
             // Remove quote character before and after names when doing autocomplete
-            input = _strip_quotes_from_names(input, size);
+            char *unquoted = _strip_quotes_from_names(input);
             for (i = 0; i < ARRAY_SIZE(nick_choices); i++) {
-                result = autocomplete_param_with_ac(input, size, nick_choices[i],
-                    nick_ac, TRUE);
-                if (result != NULL) {
-                    ui_replace_input(input, result, size);
-                    g_free(result);
-                    return;
+                result = autocomplete_param_with_ac(unquoted, nick_choices[i], nick_ac, TRUE);
+                if (result) {
+                    free(unquoted);
+                    return result;
                 }
             }
+            free(unquoted);
         }
 
     // otherwise autocomplete using roster
     } else {
         gchar *contact_choices[] = { "/msg", "/info", "/status" };
         // Remove quote character before and after names when doing autocomplete
-        input = _strip_quotes_from_names(input, size);
+        char *unquoted = _strip_quotes_from_names(input);
         for (i = 0; i < ARRAY_SIZE(contact_choices); i++) {
-            result = autocomplete_param_with_func(input, size, contact_choices[i],
-                roster_contact_autocomplete);
-            if (result != NULL) {
-                ui_replace_input(input, result, size);
-                g_free(result);
-                return;
+            result = autocomplete_param_with_func(unquoted, contact_choices[i], roster_contact_autocomplete);
+            if (result) {
+                free(unquoted);
+                return result;
             }
         }
+        free(unquoted);
 
         gchar *resource_choices[] = { "/caps", "/software", "/ping" };
         for (i = 0; i < ARRAY_SIZE(resource_choices); i++) {
-            result = autocomplete_param_with_func(input, size, resource_choices[i],
-                roster_fulljid_autocomplete);
-            if (result != NULL) {
-                ui_replace_input(input, result, size);
-                g_free(result);
-                return;
+            result = autocomplete_param_with_func(input, resource_choices[i], roster_fulljid_autocomplete);
+            if (result) {
+                return result;
             }
         }
     }
 
-    result = autocomplete_param_with_func(input, size, "/invite", roster_contact_autocomplete);
-    if (result != NULL) {
-        ui_replace_input(input, result, size);
-        g_free(result);
-        return;
+    result = autocomplete_param_with_func(input, "/invite", roster_contact_autocomplete);
+    if (result) {
+        return result;
     }
 
     gchar *invite_choices[] = { "/decline", "/join" };
     for (i = 0; i < ARRAY_SIZE(invite_choices); i++) {
-        result = autocomplete_param_with_func(input, size, invite_choices[i],
-            muc_invites_find);
-        if (result != NULL) {
-            ui_replace_input(input, result, size);
-            g_free(result);
-            return;
+        result = autocomplete_param_with_func(input, invite_choices[i], muc_invites_find);
+        if (result) {
+            return result;
         }
     }
 
@@ -1995,11 +2021,9 @@ _cmd_complete_parameters(char *input, int *size)
     Autocomplete completers[] = { help_ac, prefs_ac, disco_ac, close_ac, wins_ac, subject_ac, room_ac, time_ac };
 
     for (i = 0; i < ARRAY_SIZE(cmds); i++) {
-        result = autocomplete_param_with_ac(input, size, cmds[i], completers[i], TRUE);
-        if (result != NULL) {
-            ui_replace_input(input, result, size);
-            g_free(result);
-            return;
+        result = autocomplete_param_with_ac(input, cmds[i], completers[i], TRUE);
+        if (result) {
+            return result;
         }
     }
 
@@ -2030,9 +2054,10 @@ _cmd_complete_parameters(char *input, int *size)
     g_hash_table_insert(ac_funcs, "/titlebar",      _titlebar_autocomplete);
     g_hash_table_insert(ac_funcs, "/inpblock",      _inpblock_autocomplete);
 
-    char parsed[*size+1];
+    int len = strlen(input);
+    char parsed[len+1];
     i = 0;
-    while (i < *size) {
+    while (i < len) {
         if (input[i] == ' ') {
             break;
         } else {
@@ -2042,44 +2067,39 @@ _cmd_complete_parameters(char *input, int *size)
     }
     parsed[i] = '\0';
 
-    char * (*ac_func)(char *, int *) = g_hash_table_lookup(ac_funcs, parsed);
+    char * (*ac_func)(const char * const) = g_hash_table_lookup(ac_funcs, parsed);
     if (ac_func != NULL) {
-        result = ac_func(input, size);
-        if (result != NULL) {
-            ui_replace_input(input, result, size);
-            g_free(result);
+        result = ac_func(input);
+        if (result) {
             g_hash_table_destroy(ac_funcs);
-            return;
+            return result;
         }
     }
     g_hash_table_destroy(ac_funcs);
 
-    input[*size] = '\0';
     if (g_str_has_prefix(input, "/field")) {
-        result = _form_field_autocomplete(input, size);
-        if (result != NULL) {
-            ui_replace_input(input, result, size);
-            g_free(result);
-            return;
+        result = _form_field_autocomplete(input);
+        if (result) {
+            return result;
         }
     }
 
-    return;
+    return NULL;
 }
 
 static char *
-_sub_autocomplete(char *input, int *size)
+_sub_autocomplete(const char * const input)
 {
     char *result = NULL;
-    result = autocomplete_param_with_func(input, size, "/sub allow", presence_sub_request_find);
+    result = autocomplete_param_with_func(input, "/sub allow", presence_sub_request_find);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/sub deny", presence_sub_request_find);
+    result = autocomplete_param_with_func(input, "/sub deny", presence_sub_request_find);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/sub", sub_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/sub", sub_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2088,13 +2108,13 @@ _sub_autocomplete(char *input, int *size)
 }
 
 static char *
-_who_autocomplete(char *input, int *size)
+_who_autocomplete(const char * const input)
 {
     char *result = NULL;
     win_type_t win_type = ui_current_win_type();
 
     if (win_type == WIN_MUC) {
-        result = autocomplete_param_with_ac(input, size, "/who", who_room_ac, TRUE);
+        result = autocomplete_param_with_ac(input, "/who", who_room_ac, TRUE);
         if (result != NULL) {
             return result;
         }
@@ -2105,13 +2125,13 @@ _who_autocomplete(char *input, int *size)
             "/who unavailable" };
 
         for (i = 0; i < ARRAY_SIZE(group_commands); i++) {
-            result = autocomplete_param_with_func(input, size, group_commands[i], roster_group_autocomplete);
+            result = autocomplete_param_with_func(input, group_commands[i], roster_group_autocomplete);
             if (result != NULL) {
                 return result;
             }
         }
 
-        result = autocomplete_param_with_ac(input, size, "/who", who_roster_ac, TRUE);
+        result = autocomplete_param_with_ac(input, "/who", who_roster_ac, TRUE);
         if (result != NULL) {
             return result;
         }
@@ -2121,34 +2141,34 @@ _who_autocomplete(char *input, int *size)
 }
 
 static char *
-_roster_autocomplete(char *input, int *size)
+_roster_autocomplete(const char * const input)
 {
     char *result = NULL;
-    result = autocomplete_param_with_func(input, size, "/roster nick", roster_barejid_autocomplete);
+    result = autocomplete_param_with_func(input, "/roster nick", roster_barejid_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/roster clearnick", roster_barejid_autocomplete);
+    result = autocomplete_param_with_func(input, "/roster clearnick", roster_barejid_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/roster remove", roster_barejid_autocomplete);
+    result = autocomplete_param_with_func(input, "/roster remove", roster_barejid_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/roster show", roster_option_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/roster show", roster_option_ac, TRUE);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/roster hide", roster_option_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/roster hide", roster_option_ac, TRUE);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/roster by", roster_by_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/roster by", roster_by_ac, TRUE);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/roster", roster_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/roster", roster_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2157,31 +2177,31 @@ _roster_autocomplete(char *input, int *size)
 }
 
 static char *
-_group_autocomplete(char *input, int *size)
+_group_autocomplete(const char * const input)
 {
     char *result = NULL;
-    result = autocomplete_param_with_func(input, size, "/group show", roster_group_autocomplete);
+    result = autocomplete_param_with_func(input, "/group show", roster_group_autocomplete);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_no_with_func(input, size, "/group add", 4, roster_contact_autocomplete);
+    result = autocomplete_param_no_with_func(input, "/group add", 4, roster_contact_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_no_with_func(input, size, "/group remove", 4, roster_contact_autocomplete);
+    result = autocomplete_param_no_with_func(input, "/group remove", 4, roster_contact_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/group add", roster_group_autocomplete);
+    result = autocomplete_param_with_func(input, "/group add", roster_group_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/group remove", roster_group_autocomplete);
+    result = autocomplete_param_with_func(input, "/group remove", roster_group_autocomplete);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/group", group_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/group", group_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2190,7 +2210,7 @@ _group_autocomplete(char *input, int *size)
 }
 
 static char *
-_bookmark_autocomplete(char *input, int *size)
+_bookmark_autocomplete(const char * const input)
 {
     char *found = NULL;
 
@@ -2238,9 +2258,9 @@ _bookmark_autocomplete(char *input, int *size)
         }
 
         if (autojoin) {
-            found = autocomplete_param_with_func(input, size, beginning->str, prefs_autocomplete_boolean_choice);
+            found = autocomplete_param_with_func(input, beginning->str, prefs_autocomplete_boolean_choice);
         } else {
-            found = autocomplete_param_with_ac(input, size, beginning->str, bookmark_property_ac, TRUE);
+            found = autocomplete_param_with_ac(input, beginning->str, bookmark_property_ac, TRUE);
         }
         g_string_free(beginning, TRUE);
         if (found != NULL) {
@@ -2251,79 +2271,79 @@ _bookmark_autocomplete(char *input, int *size)
 
     g_strfreev(args);
 
-    found = autocomplete_param_with_func(input, size, "/bookmark remove", bookmark_find);
+    found = autocomplete_param_with_func(input, "/bookmark remove", bookmark_find);
     if (found != NULL) {
         return found;
     }
-    found = autocomplete_param_with_func(input, size, "/bookmark join", bookmark_find);
+    found = autocomplete_param_with_func(input, "/bookmark join", bookmark_find);
     if (found != NULL) {
         return found;
     }
-    found = autocomplete_param_with_func(input, size, "/bookmark update", bookmark_find);
+    found = autocomplete_param_with_func(input, "/bookmark update", bookmark_find);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/bookmark", bookmark_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/bookmark", bookmark_ac, TRUE);
     return found;
 }
 
 static char *
-_notify_autocomplete(char *input, int *size)
+_notify_autocomplete(const char * const input)
 {
     int i = 0;
     char *result = NULL;
 
-    result = autocomplete_param_with_func(input, size, "/notify room current", prefs_autocomplete_boolean_choice);
+    result = autocomplete_param_with_func(input, "/notify room current", prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_func(input, size, "/notify message current", prefs_autocomplete_boolean_choice);
+    result = autocomplete_param_with_func(input, "/notify message current", prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_func(input, size, "/notify typing current", prefs_autocomplete_boolean_choice);
+    result = autocomplete_param_with_func(input, "/notify typing current", prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_func(input, size, "/notify room text", prefs_autocomplete_boolean_choice);
+    result = autocomplete_param_with_func(input, "/notify room text", prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_func(input, size, "/notify message text", prefs_autocomplete_boolean_choice);
+    result = autocomplete_param_with_func(input, "/notify message text", prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/notify room", notify_room_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/notify room", notify_room_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/notify message", notify_message_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/notify message", notify_message_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/notify typing", notify_typing_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/notify typing", notify_typing_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
     gchar *boolean_choices[] = { "/notify invite", "/notify sub" };
     for (i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
-        result = autocomplete_param_with_func(input, size, boolean_choices[i],
+        result = autocomplete_param_with_func(input, boolean_choices[i],
             prefs_autocomplete_boolean_choice);
         if (result != NULL) {
             return result;
         }
     }
 
-    result = autocomplete_param_with_ac(input, size, "/notify", notify_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/notify", notify_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2332,20 +2352,20 @@ _notify_autocomplete(char *input, int *size)
 }
 
 static char *
-_autoaway_autocomplete(char *input, int *size)
+_autoaway_autocomplete(const char * const input)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_ac(input, size, "/autoaway mode", autoaway_mode_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/autoaway mode", autoaway_mode_ac, TRUE);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/autoaway check",
+    result = autocomplete_param_with_func(input, "/autoaway check",
         prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/autoaway", autoaway_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/autoaway", autoaway_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2354,21 +2374,21 @@ _autoaway_autocomplete(char *input, int *size)
 }
 
 static char *
-_log_autocomplete(char *input, int *size)
+_log_autocomplete(const char * const input)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_func(input, size, "/log rotate",
+    result = autocomplete_param_with_func(input, "/log rotate",
         prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_func(input, size, "/log shared",
+    result = autocomplete_param_with_func(input, "/log shared",
         prefs_autocomplete_boolean_choice);
     if (result != NULL) {
         return result;
     }
-    result = autocomplete_param_with_ac(input, size, "/log", log_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/log", log_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2377,16 +2397,16 @@ _log_autocomplete(char *input, int *size)
 }
 
 static char *
-_autoconnect_autocomplete(char *input, int *size)
+_autoconnect_autocomplete(const char * const input)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_func(input, size, "/autoconnect set", accounts_find_enabled);
+    result = autocomplete_param_with_func(input, "/autoconnect set", accounts_find_enabled);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/autoconnect", autoconnect_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/autoconnect", autoconnect_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2395,16 +2415,16 @@ _autoconnect_autocomplete(char *input, int *size)
 }
 
 static char *
-_otr_autocomplete(char *input, int *size)
+_otr_autocomplete(const char * const input)
 {
     char *found = NULL;
 
-    found = autocomplete_param_with_func(input, size, "/otr start", roster_contact_autocomplete);
+    found = autocomplete_param_with_func(input, "/otr start", roster_contact_autocomplete);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/otr log", otr_log_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/otr log", otr_log_ac, TRUE);
     if (found != NULL) {
         return found;
     }
@@ -2418,7 +2438,7 @@ _otr_autocomplete(char *input, int *size)
         g_string_append(beginning, " ");
         g_string_append(beginning, args[1]);
 
-        found = autocomplete_param_with_func(input, size, beginning->str, roster_contact_autocomplete);
+        found = autocomplete_param_with_func(input, beginning->str, roster_contact_autocomplete);
         g_string_free(beginning, TRUE);
         if (found != NULL) {
             g_strfreev(args);
@@ -2428,18 +2448,18 @@ _otr_autocomplete(char *input, int *size)
 
     g_strfreev(args);
 
-    found = autocomplete_param_with_ac(input, size, "/otr policy", otr_policy_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/otr policy", otr_policy_ac, TRUE);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_func(input, size, "/otr warn",
+    found = autocomplete_param_with_func(input, "/otr warn",
         prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/otr", otr_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/otr", otr_ac, TRUE);
     if (found != NULL) {
         return found;
     }
@@ -2448,10 +2468,10 @@ _otr_autocomplete(char *input, int *size)
 }
 
 static char *
-_theme_autocomplete(char *input, int *size)
+_theme_autocomplete(const char * const input)
 {
     char *result = NULL;
-    if ((strncmp(input, "/theme set ", 11) == 0) && (*size > 11)) {
+    if ((strncmp(input, "/theme set ", 11) == 0) && (strlen(input) > 11)) {
         if (theme_load_ac == NULL) {
             theme_load_ac = autocomplete_new();
             GSList *themes = theme_list();
@@ -2462,12 +2482,12 @@ _theme_autocomplete(char *input, int *size)
             g_slist_free(themes);
             autocomplete_add(theme_load_ac, "default");
         }
-        result = autocomplete_param_with_ac(input, size, "/theme set", theme_load_ac, TRUE);
+        result = autocomplete_param_with_ac(input, "/theme set", theme_load_ac, TRUE);
         if (result != NULL) {
             return result;
         }
     }
-    result = autocomplete_param_with_ac(input, size, "/theme", theme_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/theme", theme_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2476,7 +2496,7 @@ _theme_autocomplete(char *input, int *size)
 }
 
 static char *
-_resource_autocomplete(char *input, int *size)
+_resource_autocomplete(const char * const input)
 {
     char *found = NULL;
 
@@ -2486,24 +2506,24 @@ _resource_autocomplete(char *input, int *size)
         PContact contact = roster_get_contact(chatwin->barejid);
         if (contact) {
             Autocomplete ac = p_contact_resource_ac(contact);
-            found = autocomplete_param_with_ac(input, size, "/resource set", ac, FALSE);
+            found = autocomplete_param_with_ac(input, "/resource set", ac, FALSE);
             if (found != NULL) {
                 return found;
             }
         }
     }
 
-    found = autocomplete_param_with_func(input, size, "/resource title", prefs_autocomplete_boolean_choice);
+    found = autocomplete_param_with_func(input, "/resource title", prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_func(input, size, "/resource message", prefs_autocomplete_boolean_choice);
+    found = autocomplete_param_with_func(input, "/resource message", prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/resource", resource_ac, FALSE);
+    found = autocomplete_param_with_ac(input, "/resource", resource_ac, FALSE);
     if (found != NULL) {
         return found;
     }
@@ -2512,21 +2532,21 @@ _resource_autocomplete(char *input, int *size)
 }
 
 static char *
-_titlebar_autocomplete(char *input, int *size)
+_titlebar_autocomplete(const char * const input)
 {
     char *found = NULL;
 
-    found = autocomplete_param_with_func(input, size, "/titlebar show", prefs_autocomplete_boolean_choice);
+    found = autocomplete_param_with_func(input, "/titlebar show", prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_func(input, size, "/titlebar goodbye", prefs_autocomplete_boolean_choice);
+    found = autocomplete_param_with_func(input, "/titlebar goodbye", prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/titlebar", titlebar_ac, FALSE);
+    found = autocomplete_param_with_ac(input, "/titlebar", titlebar_ac, FALSE);
     if (found != NULL) {
         return found;
     }
@@ -2535,16 +2555,16 @@ _titlebar_autocomplete(char *input, int *size)
 }
 
 static char *
-_inpblock_autocomplete(char *input, int *size)
+_inpblock_autocomplete(const char * const input)
 {
     char *found = NULL;
 
-    found = autocomplete_param_with_func(input, size, "/inpblock dynamic", prefs_autocomplete_boolean_choice);
+    found = autocomplete_param_with_func(input, "/inpblock dynamic", prefs_autocomplete_boolean_choice);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/inpblock", inpblock_ac, FALSE);
+    found = autocomplete_param_with_ac(input, "/inpblock", inpblock_ac, FALSE);
     if (found != NULL) {
         return found;
     }
@@ -2553,7 +2573,7 @@ _inpblock_autocomplete(char *input, int *size)
 }
 
 static char *
-_form_autocomplete(char *input, int *size)
+_form_autocomplete(const char * const input)
 {
     ProfWin *current = wins_get_current();
     if (current->type != WIN_MUC_CONFIG) {
@@ -2565,13 +2585,13 @@ _form_autocomplete(char *input, int *size)
     ProfMucConfWin *confwin = (ProfMucConfWin*)current;
     DataForm *form = confwin->form;
     if (form) {
-        found = autocomplete_param_with_ac(input, size, "/form help", form->tag_ac, TRUE);
+        found = autocomplete_param_with_ac(input, "/form help", form->tag_ac, TRUE);
         if (found != NULL) {
             return found;
         }
     }
 
-    found = autocomplete_param_with_ac(input, size, "/form", form_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/form", form_ac, TRUE);
     if (found != NULL) {
         return found;
     }
@@ -2580,7 +2600,7 @@ _form_autocomplete(char *input, int *size)
 }
 
 static char *
-_form_field_autocomplete(char *input, int *size)
+_form_field_autocomplete(const char * const input)
 {
     ProfWin *current = wins_get_current();
     if (current->type != WIN_MUC_CONFIG) {
@@ -2595,7 +2615,6 @@ _form_field_autocomplete(char *input, int *size)
         return NULL;
     }
 
-    input[*size] = '\0';
     gchar **split = g_strsplit(input, " ", 0);
 
     if (g_strv_length(split) == 3) {
@@ -2609,13 +2628,13 @@ _form_field_autocomplete(char *input, int *size)
 
             if (((g_strcmp0(split[1], "add") == 0) || (g_strcmp0(split[1], "remove") == 0))
                     && field_type == FIELD_LIST_MULTI) {
-                found = autocomplete_param_with_ac(input, size, beginning->str, value_ac, TRUE);
+                found = autocomplete_param_with_ac(input, beginning->str, value_ac, TRUE);
 
             } else if ((g_strcmp0(split[1], "remove") == 0) && field_type == FIELD_TEXT_MULTI) {
-                found = autocomplete_param_with_ac(input, size, beginning->str, value_ac, TRUE);
+                found = autocomplete_param_with_ac(input, beginning->str, value_ac, TRUE);
 
             } else if ((g_strcmp0(split[1], "remove") == 0) && field_type == FIELD_JID_MULTI) {
-                found = autocomplete_param_with_ac(input, size, beginning->str, value_ac, TRUE);
+                found = autocomplete_param_with_ac(input, beginning->str, value_ac, TRUE);
             }
 
             g_string_free(beginning, TRUE);
@@ -2630,15 +2649,15 @@ _form_field_autocomplete(char *input, int *size)
             switch (field_type)
             {
                 case FIELD_BOOLEAN:
-                    found = autocomplete_param_with_func(input, size, split[0], prefs_autocomplete_boolean_choice);
+                    found = autocomplete_param_with_func(input, split[0], prefs_autocomplete_boolean_choice);
                     break;
                 case FIELD_LIST_SINGLE:
-                    found = autocomplete_param_with_ac(input, size, split[0], value_ac, TRUE);
+                    found = autocomplete_param_with_ac(input, split[0], value_ac, TRUE);
                     break;
                 case FIELD_LIST_MULTI:
                 case FIELD_JID_MULTI:
                 case FIELD_TEXT_MULTI:
-                    found = autocomplete_param_with_ac(input, size, split[0], form_field_multi_ac, TRUE);
+                    found = autocomplete_param_with_ac(input, split[0], form_field_multi_ac, TRUE);
                     break;
                 default:
                     break;
@@ -2652,16 +2671,16 @@ _form_field_autocomplete(char *input, int *size)
 }
 
 static char *
-_occupants_autocomplete(char *input, int *size)
+_occupants_autocomplete(const char * const input)
 {
     char *found = NULL;
 
-    found = autocomplete_param_with_ac(input, size, "/occupants default", occupants_default_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/occupants default", occupants_default_ac, TRUE);
     if (found != NULL) {
         return found;
     }
 
-    found = autocomplete_param_with_ac(input, size, "/occupants", occupants_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/occupants", occupants_ac, TRUE);
     if (found != NULL) {
         return found;
     }
@@ -2670,7 +2689,7 @@ _occupants_autocomplete(char *input, int *size)
 }
 
 static char *
-_kick_autocomplete(char *input, int *size)
+_kick_autocomplete(const char * const input)
 {
     char *result = NULL;
 
@@ -2679,7 +2698,7 @@ _kick_autocomplete(char *input, int *size)
         Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
 
         if (nick_ac != NULL) {
-            result = autocomplete_param_with_ac(input, size, "/kick", nick_ac, TRUE);
+            result = autocomplete_param_with_ac(input, "/kick", nick_ac, TRUE);
             if (result != NULL) {
                 return result;
             }
@@ -2690,7 +2709,7 @@ _kick_autocomplete(char *input, int *size)
 }
 
 static char *
-_ban_autocomplete(char *input, int *size)
+_ban_autocomplete(const char * const input)
 {
     char *result = NULL;
 
@@ -2699,7 +2718,7 @@ _ban_autocomplete(char *input, int *size)
         Autocomplete jid_ac = muc_roster_jid_ac(mucwin->roomjid);
 
         if (jid_ac != NULL) {
-            result = autocomplete_param_with_ac(input, size, "/ban", jid_ac, TRUE);
+            result = autocomplete_param_with_ac(input, "/ban", jid_ac, TRUE);
             if (result != NULL) {
                 return result;
             }
@@ -2710,7 +2729,7 @@ _ban_autocomplete(char *input, int *size)
 }
 
 static char *
-_affiliation_autocomplete(char *input, int *size)
+_affiliation_autocomplete(const char * const input)
 {
     char *result = NULL;
 
@@ -2719,7 +2738,6 @@ _affiliation_autocomplete(char *input, int *size)
         gboolean parse_result;
         Autocomplete jid_ac = muc_roster_jid_ac(mucwin->roomjid);
 
-        input[*size] = '\0';
         gchar **args = parse_args(input, 3, 3, &parse_result);
 
         if ((strncmp(input, "/affiliation", 12) == 0) && (parse_result == TRUE)) {
@@ -2728,7 +2746,7 @@ _affiliation_autocomplete(char *input, int *size)
             g_string_append(beginning, " ");
             g_string_append(beginning, args[1]);
 
-            result = autocomplete_param_with_ac(input, size, beginning->str, jid_ac, TRUE);
+            result = autocomplete_param_with_ac(input, beginning->str, jid_ac, TRUE);
             g_string_free(beginning, TRUE);
             if (result != NULL) {
                 g_strfreev(args);
@@ -2739,17 +2757,17 @@ _affiliation_autocomplete(char *input, int *size)
         g_strfreev(args);
     }
 
-    result = autocomplete_param_with_ac(input, size, "/affiliation set", affiliation_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/affiliation set", affiliation_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/affiliation list", affiliation_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/affiliation list", affiliation_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/affiliation", privilege_cmd_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/affiliation", privilege_cmd_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2758,7 +2776,7 @@ _affiliation_autocomplete(char *input, int *size)
 }
 
 static char *
-_role_autocomplete(char *input, int *size)
+_role_autocomplete(const char * const input)
 {
     char *result = NULL;
 
@@ -2767,7 +2785,6 @@ _role_autocomplete(char *input, int *size)
         gboolean parse_result;
         Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
 
-        input[*size] = '\0';
         gchar **args = parse_args(input, 3, 3, &parse_result);
 
         if ((strncmp(input, "/role", 5) == 0) && (parse_result == TRUE)) {
@@ -2776,7 +2793,7 @@ _role_autocomplete(char *input, int *size)
             g_string_append(beginning, " ");
             g_string_append(beginning, args[1]);
 
-            result = autocomplete_param_with_ac(input, size, beginning->str, nick_ac, TRUE);
+            result = autocomplete_param_with_ac(input, beginning->str, nick_ac, TRUE);
             g_string_free(beginning, TRUE);
             if (result != NULL) {
                 g_strfreev(args);
@@ -2787,17 +2804,17 @@ _role_autocomplete(char *input, int *size)
         g_strfreev(args);
     }
 
-    result = autocomplete_param_with_ac(input, size, "/role set", role_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/role set", role_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/role list", role_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/role list", role_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/role", privilege_cmd_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/role", privilege_cmd_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2806,26 +2823,26 @@ _role_autocomplete(char *input, int *size)
 }
 
 static char *
-_statuses_autocomplete(char *input, int *size)
+_statuses_autocomplete(const char * const input)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_ac(input, size, "/statuses console", statuses_setting_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/statuses console", statuses_setting_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/statuses chat", statuses_setting_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/statuses chat", statuses_setting_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/statuses muc", statuses_setting_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/statuses muc", statuses_setting_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/statuses", statuses_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/statuses", statuses_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2834,16 +2851,16 @@ _statuses_autocomplete(char *input, int *size)
 }
 
 static char *
-_alias_autocomplete(char *input, int *size)
+_alias_autocomplete(const char * const input)
 {
     char *result = NULL;
 
-    result = autocomplete_param_with_ac(input, size, "/alias remove", aliases_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/alias remove", aliases_ac, TRUE);
     if (result != NULL) {
         return result;
     }
 
-    result = autocomplete_param_with_ac(input, size, "/alias", alias_ac, TRUE);
+    result = autocomplete_param_with_ac(input, "/alias", alias_ac, TRUE);
     if (result != NULL) {
         return result;
     }
@@ -2852,12 +2869,11 @@ _alias_autocomplete(char *input, int *size)
 }
 
 static char *
-_connect_autocomplete(char *input, int *size)
+_connect_autocomplete(const char * const input)
 {
     char *found = NULL;
     gboolean result = FALSE;
 
-    input[*size] = '\0';
     gchar **args = parse_args(input, 2, 4, &result);
 
     if ((strncmp(input, "/connect", 8) == 0) && (result == TRUE)) {
@@ -2869,7 +2885,7 @@ _connect_autocomplete(char *input, int *size)
             g_string_append(beginning, " ");
             g_string_append(beginning, args[2]);
         }
-        found = autocomplete_param_with_ac(input, size, beginning->str, connect_property_ac, TRUE);
+        found = autocomplete_param_with_ac(input, beginning->str, connect_property_ac, TRUE);
         g_string_free(beginning, TRUE);
         if (found != NULL) {
             g_strfreev(args);
@@ -2879,7 +2895,7 @@ _connect_autocomplete(char *input, int *size)
 
     g_strfreev(args);
 
-    found = autocomplete_param_with_func(input, size, "/connect", accounts_find_enabled);
+    found = autocomplete_param_with_func(input, "/connect", accounts_find_enabled);
     if (found != NULL) {
         return found;
     }
@@ -2888,14 +2904,12 @@ _connect_autocomplete(char *input, int *size)
 }
 
 static char *
-_join_autocomplete(char *input, int *size)
+_join_autocomplete(const char * const input)
 {
     char *found = NULL;
     gboolean result = FALSE;
 
-    input[*size] = '\0';
-
-    found = autocomplete_param_with_func(input, size, "/join", bookmark_find);
+    found = autocomplete_param_with_func(input, "/join", bookmark_find);
     if (found != NULL) {
         return found;
     }
@@ -2911,7 +2925,7 @@ _join_autocomplete(char *input, int *size)
             g_string_append(beginning, " ");
             g_string_append(beginning, args[2]);
         }
-        found = autocomplete_param_with_ac(input, size, beginning->str, join_property_ac, TRUE);
+        found = autocomplete_param_with_ac(input, beginning->str, join_property_ac, TRUE);
         g_string_free(beginning, TRUE);
         if (found != NULL) {
             g_strfreev(args);
@@ -2925,12 +2939,11 @@ _join_autocomplete(char *input, int *size)
 }
 
 static char *
-_account_autocomplete(char *input, int *size)
+_account_autocomplete(const char * const input)
 {
     char *found = NULL;
     gboolean result = FALSE;
 
-    input[*size] = '\0';
     gchar **args = parse_args(input, 3, 4, &result);
 
     if ((strncmp(input, "/account set", 12) == 0) && (result == TRUE)) {
@@ -2939,14 +2952,14 @@ _account_autocomplete(char *input, int *size)
         if ((g_strv_length(args) > 3) && (g_strcmp0(args[2], "otr")) == 0) {
             g_string_append(beginning, " ");
             g_string_append(beginning, args[2]);
-            found = autocomplete_param_with_ac(input, size, beginning->str, otr_policy_ac, TRUE);
+            found = autocomplete_param_with_ac(input, beginning->str, otr_policy_ac, TRUE);
             g_string_free(beginning, TRUE);
             if (found != NULL) {
                 g_strfreev(args);
                 return found;
             }
         } else {
-            found = autocomplete_param_with_ac(input, size, beginning->str, account_set_ac, TRUE);
+            found = autocomplete_param_with_ac(input, beginning->str, account_set_ac, TRUE);
             g_string_free(beginning, TRUE);
             if (found != NULL) {
                 g_strfreev(args);
@@ -2958,7 +2971,7 @@ _account_autocomplete(char *input, int *size)
     if ((strncmp(input, "/account clear", 14) == 0) && (result == TRUE)) {
         GString *beginning = g_string_new("/account clear ");
         g_string_append(beginning, args[1]);
-        found = autocomplete_param_with_ac(input, size, beginning->str, account_clear_ac, TRUE);
+        found = autocomplete_param_with_ac(input, beginning->str, account_clear_ac, TRUE);
         g_string_free(beginning, TRUE);
         if (found != NULL) {
             g_strfreev(args);
@@ -2968,7 +2981,7 @@ _account_autocomplete(char *input, int *size)
 
     g_strfreev(args);
 
-    found = autocomplete_param_with_ac(input, size, "/account default", account_default_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/account default", account_default_ac, TRUE);
     if(found){
         return found;
     }
@@ -2979,31 +2992,85 @@ _account_autocomplete(char *input, int *size)
         "/account default set" };
 
     for (i = 0; i < ARRAY_SIZE(account_choice); i++) {
-        found = autocomplete_param_with_func(input, size, account_choice[i],
-            accounts_find_all);
+        found = autocomplete_param_with_func(input, account_choice[i], accounts_find_all);
         if (found != NULL) {
             return found;
         }
     }
 
-    found = autocomplete_param_with_ac(input, size, "/account", account_ac, TRUE);
+    found = autocomplete_param_with_ac(input, "/account", account_ac, TRUE);
     return found;
 }
 
+static int
+_cmp_command(Command *cmd1, Command *cmd2)
+{
+    return g_strcmp0(cmd1->cmd, cmd2->cmd);
+}
+
+void
+command_docgen(void)
+{
+    GList *cmds = NULL;
+    unsigned int i;
+    for (i = 0; i < ARRAY_SIZE(command_defs); i++) {
+        Command *pcmd = command_defs+i;
+        cmds = g_list_insert_sorted(cmds, pcmd, (GCompareFunc)_cmp_command);
+    }
+
+    FILE *toc_fragment = fopen("toc_fragment.html", "w");
+    FILE *main_fragment = fopen("main_fragment.html", "w");
+
+    fputs("<ul><li><ul><li>\n", toc_fragment);
+    fputs("<hr>\n", main_fragment);
+
+    GList *curr = cmds;
+    while (curr) {
+        Command *pcmd = curr->data;
+
+        fprintf(toc_fragment, "<a href=\"#%s\">%s</a>,\n", &pcmd->cmd[1], pcmd->cmd);
+
+        fprintf(main_fragment, "<a name=\"%s\"></a>\n", &pcmd->cmd[1]);
+        fprintf(main_fragment, "<h4>%s</h4>\n", pcmd->cmd);
+        fputs("<p>Usage:</p>\n", main_fragment);
+        fprintf(main_fragment, "<p><pre><code>%s</code></pre></p>\n", pcmd->help.usage);
+
+        fputs("<p>Details:</p>\n", main_fragment);
+        fputs("<p><pre><code>", main_fragment);
+        int i = 2;
+        while (pcmd->help.long_help[i] != NULL) {
+            fprintf(main_fragment, "%s\n", pcmd->help.long_help[i++]);
+        }
+        fputs("</code></pre></p>\n<a href=\"#top\"><h5>back to top</h5></a><br><hr>\n", main_fragment);
+        fputs("\n", main_fragment);
+
+        curr = g_list_next(curr);
+    }
+
+    fputs("</ul></ul>\n", toc_fragment);
+
+    fclose(toc_fragment);
+    fclose(main_fragment);
+    g_list_free(cmds);
+}
+
 static char *
-_strip_quotes_from_names(char *input, int *size) {
+_strip_quotes_from_names(const char * const input) {
+    char *unquoted = strdup(input);
+
     // Remove starting quote if it exists
-    if(strchr(input, '"') != NULL) {
-        if(strchr(input, ' ') + 1 == strchr(input, '"')) {
-            memmove(strchr(input, '"'), strchr(input, '"')+1, strchr(input, '\0') - strchr(input, '"'));
+    if(strchr(unquoted, '"') != NULL) {
+        if(strchr(unquoted, ' ') + 1 == strchr(unquoted, '"')) {
+            memmove(strchr(unquoted, '"'), strchr(unquoted, '"')+1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
         }
     }
 
     // Remove ending quote if it exists
-    if(strchr(input, '"') != NULL) {
-        if(strchr(input, '\0') - 1 == strchr(input, '"')) {
-            memmove(strchr(input, '"'), strchr(input, '"')+1, strchr(input, '\0') - strchr(input, '"'));
+    if(strchr(unquoted, '"') != NULL) {
+        if(strchr(unquoted, '\0') - 1 == strchr(unquoted, '"')) {
+            memmove(strchr(unquoted, '"'), strchr(unquoted, '"')+1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
         }
     }
-    return input;
+
+    return unquoted;
 }
diff --git a/src/command/command.h b/src/command/command.h
index 13cf2d00..8be1143f 100644
--- a/src/command/command.h
+++ b/src/command/command.h
@@ -44,7 +44,7 @@ GHashTable *commands;
 void cmd_init(void);
 void cmd_uninit(void);
 
-void cmd_autocomplete(char *input, int *size);
+char* cmd_autocomplete(const char * const input);
 void cmd_reset_autocomplete(void);
 void cmd_autocomplete_add(char *value);
 void cmd_autocomplete_remove(char *value);
@@ -53,9 +53,8 @@ void cmd_autocomplete_remove_form_fields(DataForm *form);
 void cmd_alias_add(char *value);
 void cmd_alias_remove(char *value);
 
-gboolean cmd_execute(const char * const command, const char * const inp);
-gboolean cmd_execute_alias(const char * const inp, gboolean *ran);
-gboolean cmd_execute_default(const char * const inp);
+gboolean cmd_process_input(char *inp);
+void cmd_execute_connect(const char * const account);
 
 gboolean cmd_exists(char *cmd);
 
@@ -64,7 +63,9 @@ GSList * cmd_get_settings_help(void);
 GSList * cmd_get_presence_help(void);
 
 void cmd_history_append(char *inp);
-char *cmd_history_previous(char *inp, int *size);
-char *cmd_history_next(char *inp, int *size);
+char *cmd_history_previous(char *inp);
+char *cmd_history_next(char *inp);
+
+void command_docgen(void);
 
 #endif
diff --git a/src/command/commands.c b/src/command/commands.c
index 75a4f6cf..2059c982 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -154,6 +154,10 @@ cmd_connect(gchar **args, struct cmd_help_t help)
                         cons_show("Error evaluating password, see logs for details.");
                         return TRUE;
                     }
+                    // strip trailing newline
+                    if (g_str_has_suffix(account->password, "\n")) {
+                        account->password[strlen(account->password)-1] = '\0';
+                    }
                 } else {
                     log_error("popen failed when running eval_password.");
                     cons_show("Error evaluating password, see logs for details.");
@@ -710,7 +714,7 @@ cmd_help(gchar **args, struct cmd_help_t help)
             "/rooms", "/tiny", "/who", "/nick", "/privileges", "/info", "/occupants" };
         _cmd_show_filtered_help("Groupchat commands", filter, ARRAY_SIZE(filter));
 
-    } else if (strcmp(args[0], "presence") == 0) {
+    } else if (strcmp(args[0], "presences") == 0) {
         gchar *filter[] = { "/autoaway", "/away", "/chat", "/dnd",
             "/online", "/priority", "/account", "/status", "/statuses", "/who",
             "/xa" };
diff --git a/src/command/history.c b/src/command/history.c
deleted file mode 100644
index 92846246..00000000
--- a/src/command/history.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * history.c
- *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
- *
- * This file is part of Profanity.
- *
- * Profanity is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Profanity is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
- *
- * In addition, as a special exception, the copyright holders give permission to
- * link the code of portions of this program with the OpenSSL library under
- * certain conditions as described in each individual source file, and
- * distribute linked combinations including the two.
- *
- * You must obey the GNU General Public License in all respects for all of the
- * code used other than OpenSSL. If you modify file(s) with this exception, you
- * may extend this exception to your version of the file(s), but you are not
- * obligated to do so. If you do not wish to do so, delete this exception
- * statement from your version. If you delete this exception statement from all
- * source files in the program, then also delete it here.
- *
- */
-
-#include "tools/history.h"
-
-#define MAX_HISTORY 100
-
-static History history;
-
-void _stringify_input(char *inp, int size, char *string);
-
-void
-cmd_history_init(void)
-{
-    history = history_new(MAX_HISTORY);
-}
-
-void
-cmd_history_append(char *inp)
-{
-    history_append(history, inp);
-}
-
-char *
-cmd_history_previous(char *inp, int *size)
-{
-    char inp_str[*size + 1];
-    _stringify_input(inp, *size, inp_str);
-
-    return history_previous(history, inp_str);
-}
-
-char *
-cmd_history_next(char *inp, int *size)
-{
-    char inp_str[*size + 1];
-    _stringify_input(inp, *size, inp_str);
-
-    return history_next(history, inp_str);
-}
-
-void
-_stringify_input(char *inp, int size, char *string)
-{
-    int i;
-    for (i = 0; i < size; i++) {
-        string[i] = inp[i];
-    }
-    string[size] = '\0';
-}
diff --git a/src/command/history.h b/src/command/history.h
deleted file mode 100644
index f2d7c26b..00000000
--- a/src/command/history.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * history.h
- *
- * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
- *
- * This file is part of Profanity.
- *
- * Profanity is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Profanity is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
- *
- * In addition, as a special exception, the copyright holders give permission to
- * link the code of portions of this program with the OpenSSL library under
- * certain conditions as described in each individual source file, and
- * distribute linked combinations including the two.
- *
- * You must obey the GNU General Public License in all respects for all of the
- * code used other than OpenSSL. If you modify file(s) with this exception, you
- * may extend this exception to your version of the file(s), but you are not
- * obligated to do so. If you do not wish to do so, delete this exception
- * statement from your version. If you delete this exception statement from all
- * source files in the program, then also delete it here.
- *
- */
-
-#ifndef COMMAND_HISTORY_H
-#define COMMAND_HISTORY_H
-
-void cmd_history_init(void);
-
-#endif
diff --git a/src/common.c b/src/common.c
index ffd12899..7638da31 100644
--- a/src/common.c
+++ b/src/common.c
@@ -191,7 +191,7 @@ str_replace(const char *string, const char *substr,
 }
 
 int
-str_contains(char str[], int size, char ch)
+str_contains(const char str[], int size, char ch)
 {
     int i;
     for (i = 0; i < size; i++) {
@@ -202,6 +202,28 @@ str_contains(char str[], int size, char ch)
     return 0;
 }
 
+int
+utf8_display_len(const char * const str)
+{
+    if (!str) {
+        return 0;
+    }
+
+    int len = 0;
+    gchar *curr = g_utf8_offset_to_pointer(str, 0);
+    while (*curr != '\0') {
+        gunichar curru = g_utf8_get_char(curr);
+        if (g_unichar_iswide(curru)) {
+            len += 2;
+        } else {
+            len ++;
+        }
+        curr = g_utf8_next_char(curr);
+    }
+
+    return len;
+}
+
 char *
 prof_getline(FILE *stream)
 {
diff --git a/src/common.h b/src/common.h
index 55451dea..26d4a99a 100644
--- a/src/common.h
+++ b/src/common.h
@@ -103,7 +103,8 @@ gboolean create_dir(char *name);
 gboolean mkdir_recursive(const char *dir);
 char * str_replace(const char *string, const char *substr,
     const char *replacement);
-int str_contains(char str[], int size, char ch);
+int str_contains(const char str[], int size, char ch);
+int utf8_display_len(const char * const str);
 char * prof_getline(FILE *stream);
 char* release_get_latest(void);
 gboolean release_is_new(char *found_version);
diff --git a/src/config/accounts.c b/src/config/accounts.c
index d86fe3af..4d4d47d0 100644
--- a/src/config/accounts.c
+++ b/src/config/accounts.c
@@ -117,13 +117,13 @@ accounts_close(void)
 }
 
 char *
-accounts_find_enabled(char *prefix)
+accounts_find_enabled(const char * const prefix)
 {
     return autocomplete_complete(enabled_ac, prefix, TRUE);
 }
 
 char *
-accounts_find_all(char *prefix)
+accounts_find_all(const char * const prefix)
 {
     return autocomplete_complete(all_ac, prefix, TRUE);
 }
diff --git a/src/config/accounts.h b/src/config/accounts.h
index a1dda018..cbbe88e6 100644
--- a/src/config/accounts.h
+++ b/src/config/accounts.h
@@ -43,8 +43,8 @@
 void accounts_load(void);
 void accounts_close(void);
 
-char * accounts_find_all(char *prefix);
-char * accounts_find_enabled(char *prefix);
+char * accounts_find_all(const char * const prefix);
+char * accounts_find_enabled(const char * const prefix);
 void accounts_reset_all_search(void);
 void accounts_reset_enabled_search(void);
 void accounts_add(const char *jid, const char *altdomain, const int port);
diff --git a/src/config/preferences.c b/src/config/preferences.c
index 5b683426..67f12b18 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -154,7 +154,7 @@ prefs_close(void)
 }
 
 char *
-prefs_autocomplete_boolean_choice(char *prefix)
+prefs_autocomplete_boolean_choice(const char * const prefix)
 {
     return autocomplete_complete(boolean_choice_ac, prefix, TRUE);
 }
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 9590eb64..68286f09 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -114,7 +114,7 @@ void prefs_close(void);
 
 char * prefs_find_login(char *prefix);
 void prefs_reset_login_search(void);
-char * prefs_autocomplete_boolean_choice(char *prefix);
+char * prefs_autocomplete_boolean_choice(const char * const prefix);
 void prefs_reset_boolean_choice(void);
 
 gint prefs_get_gone(void);
diff --git a/src/config/theme.c b/src/config/theme.c
index 6d3c5938..a5dbd0dd 100644
--- a/src/config/theme.c
+++ b/src/config/theme.c
@@ -592,7 +592,7 @@ theme_attrs(theme_item_t attrs)
     case THEME_BLACK_BOLD:              result = COLOR_PAIR(52); break;
     case THEME_MAGENTA:                 result = COLOR_PAIR(53); break;
     case THEME_MAGENTA_BOLD:            result = COLOR_PAIR(53); break;
-    default:                           break;
+    default:                            break;
     }
 
     if (g_hash_table_lookup(bold_items, GINT_TO_POINTER(attrs))) {
diff --git a/src/contact.c b/src/contact.c
index f16f1679..943be374 100644
--- a/src/contact.c
+++ b/src/contact.c
@@ -381,7 +381,7 @@ void
 p_contact_set_presence(const PContact contact, Resource *resource)
 {
     g_hash_table_replace(contact->available_resources, strdup(resource->name), resource);
-    autocomplete_add(contact->resource_ac, strdup(resource->name));
+    autocomplete_add(contact->resource_ac, resource->name);
 }
 
 void
diff --git a/src/main.c b/src/main.c
index f3b6a17f..7ee5affe 100644
--- a/src/main.c
+++ b/src/main.c
@@ -40,13 +40,7 @@
 #endif
 
 #include "profanity.h"
-
-#ifdef HAVE_LIBOTR
-#include "otr/otr.h"
-#endif
-#include "xmpp/xmpp.h"
-
-#include "ui/ui.h"
+#include "command/command.h"
 
 static gboolean disable_tls = FALSE;
 static gboolean version = FALSE;
@@ -56,6 +50,11 @@ static char *account_name = NULL;
 int
 main(int argc, char **argv)
 {
+    if (argc == 2 && g_strcmp0(argv[1], "docgen") == 0 && g_strcmp0(PACKAGE_STATUS, "development") == 0) {
+        command_docgen();
+        return 0;
+    }
+
     static GOptionEntry entries[] =
     {
         { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Show version information", NULL },
diff --git a/src/muc.c b/src/muc.c
index 74e0d66e..f50f3879 100644
--- a/src/muc.c
+++ b/src/muc.c
@@ -141,7 +141,7 @@ muc_invites_reset_ac(void)
 }
 
 char *
-muc_invites_find(char *search_str)
+muc_invites_find(const char * const search_str)
 {
     return autocomplete_complete(invite_ac, search_str, TRUE);
 }
@@ -632,8 +632,8 @@ muc_roster_nick_change_complete(const char * const room,
     return NULL;
 }
 
-void
-muc_autocomplete(char *input, int *size)
+char *
+muc_autocomplete(const char * const input)
 {
     win_type_t wintype = ui_current_win_type();
     if (wintype == WIN_MUC) {
@@ -641,8 +641,7 @@ muc_autocomplete(char *input, int *size)
         ChatRoom *chat_room = g_hash_table_lookup(rooms, mucwin->roomjid);
 
         if (chat_room && chat_room->nick_ac) {
-            input[*size] = '\0';
-            char *search_str = NULL;
+            const char * search_str = NULL;
 
             gchar *last_space = g_strrstr(input, " ");
             if (!last_space) {
@@ -664,12 +663,15 @@ muc_autocomplete(char *input, int *size)
                 if (!last_space || (*(last_space+1) == '\0')) {
                     g_string_append(replace_with, ": ");
                 }
-                ui_replace_input(input, replace_with->str, size);
-                g_string_free(replace_with, TRUE);
                 g_free(result);
+                result = replace_with->str;
+                g_string_free(replace_with, FALSE);
+                return result;
             }
         }
     }
+
+    return NULL;
 }
 
 void
diff --git a/src/muc.h b/src/muc.h
index 3f4ea876..01f8b44b 100644
--- a/src/muc.h
+++ b/src/muc.h
@@ -114,7 +114,7 @@ gint muc_invites_count(void);
 GSList* muc_invites(void);
 gboolean muc_invites_contain(const char * const room);
 void muc_invites_reset_ac(void);
-char* muc_invites_find(char *search_str);
+char* muc_invites_find(const char * const search_str);
 void muc_invites_clear(void);
 
 void muc_set_subject(const char * const room, const char * const subject);
@@ -123,7 +123,7 @@ char* muc_subject(const char * const room);
 void muc_pending_broadcasts_add(const char * const room, const char * const message);
 GList * muc_pending_broadcasts(const char * const room);
 
-void muc_autocomplete(char *input, int *size);
+char* muc_autocomplete(const char * const input);
 void muc_autocomplete_reset(const char * const room);
 
 gboolean muc_requires_config(const char * const room);
diff --git a/src/profanity.c b/src/profanity.c
index b28eae20..13297124 100644
--- a/src/profanity.c
+++ b/src/profanity.c
@@ -64,10 +64,11 @@
 #include "ui/ui.h"
 #include "ui/windows.h"
 
-static void _handle_idle_time(void);
+static void _check_autoaway(void);
 static void _init(const int disable_tls, char *log_level);
 static void _shutdown(void);
 static void _create_directories(void);
+static void _connect_default(const char * const account);
 
 static gboolean idle = FALSE;
 
@@ -75,62 +76,29 @@ void
 prof_run(const int disable_tls, char *log_level, char *account_name)
 {
     _init(disable_tls, log_level);
-    log_info("Starting main event loop");
-    ui_input_nonblocking(TRUE);
-    GTimer *timer = g_timer_new();
-    gboolean cmd_result = TRUE;
-    jabber_conn_status_t conn_status = jabber_get_connection_status();
-
-    char inp[INP_WIN_MAX];
-    int size = 0;
-
-    char *pref_connect_account = prefs_get_string(PREF_CONNECT_ACCOUNT);
-    if (account_name != NULL) {
-        char *cmd = "/connect";
-        snprintf(inp, sizeof(inp), "%s %s", cmd, account_name);
-        process_input(inp);
-    } else if (pref_connect_account != NULL) {
-        char *cmd = "/connect";
-        snprintf(inp, sizeof(inp), "%s %s", cmd, pref_connect_account);
-        process_input(inp);
-    }
-    prefs_free_string(pref_connect_account);
+    _connect_default(account_name);
     ui_update();
 
-    while(cmd_result == TRUE) {
-        wint_t ch = ERR;
-        int result;
-        size = 0;
-
-        while(ch != '\n') {
-            conn_status = jabber_get_connection_status();
-            if (conn_status == JABBER_CONNECTED) {
-                _handle_idle_time();
-            }
-
-            gdouble elapsed = g_timer_elapsed(timer, NULL);
-
-            gint remind_period = prefs_get_notify_remind();
-            if (remind_period > 0 && elapsed >= remind_period) {
-                notify_remind();
-                g_timer_start(timer);
-            }
+    char *line = NULL;
+    gboolean cmd_result = TRUE;
 
-            ch = ui_get_char(inp, &size, &result);
+    log_info("Starting main event loop");
 
-            ui_handle_special_keys(&ch, result);
+    while(cmd_result) {
+        while(!line) {
+            _check_autoaway();
+            line = ui_readline();
 #ifdef HAVE_LIBOTR
             otr_poll();
 #endif
+            notify_remind();
             jabber_process_events();
             ui_update();
         }
-
-        inp[size++] = '\0';
-        cmd_result = process_input(inp);
+        cmd_result = cmd_process_input(line);
+        ui_input_clear();
+        FREE_SET_NULL(line);
     }
-
-    g_timer_destroy(timer);
 }
 
 void
@@ -166,57 +134,38 @@ prof_handle_activity(void)
     }
 }
 
-/*
- * Take a line of input and process it, return TRUE if profanity is to
- * continue, FALSE otherwise
- */
-gboolean
-process_input(char *inp)
+static void
+_connect_default(const char * const account)
 {
-    log_debug("Input received: %s", inp);
-    gboolean result = FALSE;
-    g_strstrip(inp);
-
-    // add line to history if something typed
-    if (strlen(inp) > 0) {
-        cmd_history_append(inp);
-    }
-
-    // just carry on if no input
-    if (strlen(inp) == 0) {
-        result = TRUE;
-
-    // handle command if input starts with a '/'
-    } else if (inp[0] == '/') {
-        char *inp_cpy = strdup(inp);
-        char *command = strtok(inp_cpy, " ");
-        result = cmd_execute(command, inp);
-        free(inp_cpy);
-
-    // call a default handler if input didn't start with '/'
+    if (account) {
+        cmd_execute_connect(account);
     } else {
-        result = cmd_execute_default(inp);
+        char *pref_connect_account = prefs_get_string(PREF_CONNECT_ACCOUNT);
+        if (pref_connect_account) {
+            cmd_execute_connect(pref_connect_account);
+            prefs_free_string(pref_connect_account);
+        }
     }
-
-    ui_input_clear();
-    roster_reset_search_attempts();
-
-    return result;
 }
 
 static void
-_handle_idle_time()
+_check_autoaway()
 {
+    jabber_conn_status_t conn_status = jabber_get_connection_status();
+    if (conn_status != JABBER_CONNECTED) {
+        return;
+    }
+
     gint prefs_time = prefs_get_autoaway_time() * 60000;
-    resource_presence_t current_presence = accounts_get_last_presence(jabber_get_account_name());
     unsigned long idle_ms = ui_get_idle_time();
     char *pref_autoaway_mode = prefs_get_string(PREF_AUTOAWAY_MODE);
-    char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
 
     if (!idle) {
+        resource_presence_t current_presence = accounts_get_last_presence(jabber_get_account_name());
         if ((current_presence == RESOURCE_ONLINE) || (current_presence == RESOURCE_CHAT)) {
             if (idle_ms >= prefs_time) {
                 idle = TRUE;
+                char *pref_autoaway_message = prefs_get_string(PREF_AUTOAWAY_MESSAGE);
 
                 // handle away mode
                 if (strcmp(pref_autoaway_mode, "away") == 0) {
@@ -227,6 +176,8 @@ _handle_idle_time()
                 } else if (strcmp(pref_autoaway_mode, "idle") == 0) {
                     presence_update(RESOURCE_ONLINE, pref_autoaway_message, idle_ms / 1000);
                 }
+
+                prefs_free_string(pref_autoaway_message);
             }
         }
 
@@ -246,8 +197,8 @@ _handle_idle_time()
             }
         }
     }
+
     prefs_free_string(pref_autoaway_mode);
-    prefs_free_string(pref_autoaway_message);
 }
 
 static void
@@ -287,6 +238,7 @@ _init(const int disable_tls, char *log_level)
     otr_init();
 #endif
     atexit(_shutdown);
+    ui_input_nonblocking(TRUE);
 }
 
 static void
diff --git a/src/roster_list.c b/src/roster_list.c
index 2d01d205..44d05ff0 100644
--- a/src/roster_list.c
+++ b/src/roster_list.c
@@ -344,13 +344,13 @@ roster_has_pending_subscriptions(void)
 }
 
 char *
-roster_contact_autocomplete(char *search_str)
+roster_contact_autocomplete(const char * const search_str)
 {
     return autocomplete_complete(name_ac, search_str, TRUE);
 }
 
 char *
-roster_fulljid_autocomplete(char *search_str)
+roster_fulljid_autocomplete(const char * const search_str)
 {
     return autocomplete_complete(fulljid_ac, search_str, TRUE);
 }
@@ -406,13 +406,13 @@ roster_get_groups(void)
 }
 
 char *
-roster_group_autocomplete(char *search_str)
+roster_group_autocomplete(const char * const search_str)
 {
     return autocomplete_complete(groups_ac, search_str, TRUE);
 }
 
 char *
-roster_barejid_autocomplete(char *search_str)
+roster_barejid_autocomplete(const char * const search_str)
 {
     return autocomplete_complete(barejid_ac, search_str, TRUE);
 }
diff --git a/src/roster_list.h b/src/roster_list.h
index 7743ece6..e193085b 100644
--- a/src/roster_list.h
+++ b/src/roster_list.h
@@ -59,12 +59,12 @@ char * roster_barejid_from_name(const char * const name);
 GSList * roster_get_contacts(void);
 GSList * roster_get_contacts_online(void);
 gboolean roster_has_pending_subscriptions(void);
-char * roster_contact_autocomplete(char *search_str);
-char * roster_fulljid_autocomplete(char *search_str);
+char * roster_contact_autocomplete(const char * const search_str);
+char * roster_fulljid_autocomplete(const char * const search_str);
 GSList * roster_get_group(const char * const group);
 GSList * roster_get_groups(void);
-char * roster_group_autocomplete(char *search_str);
-char * roster_barejid_autocomplete(char *search_str);
+char * roster_group_autocomplete(const char * const search_str);
+char * roster_barejid_autocomplete(const char * const search_str);
 GSList * roster_get_contacts_by_presence(const char * const presence);
 GSList * roster_get_nogroup(void);
 
diff --git a/src/tools/autocomplete.c b/src/tools/autocomplete.c
index 486fd2ba..2623c828 100644
--- a/src/tools/autocomplete.c
+++ b/src/tools/autocomplete.c
@@ -169,7 +169,7 @@ autocomplete_contains(Autocomplete ac, const char *value)
 }
 
 gchar *
-autocomplete_complete(Autocomplete ac, gchar *search_str, gboolean quote)
+autocomplete_complete(Autocomplete ac, const gchar *search_str, gboolean quote)
 {
     gchar *found = NULL;
 
@@ -216,8 +216,7 @@ autocomplete_complete(Autocomplete ac, gchar *search_str, gboolean quote)
 }
 
 char *
-autocomplete_param_with_func(char *input, int *size, char *command,
-    autocomplete_func func)
+autocomplete_param_with_func(const char * const input, char *command, autocomplete_func func)
 {
     GString *auto_msg = NULL;
     char *result = NULL;
@@ -225,15 +224,16 @@ autocomplete_param_with_func(char *input, int *size, char *command,
     sprintf(command_cpy, "%s ", command);
     int len = strlen(command_cpy);
 
-    if ((strncmp(input, command_cpy, len) == 0) && (*size > len)) {
+    if ((strncmp(input, command_cpy, len) == 0) && (strlen(input) > len)) {
         int i;
-        char inp_cpy[*size];
-        for(i = len; i < *size; i++) {
-            inp_cpy[i-len] = input[i];
+        int inp_len = strlen(input);
+        char prefix[inp_len];
+        for(i = len; i < inp_len; i++) {
+            prefix[i-len] = input[i];
         }
-        inp_cpy[(*size) - len] = '\0';
+        prefix[inp_len - len] = '\0';
 
-        char *found = func(inp_cpy);
+        char *found = func(prefix);
         if (found) {
             auto_msg = g_string_new(command_cpy);
             g_string_append(auto_msg, found);
@@ -247,23 +247,23 @@ autocomplete_param_with_func(char *input, int *size, char *command,
 }
 
 char *
-autocomplete_param_with_ac(char *input, int *size, char *command,
-    Autocomplete ac, gboolean quote)
+autocomplete_param_with_ac(const char * const input, char *command, Autocomplete ac, gboolean quote)
 {
     GString *auto_msg = NULL;
     char *result = NULL;
     char *command_cpy = malloc(strlen(command) + 2);
     sprintf(command_cpy, "%s ", command);
     int len = strlen(command_cpy);
-    if ((strncmp(input, command_cpy, len) == 0) && (*size > len)) {
+    int inp_len = strlen(input);
+    if ((strncmp(input, command_cpy, len) == 0) && (strlen(input) > len)) {
         int i;
-        char inp_cpy[*size];
-        for(i = len; i < *size; i++) {
-            inp_cpy[i-len] = input[i];
+        char prefix[inp_len];
+        for(i = len; i < inp_len; i++) {
+            prefix[i-len] = input[i];
         }
-        inp_cpy[(*size) - len] = '\0';
+        prefix[inp_len - len] = '\0';
 
-        char *found = autocomplete_complete(ac, inp_cpy, quote);
+        char *found = autocomplete_complete(ac, prefix, quote);
         if (found) {
             auto_msg = g_string_new(command_cpy);
             g_string_append(auto_msg, found);
@@ -278,28 +278,18 @@ autocomplete_param_with_ac(char *input, int *size, char *command,
 }
 
 char *
-autocomplete_param_no_with_func(char *input, int *size, char *command,
-    int arg_number, autocomplete_func func)
+autocomplete_param_no_with_func(const char * const input, char *command, int arg_number, autocomplete_func func)
 {
-    if (strncmp(input, command, strlen(command)) == 0 && (*size > strlen(command))) {
-        int i = 0;
+    if (strncmp(input, command, strlen(command)) == 0 && (strlen(input) > strlen(command))) {
         GString *result_str = NULL;
 
-        // copy and null terminate input
-        gchar inp_cpy[*size];
-        for (i = 0; i < *size; i++) {
-            inp_cpy[i] = input[i];
-        }
-        inp_cpy[i] = '\0';
-        g_strstrip(inp_cpy);
-
         // count tokens properly
-        int num_tokens = count_tokens(inp_cpy);
+        int num_tokens = count_tokens(input);
 
         // if correct number of tokens, then candidate for autocompletion of last param
         if (num_tokens == arg_number) {
-            gchar *start_str = get_start(inp_cpy, arg_number);
-            gchar *comp_str = g_strdup(&inp_cpy[strlen(start_str)]);
+            gchar *start_str = get_start(input, arg_number);
+            gchar *comp_str = g_strdup(&input[strlen(start_str)]);
 
             // autocomplete param
             if (comp_str) {
diff --git a/src/tools/autocomplete.h b/src/tools/autocomplete.h
index a029b7ef..70cd8f30 100644
--- a/src/tools/autocomplete.h
+++ b/src/tools/autocomplete.h
@@ -37,7 +37,7 @@
 
 #include <glib.h>
 
-typedef char*(*autocomplete_func)(char *);
+typedef char*(*autocomplete_func)(const char * const);
 typedef struct autocomplete_t *Autocomplete;
 
 // allocate new autocompleter with no items
@@ -53,18 +53,18 @@ void autocomplete_add(Autocomplete ac, const char *item);
 void autocomplete_remove(Autocomplete ac, const char * const item);
 
 // find the next item prefixed with search string
-gchar * autocomplete_complete(Autocomplete ac, gchar *search_str, gboolean quote);
+gchar * autocomplete_complete(Autocomplete ac, const gchar *search_str, gboolean quote);
 
 GSList * autocomplete_create_list(Autocomplete ac);
 gint autocomplete_length(Autocomplete ac);
 
-char * autocomplete_param_with_func(char *input, int *size, char *command,
+char * autocomplete_param_with_func(const char * const input, char *command,
     autocomplete_func func);
 
-char * autocomplete_param_with_ac(char *input, int *size, char *command,
+char * autocomplete_param_with_ac(const char * const input, char *command,
     Autocomplete ac, gboolean quote);
 
-char * autocomplete_param_no_with_func(char *input, int *size, char *command,
+char * autocomplete_param_no_with_func(const char * const input, char *command,
     int arg_number, autocomplete_func func);
 
 void autocomplete_reset(Autocomplete ac);
diff --git a/src/tools/parser.c b/src/tools/parser.c
index ae149155..e91b227d 100644
--- a/src/tools/parser.c
+++ b/src/tools/parser.c
@@ -316,7 +316,7 @@ parse_args_with_freetext(const char * const inp, int min, int max, gboolean *res
 }
 
 int
-count_tokens(char *string)
+count_tokens(const char * const string)
 {
     int length = g_utf8_strlen(string, -1);
     gboolean in_quotes = FALSE;
@@ -347,7 +347,7 @@ count_tokens(char *string)
 }
 
 char *
-get_start(char *string, int tokens)
+get_start(const char * const string, int tokens)
 {
     GString *result = g_string_new("");
     int length = g_utf8_strlen(string, -1);
diff --git a/src/tools/parser.h b/src/tools/parser.h
index 7ecef3fc..eeb97df3 100644
--- a/src/tools/parser.h
+++ b/src/tools/parser.h
@@ -39,8 +39,8 @@
 
 gchar** parse_args(const char * const inp, int min, int max, gboolean *result);
 gchar** parse_args_with_freetext(const char * const inp, int min, int max, gboolean *result);
-int count_tokens(char *string);
-char* get_start(char *string, int tokens);
+int count_tokens(const char * const string);
+char* get_start(const char * const string, int tokens);
 GHashTable* parse_options(gchar **args, gchar **keys, gboolean *res);
 void options_destroy(GHashTable *options);
 
diff --git a/src/ui/console.c b/src/ui/console.c
index dd50d6d3..cdf5d1b8 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1402,7 +1402,7 @@ cons_help(void)
     cons_show("/help basic      - List basic commands for getting started.");
     cons_show("/help chatting   - List chat commands.");
     cons_show("/help groupchat  - List groupchat commands.");
-    cons_show("/help presence   - List commands to change presence.");
+    cons_show("/help presences  - List commands to change presence.");
     cons_show("/help contacts   - List commands for manipulating your roster.");
     cons_show("/help service    - List service discovery commands.");
     cons_show("/help settings   - List commands for changing settings.");
diff --git a/src/ui/core.c b/src/ui/core.c
index e0f46f6e..85d5748a 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -74,14 +74,15 @@
 
 static char *win_title;
 
+static int inp_size;
+
 #ifdef HAVE_LIBXSS
 static Display *display;
 #endif
 
 static GTimer *ui_idle_time;
 
-static void _win_handle_switch(const wint_t * const ch);
-static void _win_handle_page(const wint_t * const ch, const int result);
+static void _win_handle_switch(const wint_t ch);
 static void _win_show_history(int win_index, const char * const contact);
 static void _ui_draw_term_title(void);
 
@@ -103,11 +104,13 @@ ui_init(void)
     status_bar_active(1);
     create_input_window();
     wins_init();
+    notifier_initialise();
     cons_about();
 #ifdef HAVE_LIBXSS
     display = XOpenDisplay(0);
 #endif
     ui_idle_time = g_timer_new();
+    inp_size = 0;
     ProfWin *window = wins_get_current();
     win_update_virtual(window);
 }
@@ -174,30 +177,42 @@ ui_close(void)
     endwin();
 }
 
-wint_t
-ui_get_char(char *input, int *size, int *result)
+char*
+ui_readline(void)
 {
-    wint_t ch = inp_get_char(input, size, result);
-    if (ch != ERR && *result != ERR) {
+    int key_type;
+    wint_t ch;
+
+    char *line = inp_read(&key_type, &ch);
+    _win_handle_switch(ch);
+
+    ProfWin *current = wins_get_current();
+    win_handle_page(current, ch, key_type);
+
+    if (ch == KEY_RESIZE) {
+        ui_resize();
+    }
+
+    if (ch != ERR && key_type != ERR) {
         ui_reset_idle_time();
         ui_input_nonblocking(TRUE);
     } else {
         ui_input_nonblocking(FALSE);
     }
 
-    return ch;
+    return line;
 }
 
 void
-ui_input_clear(void)
+ui_inp_history_append(char *inp)
 {
-    inp_win_reset();
+    inp_history_append(inp);
 }
 
 void
-ui_replace_input(char *input, const char * const new_input, int *size)
+ui_input_clear(void)
 {
-    inp_replace_input(input, new_input, size);
+    inp_win_reset();
 }
 
 void
@@ -702,16 +717,6 @@ ui_disconnected(void)
 }
 
 void
-ui_handle_special_keys(const wint_t * const ch, const int result)
-{
-    _win_handle_switch(ch);
-    _win_handle_page(ch, result);
-    if (*ch == KEY_RESIZE) {
-        ui_resize();
-    }
-}
-
-void
 ui_close_connected_win(int index)
 {
     ProfWin *window = wins_get_by_num(index);
@@ -1413,8 +1418,8 @@ ui_outgoing_chat_msg(const char * const from, const char * const barejid,
     // create new window
     if (window == NULL) {
         window = wins_new_chat(barejid);
-        ProfChatWin *chatwin = (ProfChatWin*)window;
 #ifdef HAVE_LIBOTR
+        ProfChatWin *chatwin = (ProfChatWin*)window;
         if (otr_is_secure(barejid)) {
             chatwin->is_otr = TRUE;
         }
@@ -2935,143 +2940,32 @@ ui_hide_roster(void)
 }
 
 static void
-_win_handle_switch(const wint_t * const ch)
+_win_handle_switch(const wint_t ch)
 {
-    if (*ch == KEY_F(1)) {
+    if (ch == KEY_F(1)) {
         ui_switch_win(1);
-    } else if (*ch == KEY_F(2)) {
+    } else if (ch == KEY_F(2)) {
         ui_switch_win(2);
-    } else if (*ch == KEY_F(3)) {
+    } else if (ch == KEY_F(3)) {
         ui_switch_win(3);
-    } else if (*ch == KEY_F(4)) {
+    } else if (ch == KEY_F(4)) {
         ui_switch_win(4);
-    } else if (*ch == KEY_F(5)) {
+    } else if (ch == KEY_F(5)) {
         ui_switch_win(5);
-    } else if (*ch == KEY_F(6)) {
+    } else if (ch == KEY_F(6)) {
         ui_switch_win(6);
-    } else if (*ch == KEY_F(7)) {
+    } else if (ch == KEY_F(7)) {
         ui_switch_win(7);
-    } else if (*ch == KEY_F(8)) {
+    } else if (ch == KEY_F(8)) {
         ui_switch_win(8);
-    } else if (*ch == KEY_F(9)) {
+    } else if (ch == KEY_F(9)) {
         ui_switch_win(9);
-    } else if (*ch == KEY_F(10)) {
+    } else if (ch == KEY_F(10)) {
         ui_switch_win(0);
     }
 }
 
 static void
-_win_handle_page(const wint_t * const ch, const int result)
-{
-    ProfWin *current = wins_get_current();
-    int rows = getmaxy(stdscr);
-    int y = getcury(current->layout->win);
-
-    int page_space = rows - 4;
-    int *page_start = &(current->layout->y_pos);
-
-    if (prefs_get_boolean(PREF_MOUSE)) {
-        MEVENT mouse_event;
-
-        if (*ch == KEY_MOUSE) {
-            if (getmouse(&mouse_event) == OK) {
-
-#ifdef PLATFORM_CYGWIN
-                if (mouse_event.bstate & BUTTON5_PRESSED) { // mouse wheel down
-#else
-                if (mouse_event.bstate & BUTTON2_PRESSED) { // mouse wheel down
-#endif
-                    *page_start += 4;
-
-                    // only got half a screen, show full screen
-                    if ((y - (*page_start)) < page_space)
-                        *page_start = y - page_space;
-
-                    // went past end, show full screen
-                    else if (*page_start >= y)
-                        *page_start = y - page_space;
-
-                    current->layout->paged = 1;
-                    win_update_virtual(current);
-                } else if (mouse_event.bstate & BUTTON4_PRESSED) { // mouse wheel up
-                    *page_start -= 4;
-
-                    // went past beginning, show first page
-                    if (*page_start < 0)
-                        *page_start = 0;
-
-                    current->layout->paged = 1;
-                    win_update_virtual(current);
-                }
-            }
-        }
-    }
-
-    // page up
-    if (*ch == KEY_PPAGE) {
-        *page_start -= page_space;
-
-        // went past beginning, show first page
-        if (*page_start < 0)
-            *page_start = 0;
-
-        current->layout->paged = 1;
-        win_update_virtual(current);
-
-    // page down
-    } else if (*ch == KEY_NPAGE) {
-        *page_start += page_space;
-
-        // only got half a screen, show full screen
-        if ((y - (*page_start)) < page_space)
-            *page_start = y - page_space;
-
-        // went past end, show full screen
-        else if (*page_start >= y)
-            *page_start = y - page_space - 1;
-
-        current->layout->paged = 1;
-        win_update_virtual(current);
-    }
-
-    // switch off page if last line and space line visible
-    if ((y) - *page_start == page_space) {
-        current->layout->paged = 0;
-    }
-
-    if (current->layout->type == LAYOUT_SPLIT) {
-        ProfLayoutSplit *split_layout = (ProfLayoutSplit*)current->layout;
-        int sub_y = getcury(split_layout->subwin);
-        int *sub_y_pos = &(split_layout->sub_y_pos);
-
-        // alt up arrow
-        if ((result == KEY_CODE_YES) && ((*ch == 565) || (*ch == 337))) {
-            *sub_y_pos -= page_space;
-
-            // went past beginning, show first page
-            if (*sub_y_pos < 0)
-                *sub_y_pos = 0;
-
-            win_update_virtual(current);
-
-        // alt down arrow
-        } else if ((result == KEY_CODE_YES) && ((*ch == 524) || (*ch == 336))) {
-            *sub_y_pos += page_space;
-
-            // only got half a screen, show full screen
-            if ((sub_y- (*sub_y_pos)) < page_space)
-                *sub_y_pos = sub_y - page_space;
-
-            // went past end, show full screen
-            else if (*sub_y_pos >= sub_y)
-                *sub_y_pos = sub_y - page_space - 1;
-
-            win_update_virtual(current);
-        }
-    }
-}
-
-static void
 _win_show_history(int win_index, const char * const contact)
 {
     ProfWin *window = wins_get_by_num(win_index);
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index a6877a86..85ddc79a 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -50,6 +50,7 @@
 #include "config/accounts.h"
 #include "config/preferences.h"
 #include "config/theme.h"
+#include "tools/history.h"
 #include "log.h"
 #include "muc.h"
 #include "profanity.h"
@@ -72,17 +73,25 @@
 #define KEY_CTRL_U 0025
 #define KEY_CTRL_W 0027
 
+#define MAX_HISTORY 100
+#define INP_WIN_MAX 1000
+
 static WINDOW *inp_win;
+static History history;
+
+static char input[INP_WIN_MAX];
+static int input_len_bytes;
+
 static int pad_start = 0;
 static int rows, cols;
 
-static int _handle_edit(int result, const wint_t ch, char *input, int *size);
-static int _handle_alt_key(char *input, int *size, int key);
-static void _handle_backspace(int display_size, int inp_x, int *size, char *input);
+static int _handle_edit(int key_type, const wint_t ch);
+static int _handle_alt_key(int key);
+static void _handle_backspace(void);
 static int _printable(const wint_t ch);
 static void _clear_input(void);
-static void _go_to_end(int display_size);
-static void _delete_previous_word(char *input, int *size);
+static void _go_to_end(void);
+static void _delete_previous_word(void);
 
 void
 create_input_window(void)
@@ -98,6 +107,7 @@ create_input_window(void)
     keypad(inp_win, TRUE);
     wmove(inp_win, 0, 0);
     _inp_win_update_virtual();
+    history = history_new(MAX_HISTORY);
 }
 
 void
@@ -131,38 +141,34 @@ inp_block(void)
     wtimeout(inp_win, -1);
 }
 
-wint_t
-inp_get_char(char *input, int *size, int *result)
+char *
+inp_read(int *key_type, wint_t *ch)
 {
-    wint_t ch;
-    int display_size = 0;
-
-    if (*size != 0) {
-        display_size = g_utf8_strlen(input, *size);
-    }
+    int display_size = utf8_display_len(input);
 
     // echo off, and get some more input
     noecho();
-    *result = wget_wch(inp_win, &ch);
+    *key_type = wget_wch(inp_win, ch);
 
     gboolean in_command = FALSE;
     if ((display_size > 0 && input[0] == '/') ||
-            (display_size == 0 && ch == '/')) {
+            (display_size == 0 && *ch == '/')) {
         in_command = TRUE;
     }
 
-    if (*result == ERR) {
+    if (*key_type == ERR) {
         prof_handle_idle();
     }
-    if ((*result != ERR) && (*result != KEY_CODE_YES) && !in_command && _printable(ch)) {
+    if ((*key_type != ERR) && (*key_type != KEY_CODE_YES) && !in_command && _printable(*ch)) {
         prof_handle_activity();
     }
 
     // if it wasn't an arrow key etc
-    if (!_handle_edit(*result, ch, input, size)) {
-        if (_printable(ch) && *result != KEY_CODE_YES) {
-            if (*size >= INP_WIN_MAX) {
-                return ERR;
+    if (!_handle_edit(*key_type, *ch)) {
+        if (_printable(*ch) && *key_type != KEY_CODE_YES) {
+            if (input_len_bytes >= INP_WIN_MAX) {
+                *ch = ERR;
+                return NULL;
             }
 
             int inp_x = getcurx(inp_win);
@@ -170,11 +176,11 @@ inp_get_char(char *input, int *size, int *result)
             // handle insert if not at end of input
             if (inp_x < display_size) {
                 char bytes[MB_CUR_MAX];
-                size_t utf_len = wcrtomb(bytes, ch, NULL);
+                size_t utf_len = wcrtomb(bytes, *ch, NULL);
 
                 char *next_ch = g_utf8_offset_to_pointer(input, inp_x);
                 char *offset;
-                for (offset = &input[*size - 1]; offset >= next_ch; offset--) {
+                for (offset = &input[input_len_bytes - 1]; offset >= next_ch; offset--) {
                     *(offset + utf_len) = *offset;
                 }
                 int i;
@@ -182,8 +188,8 @@ inp_get_char(char *input, int *size, int *result)
                      *(next_ch + i) = bytes[i];
                 }
 
-                *size += utf_len;
-                input[*size] = '\0';
+                input_len_bytes += utf_len;
+                input[input_len_bytes] = '\0';
                 waddstr(inp_win, next_ch);
                 wmove(inp_win, 0, inp_x + 1);
 
@@ -195,15 +201,15 @@ inp_get_char(char *input, int *size, int *result)
             // otherwise just append
             } else {
                 char bytes[MB_CUR_MAX+1];
-                size_t utf_len = wcrtomb(bytes, ch, NULL);
+                size_t utf_len = wcrtomb(bytes, *ch, NULL);
 
                 // wcrtomb can return (size_t) -1
                 if (utf_len < MB_CUR_MAX) {
                     int i;
                     for (i = 0 ; i < utf_len; i++) {
-                        input[(*size)++] = bytes[i];
+                        input[input_len_bytes++] = bytes[i];
                     }
-                    input[*size] = '\0';
+                    input[input_len_bytes] = '\0';
 
                     bytes[utf_len] = '\0';
                     waddstr(inp_win, bytes);
@@ -225,7 +231,13 @@ inp_get_char(char *input, int *size, int *result)
 
     echo();
 
-    return ch;
+    if (*ch == '\n') {
+        input[input_len_bytes] = '\0';
+        input_len_bytes = 0;
+        return strdup(input);
+    } else {
+        return NULL;
+    }
 }
 
 void
@@ -248,16 +260,14 @@ inp_put_back(void)
 }
 
 void
-inp_replace_input(char *input, const char * const new_input, int *size)
+inp_replace_input(const char * const new_input)
 {
-    int display_size;
     strncpy(input, new_input, INP_WIN_MAX);
-    *size = strlen(input);
-    display_size = g_utf8_strlen(input, *size);
+    input_len_bytes = strlen(input);
     inp_win_reset();
-    input[*size] = '\0';
+    input[input_len_bytes] = '\0';
     waddstr(inp_win, input);
-    _go_to_end(display_size);
+    _go_to_end();
 }
 
 void
@@ -268,6 +278,12 @@ inp_win_reset(void)
     _inp_win_update_virtual();
 }
 
+void
+inp_history_append(char *inp)
+{
+    history_append(history, inp);
+}
+
 static void
 _clear_input(void)
 {
@@ -281,23 +297,17 @@ _clear_input(void)
  * return 0 if it wasn't
  */
 static int
-_handle_edit(int result, const wint_t ch, char *input, int *size)
+_handle_edit(int key_type, const wint_t ch)
 {
     char *prev = NULL;
     char *next = NULL;
-    int inp_x = 0;
+    int inp_x = getcurx(inp_win);
     int next_ch;
-    int display_size = 0;
-
-    if (*size != 0) {
-        display_size = g_utf8_strlen(input, *size);
-    }
-
-    inp_x = getcurx(inp_win);
+    int display_size = utf8_display_len(input);
 
     // CTRL-LEFT
-    if ((result == KEY_CODE_YES) && (ch == 547 || ch == 545 || ch == 544 || ch == 540 || ch == 539) && (inp_x > 0)) {
-        input[*size] = '\0';
+    if ((key_type == KEY_CODE_YES) && (ch == 547 || ch == 545 || ch == 544 || ch == 540 || ch == 539) && (inp_x > 0)) {
+        input[input_len_bytes] = '\0';
         gchar *curr_ch = g_utf8_offset_to_pointer(input, inp_x);
         curr_ch = g_utf8_find_prev_char(input, curr_ch);
         gchar *prev_ch;
@@ -346,8 +356,8 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
         return 1;
 
     // CTRL-RIGHT
-    } else if ((result == KEY_CODE_YES) && (ch == 562 || ch == 560 || ch == 555 || ch == 559 || ch == 554) && (inp_x < display_size)) {
-        input[*size] = '\0';
+    } else if ((key_type == KEY_CODE_YES) && (ch == 562 || ch == 560 || ch == 555 || ch == 559 || ch == 554) && (inp_x < display_size)) {
+        input[input_len_bytes] = '\0';
         gchar *curr_ch = g_utf8_offset_to_pointer(input, inp_x);
         gchar *next_ch = g_utf8_find_next_char(curr_ch, NULL);
         gunichar curr_uni;
@@ -389,12 +399,12 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
         return 1;
 
     // ALT-LEFT
-    } else if ((result == KEY_CODE_YES) && (ch == 537 || ch == 542)) {
+    } else if ((key_type == KEY_CODE_YES) && (ch == 537 || ch == 542)) {
         ui_previous_win();
         return 1;
 
     // ALT-RIGHT
-    } else if ((result == KEY_CODE_YES) && (ch == 552 || ch == 557)) {
+    } else if ((key_type == KEY_CODE_YES) && (ch == 552 || ch == 557)) {
         ui_next_win();
         return 1;
 
@@ -406,34 +416,34 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             // check for ALT-key
             next_ch = wgetch(inp_win);
             if (next_ch != ERR) {
-                return _handle_alt_key(input, size, next_ch);
+                return _handle_alt_key(next_ch);
             } else {
-                *size = 0;
+                input_len_bytes = 0;
                 inp_win_reset();
                 return 1;
             }
 
         case 127:
-            _handle_backspace(display_size, inp_x, size, input);
+            _handle_backspace();
             return 1;
         case KEY_BACKSPACE:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
-            _handle_backspace(display_size, inp_x, size, input);
+            _handle_backspace();
             return 1;
 
         case KEY_DC: // DEL
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_D:
             if (inp_x == display_size-1) {
                 gchar *start = g_utf8_substring(input, 0, inp_x);
-                for (*size = 0; *size < strlen(start); (*size)++) {
-                    input[*size] = start[*size];
+                for (input_len_bytes = 0; input_len_bytes < strlen(start); input_len_bytes++) {
+                    input[input_len_bytes] = start[input_len_bytes];
                 }
-                input[*size] = '\0';
+                input[input_len_bytes] = '\0';
 
                 g_free(start);
 
@@ -441,14 +451,14 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
                 waddstr(inp_win, input);
             } else if (inp_x < display_size-1) {
                 gchar *start = g_utf8_substring(input, 0, inp_x);
-                gchar *end = g_utf8_substring(input, inp_x+1, *size);
+                gchar *end = g_utf8_substring(input, inp_x+1, input_len_bytes);
                 GString *new = g_string_new(start);
                 g_string_append(new, end);
 
-                for (*size = 0; *size < strlen(new->str); (*size)++) {
-                    input[*size] = new->str[*size];
+                for (input_len_bytes = 0; input_len_bytes < strlen(new->str); input_len_bytes++) {
+                    input[input_len_bytes] = new->str[input_len_bytes];
                 }
-                input[*size] = '\0';
+                input[input_len_bytes] = '\0';
 
                 g_free(start);
                 g_free(end);
@@ -461,7 +471,7 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             return 1;
 
         case KEY_LEFT:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_B:
@@ -477,7 +487,7 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             return 1;
 
         case KEY_RIGHT:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_F:
@@ -493,33 +503,35 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             return 1;
 
         case KEY_UP:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_P:
-            prev = cmd_history_previous(input, size);
+            input[input_len_bytes] = '\0';
+            prev = history_previous(history, input);
             if (prev) {
-                inp_replace_input(input, prev, size);
+                inp_replace_input(prev);
             }
             return 1;
 
         case KEY_DOWN:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_N:
-            next = cmd_history_next(input, size);
+            input[input_len_bytes] = '\0';
+            next = history_next(history, input);
             if (next) {
-                inp_replace_input(input, next, size);
-            } else if (*size != 0) {
-                input[*size] = '\0';
-                cmd_history_append(input);
-                inp_replace_input(input, "", size);
+                inp_replace_input(next);
+            } else if (input_len_bytes != 0) {
+                input[input_len_bytes] = '\0';
+                history_append(history, input);
+                inp_replace_input("");
             }
             return 1;
 
         case KEY_HOME:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_A:
@@ -529,31 +541,40 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
             return 1;
 
         case KEY_END:
-            if (result != KEY_CODE_YES) {
+            if (key_type != KEY_CODE_YES) {
                 return 0;
             }
         case KEY_CTRL_E:
-            _go_to_end(display_size);
+            _go_to_end();
             return 1;
 
         case 9: // tab
-            if (*size != 0) {
+            if (input_len_bytes != 0) {
+                input[input_len_bytes] = '\0';
                 if ((strncmp(input, "/", 1) != 0) && (ui_current_win_type() == WIN_MUC)) {
-                    muc_autocomplete(input, size);
+                    char *result = muc_autocomplete(input);
+                    if (result) {
+                        inp_replace_input(result);
+                        free(result);
+                    }
                 } else if (strncmp(input, "/", 1) == 0) {
-                    cmd_autocomplete(input, size);
+                    char *result = cmd_autocomplete(input);
+                    if (result) {
+                        inp_replace_input(result);
+                        free(result);
+                    }
                 }
             }
             return 1;
 
         case KEY_CTRL_W:
-            _delete_previous_word(input, size);
+            _delete_previous_word();
             return 1;
             break;
 
         case KEY_CTRL_U:
             while (getcurx(inp_win) > 0) {
-                _delete_previous_word(input, size);
+                _delete_previous_word();
             }
             return 1;
             break;
@@ -565,18 +586,20 @@ _handle_edit(int result, const wint_t ch, char *input, int *size)
 }
 
 static void
-_handle_backspace(int display_size, int inp_x, int *size, char *input)
+_handle_backspace(void)
 {
+    int inp_x = getcurx(inp_win);
+    int display_size = utf8_display_len(input);
     roster_reset_search_attempts();
     if (display_size > 0) {
 
         // if at end, delete last char
         if (inp_x >= display_size) {
             gchar *start = g_utf8_substring(input, 0, inp_x-1);
-            for (*size = 0; *size < strlen(start); (*size)++) {
-                input[*size] = start[*size];
+            for (input_len_bytes = 0; input_len_bytes < strlen(start); input_len_bytes++) {
+                input[input_len_bytes] = start[input_len_bytes];
             }
-            input[*size] = '\0';
+            input[input_len_bytes] = '\0';
 
             g_free(start);
 
@@ -587,14 +610,14 @@ _handle_backspace(int display_size, int inp_x, int *size, char *input)
         // if in middle, delete and shift chars left
         } else if (inp_x > 0 && inp_x < display_size) {
             gchar *start = g_utf8_substring(input, 0, inp_x - 1);
-            gchar *end = g_utf8_substring(input, inp_x, *size);
+            gchar *end = g_utf8_substring(input, inp_x, input_len_bytes);
             GString *new = g_string_new(start);
             g_string_append(new, end);
 
-            for (*size = 0; *size < strlen(new->str); (*size)++) {
-                input[*size] = new->str[*size];
+            for (input_len_bytes = 0; input_len_bytes < strlen(new->str); input_len_bytes++) {
+                input[input_len_bytes] = new->str[input_len_bytes];
             }
-            input[*size] = '\0';
+            input[input_len_bytes] = '\0';
 
             g_free(start);
             g_free(end);
@@ -619,7 +642,7 @@ _handle_backspace(int display_size, int inp_x, int *size, char *input)
 }
 
 static int
-_handle_alt_key(char *input, int *size, int key)
+_handle_alt_key(int key)
 {
     switch (key)
     {
@@ -661,7 +684,7 @@ _handle_alt_key(char *input, int *size, int key)
             break;
         case 263:
         case 127:
-            _delete_previous_word(input, size);
+            _delete_previous_word();
             break;
         default:
             break;
@@ -670,12 +693,12 @@ _handle_alt_key(char *input, int *size, int key)
 }
 
 static void
-_delete_previous_word(char *input, int *size)
+_delete_previous_word(void)
 {
     int end_del = getcurx(inp_win);
     int start_del = end_del;
 
-    input[*size] = '\0';
+    input[input_len_bytes] = '\0';
     gchar *curr_ch = g_utf8_offset_to_pointer(input, end_del);
     curr_ch = g_utf8_find_prev_char(input, curr_ch);
     gchar *prev_ch;
@@ -721,8 +744,8 @@ _delete_previous_word(char *input, int *size)
         input[strlen(start_string)+i] = end_string[i];
     }
 
-    *size = strlen(start_string)+i;
-    input[*size] = '\0';
+    input_len_bytes = strlen(start_string)+i;
+    input[input_len_bytes] = '\0';
 
     _clear_input();
     waddstr(inp_win, input);
@@ -740,8 +763,9 @@ _delete_previous_word(char *input, int *size)
 }
 
 static void
-_go_to_end(int display_size)
+_go_to_end(void)
 {
+    int display_size = utf8_display_len(input);
     wmove(inp_win, 0, display_size);
     if (display_size > cols-2) {
         pad_start = display_size - cols + 1;
diff --git a/src/ui/inputwin.h b/src/ui/inputwin.h
index b5a26c10..39fde720 100644
--- a/src/ui/inputwin.h
+++ b/src/ui/inputwin.h
@@ -36,13 +36,14 @@
 #define UI_INPUTWIN_H
 
 void create_input_window(void);
-wint_t inp_get_char(char *input, int *size, int *result);
+char* inp_read(int *key_type, wint_t *ch);
 void inp_win_reset(void);
 void inp_win_resize(void);
 void inp_put_back(void);
 void inp_non_block(gint);
 void inp_block(void);
 void inp_get_password(char *passwd);
-void inp_replace_input(char *input, const char * const new_input, int *size);
+void inp_replace_input(const char * const new_input);
+void inp_history_append(char *inp);
 
 #endif
diff --git a/src/ui/notifier.c b/src/ui/notifier.c
index ff93443f..7ca8f705 100644
--- a/src/ui/notifier.c
+++ b/src/ui/notifier.c
@@ -48,10 +48,19 @@
 #include "log.h"
 #include "muc.h"
 #include "ui/ui.h"
+#include "config/preferences.h"
 
 static void _notify(const char * const message, int timeout,
     const char * const category);
 
+static GTimer *remind_timer;
+
+void
+notifier_initialise(void)
+{
+    remind_timer = g_timer_new();
+}
+
 void
 notifier_uninit(void)
 {
@@ -60,6 +69,7 @@ notifier_uninit(void)
         notify_uninit();
     }
 #endif
+    g_timer_destroy(remind_timer);
 }
 
 void
@@ -128,46 +138,52 @@ notify_subscription(const char * const from)
 void
 notify_remind(void)
 {
-    gint unread = ui_unread();
-    gint open = muc_invites_count();
-    gint subs = presence_sub_request_count();
+    gdouble elapsed = g_timer_elapsed(remind_timer, NULL);
+    gint remind_period = prefs_get_notify_remind();
+    if (remind_period > 0 && elapsed >= remind_period) {
+        gint unread = ui_unread();
+        gint open = muc_invites_count();
+        gint subs = presence_sub_request_count();
 
-    GString *text = g_string_new("");
+        GString *text = g_string_new("");
 
-    if (unread > 0) {
-        if (unread == 1) {
-            g_string_append(text, "1 unread message");
-        } else {
-            g_string_append_printf(text, "%d unread messages", unread);
-        }
-
-    }
-    if (open > 0) {
         if (unread > 0) {
-            g_string_append(text, "\n");
+            if (unread == 1) {
+                g_string_append(text, "1 unread message");
+            } else {
+                g_string_append_printf(text, "%d unread messages", unread);
+            }
+
         }
-        if (open == 1) {
-            g_string_append(text, "1 room invite");
-        } else {
-            g_string_append_printf(text, "%d room invites", open);
+        if (open > 0) {
+            if (unread > 0) {
+                g_string_append(text, "\n");
+            }
+            if (open == 1) {
+                g_string_append(text, "1 room invite");
+            } else {
+                g_string_append_printf(text, "%d room invites", open);
+            }
         }
-    }
-    if (subs > 0) {
-        if ((unread > 0) || (open > 0)) {
-            g_string_append(text, "\n");
+        if (subs > 0) {
+            if ((unread > 0) || (open > 0)) {
+                g_string_append(text, "\n");
+            }
+            if (subs == 1) {
+                g_string_append(text, "1 subscription request");
+            } else {
+                g_string_append_printf(text, "%d subscription requests", subs);
+            }
         }
-        if (subs == 1) {
-            g_string_append(text, "1 subscription request");
-        } else {
-            g_string_append_printf(text, "%d subscription requests", subs);
+
+        if ((unread > 0) || (open > 0) || (subs > 0)) {
+            _notify(text->str, 5000, "Incoming message");
         }
-    }
 
-    if ((unread > 0) || (open > 0) || (subs > 0)) {
-        _notify(text->str, 5000, "Incoming message");
-    }
+        g_string_free(text, TRUE);
 
-    g_string_free(text, TRUE);
+        g_timer_start(remind_timer);
+    }
 }
 
 static void
diff --git a/src/ui/ui.h b/src/ui/ui.h
index e28914ff..99e73b4a 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -51,8 +51,6 @@
 #include "ui/window.h"
 #include "xmpp/xmpp.h"
 
-#define INP_WIN_MAX 1000
-
 // ui startup and control
 void ui_init(void);
 void ui_load_colours(void);
@@ -61,7 +59,6 @@ void ui_close(void);
 void ui_redraw(void);
 void ui_resize(void);
 GSList* ui_get_chat_recipients(void);
-void ui_handle_special_keys(const wint_t * const ch, const int result);
 gboolean ui_switch_win(const int i);
 void ui_next_win(void);
 void ui_previous_win(void);
@@ -230,10 +227,9 @@ void ui_update_presence(const resource_presence_t resource_presence,
 void ui_about(void);
 void ui_statusbar_new(const int win);
 
-wint_t ui_get_char(char *input, int *size, int *result);
+char * ui_readline(void);
 void ui_input_clear(void);
 void ui_input_nonblocking(gboolean);
-void ui_replace_input(char *input, const char * const new_input, int *size);
 
 void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void));
 
@@ -243,6 +239,8 @@ void ui_open_xmlconsole_win(void);
 
 gboolean ui_win_has_unsaved_form(int num);
 
+void ui_inp_history_append(char *inp);
+
 // console window actions
 void cons_show(const char * const msg, ...);
 void cons_about(void);
@@ -330,6 +328,7 @@ void rosterwin_roster(void);
 void occupantswin_occupants(const char * const room);
 
 // desktop notifier actions
+void notifier_initialise(void);
 void notifier_uninit(void);
 
 void notify_typing(const char * const handle);
diff --git a/src/ui/window.c b/src/ui/window.c
index 3a45ab01..54f1b99f 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -330,6 +330,7 @@ win_free(ProfWin* window)
         buffer_free(window->layout->buffer);
         delwin(window->layout->win);
     }
+    free(window->layout);
 
     if (window->type == WIN_CHAT) {
         ProfChatWin *chatwin = (ProfChatWin*)window;
@@ -358,6 +359,116 @@ win_free(ProfWin* window)
 }
 
 void
+win_handle_page(ProfWin *window, const wint_t ch, const int result)
+{
+    int rows = getmaxy(stdscr);
+    int y = getcury(window->layout->win);
+
+    int page_space = rows - 4;
+    int *page_start = &(window->layout->y_pos);
+
+    if (prefs_get_boolean(PREF_MOUSE)) {
+        MEVENT mouse_event;
+
+        if (ch == KEY_MOUSE) {
+            if (getmouse(&mouse_event) == OK) {
+
+#ifdef PLATFORM_CYGWIN
+                if (mouse_event.bstate & BUTTON5_PRESSED) { // mouse wheel down
+#else
+                if (mouse_event.bstate & BUTTON2_PRESSED) { // mouse wheel down
+#endif
+                    *page_start += 4;
+
+                    // only got half a screen, show full screen
+                    if ((y - (*page_start)) < page_space)
+                        *page_start = y - page_space;
+
+                    // went past end, show full screen
+                    else if (*page_start >= y)
+                        *page_start = y - page_space;
+
+                    window->layout->paged = 1;
+                    win_update_virtual(window);
+                } else if (mouse_event.bstate & BUTTON4_PRESSED) { // mouse wheel up
+                    *page_start -= 4;
+
+                    // went past beginning, show first page
+                    if (*page_start < 0)
+                        *page_start = 0;
+
+                    window->layout->paged = 1;
+                    win_update_virtual(window);
+                }
+            }
+        }
+    }
+
+    // page up
+    if (ch == KEY_PPAGE) {
+        *page_start -= page_space;
+
+        // went past beginning, show first page
+        if (*page_start < 0)
+            *page_start = 0;
+
+        window->layout->paged = 1;
+        win_update_virtual(window);
+
+    // page down
+    } else if (ch == KEY_NPAGE) {
+        *page_start += page_space;
+
+        // only got half a screen, show full screen
+        if ((y - (*page_start)) < page_space)
+            *page_start = y - page_space;
+
+        // went past end, show full screen
+        else if (*page_start >= y)
+            *page_start = y - page_space - 1;
+
+        window->layout->paged = 1;
+        win_update_virtual(window);
+    }
+
+    // switch off page if last line and space line visible
+    if ((y) - *page_start == page_space) {
+        window->layout->paged = 0;
+    }
+
+    if (window->layout->type == LAYOUT_SPLIT) {
+        ProfLayoutSplit *split_layout = (ProfLayoutSplit*)window->layout;
+        int sub_y = getcury(split_layout->subwin);
+        int *sub_y_pos = &(split_layout->sub_y_pos);
+
+        // alt up arrow
+        if ((result == KEY_CODE_YES) && ((ch == 565) || (ch == 337))) {
+            *sub_y_pos -= page_space;
+
+            // went past beginning, show first page
+            if (*sub_y_pos < 0)
+                *sub_y_pos = 0;
+
+            win_update_virtual(window);
+
+        // alt down arrow
+        } else if ((result == KEY_CODE_YES) && ((ch == 524) || (ch == 336))) {
+            *sub_y_pos += page_space;
+
+            // only got half a screen, show full screen
+            if ((sub_y- (*sub_y_pos)) < page_space)
+                *sub_y_pos = sub_y - page_space;
+
+            // went past end, show full screen
+            else if (*sub_y_pos >= sub_y)
+                *sub_y_pos = sub_y - page_space - 1;
+
+            win_update_virtual(window);
+        }
+    }
+}
+
+void
 win_update_virtual(ProfWin *window)
 {
     int rows, cols;
diff --git a/src/ui/window.h b/src/ui/window.h
index b6bd0298..fd10a1d7 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -37,6 +37,8 @@
 
 #include "config.h"
 
+#include <wchar.h>
+
 #ifdef HAVE_NCURSESW_NCURSES_H
 #include <ncursesw/ncurses.h>
 #elif HAVE_NCURSES_H
@@ -176,6 +178,7 @@ void win_show_subwin(ProfWin *window);
 int win_roster_cols(void);
 int win_occpuants_cols(void);
 void win_printline_nowrap(WINDOW *win, char *msg);
+void win_handle_page(ProfWin *current, const wint_t ch, const int result);
 
 int win_unread(ProfWin *window);
 gboolean win_has_active_subwin(ProfWin *window);
diff --git a/src/xmpp/bookmark.c b/src/xmpp/bookmark.c
index ddc6e300..94adabea 100644
--- a/src/xmpp/bookmark.c
+++ b/src/xmpp/bookmark.c
@@ -220,7 +220,7 @@ bookmark_get_list(void)
 }
 
 char *
-bookmark_find(char *search_str)
+bookmark_find(const char * const search_str)
 {
     return autocomplete_complete(bookmark_ac, search_str, TRUE);
 }
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index 1b3e7fc7..65384a0d 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -158,7 +158,7 @@ presence_clear_sub_requests(void)
 }
 
 char *
-presence_sub_request_find(char * search_str)
+presence_sub_request_find(const char * const search_str)
 {
     return autocomplete_complete(sub_requests_ac, search_str, TRUE);
 }
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 161eebdf..a004a4bf 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -163,7 +163,7 @@ void presence_subscription(const char * const jid, const jabber_subscr_t action)
 GSList* presence_get_subscription_requests(void);
 gint presence_sub_request_count(void);
 void presence_reset_sub_request_search(void);
-char * presence_sub_request_find(char * search_str);
+char * presence_sub_request_find(const char * const search_str);
 void presence_join_room(char *room, char *nick, char * passwd);
 void presence_change_room_nick(const char * const room, const char * const nick);
 void presence_leave_chat_room(const char * const room_jid);
@@ -208,7 +208,7 @@ gboolean bookmark_update(const char *jid, const char *nick, const char *password
 gboolean bookmark_remove(const char *jid);
 gboolean bookmark_join(const char *jid);
 const GList * bookmark_get_list(void);
-char * bookmark_find(char *search_str);
+char * bookmark_find(const char * const search_str);
 void bookmark_autocomplete_reset(void);
 
 void roster_send_name_change(const char * const barejid, const char * const new_name, GSList *groups);
diff --git a/tests/test_common.c b/tests/test_common.c
index bac03cfd..0c4530a0 100644
--- a/tests/test_common.c
+++ b/tests/test_common.c
@@ -544,3 +544,46 @@ void test_p_sha1_hash7(void **state)
 
     assert_string_equal(result, "bNfKVfqEOGmzlH8M+e8FYTB46SU=");
 }
+
+void utf8_display_len_null_str(void **state)
+{
+    int result = utf8_display_len(NULL);
+
+    assert_int_equal(0, result);
+}
+
+void utf8_display_len_1_non_wide(void **state)
+{
+    int result = utf8_display_len("1");
+
+    assert_int_equal(1, result);
+}
+
+void utf8_display_len_1_wide(void **state)
+{
+    int result = utf8_display_len("四");
+
+    assert_int_equal(2, result);
+}
+
+void utf8_display_len_non_wide(void **state)
+{
+    int result = utf8_display_len("123456789abcdef");
+
+    assert_int_equal(15, result);
+}
+
+void utf8_display_len_wide(void **state)
+{
+    int result = utf8_display_len("12三四56");
+
+    assert_int_equal(8, result);
+}
+
+void utf8_display_len_all_wide(void **state)
+{
+    int result = utf8_display_len("ひらがな");
+
+    assert_int_equal(8, result);
+}
+
diff --git a/tests/test_common.h b/tests/test_common.h
index 8b5128fd..1866e73d 100644
--- a/tests/test_common.h
+++ b/tests/test_common.h
@@ -46,3 +46,9 @@ void test_p_sha1_hash5(void **state);
 void test_p_sha1_hash6(void **state);
 void test_p_sha1_hash6(void **state);
 void test_p_sha1_hash7(void **state);
+void utf8_display_len_null_str(void **state);
+void utf8_display_len_1_non_wide(void **state);
+void utf8_display_len_1_wide(void **state);
+void utf8_display_len_non_wide(void **state);
+void utf8_display_len_wide(void **state);
+void utf8_display_len_all_wide(void **state);
\ No newline at end of file
diff --git a/tests/testsuite.c b/tests/testsuite.c
index 32d298cf..cf511c59 100644
--- a/tests/testsuite.c
+++ b/tests/testsuite.c
@@ -85,6 +85,12 @@ int main(int argc, char* argv[]) {
         unit_test(test_p_sha1_hash5),
         unit_test(test_p_sha1_hash6),
         unit_test(test_p_sha1_hash7),
+        unit_test(utf8_display_len_null_str),
+        unit_test(utf8_display_len_1_non_wide),
+        unit_test(utf8_display_len_1_wide),
+        unit_test(utf8_display_len_non_wide),
+        unit_test(utf8_display_len_wide),
+        unit_test(utf8_display_len_all_wide),
 
         unit_test(clear_empty),
         unit_test(reset_after_create),
diff --git a/tests/ui/stub_ui.c b/tests/ui/stub_ui.c
index 76b71265..51b82d42 100644
--- a/tests/ui/stub_ui.c
+++ b/tests/ui/stub_ui.c
@@ -64,8 +64,6 @@ GSList* ui_get_chat_recipients(void)
     return NULL;
 }
 
-void ui_handle_special_keys(const wint_t * const ch, const int result) {}
-
 gboolean ui_switch_win(const int i)
 {
     check_expected(i);
@@ -325,14 +323,15 @@ void ui_update_presence(const resource_presence_t resource_presence,
 void ui_about(void) {}
 void ui_statusbar_new(const int win) {}
 
-wint_t ui_get_char(char *input, int *size, int *result)
+char * ui_readline(void)
 {
-    return 0;
+    return NULL;
 }
 
+void ui_inp_history_append(char *inp) {}
+
 void ui_input_clear(void) {}
 void ui_input_nonblocking(gboolean reset) {}
-void ui_replace_input(char *input, const char * const new_input, int *size) {}
 
 void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void)) {}
 
diff --git a/tests/xmpp/stub_xmpp.c b/tests/xmpp/stub_xmpp.c
index cc5ad5fc..281857f0 100644
--- a/tests/xmpp/stub_xmpp.c
+++ b/tests/xmpp/stub_xmpp.c
@@ -91,7 +91,7 @@ gint presence_sub_request_count(void)
 
 void presence_reset_sub_request_search(void) {}
 
-char * presence_sub_request_find(char * search_str)
+char * presence_sub_request_find(const char * const search_str)
 {
     return  NULL;
 }
@@ -189,7 +189,7 @@ const GList * bookmark_get_list(void)
     return (GList *)mock();
 }
 
-char * bookmark_find(char *search_str)
+char * bookmark_find(const char * const search_str)
 {
     return NULL;
 }