about summary refs log tree commit diff stats
path: root/src/command
diff options
context:
space:
mode:
Diffstat (limited to 'src/command')
-rw-r--r--src/command/cmd_ac.c246
-rw-r--r--src/command/cmd_defs.c125
-rw-r--r--src/command/cmd_defs.h1
-rw-r--r--src/command/cmd_funcs.c756
-rw-r--r--src/command/cmd_funcs.h14
5 files changed, 1047 insertions, 95 deletions
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 58ad758a..5abee8be 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -2,6 +2,7 @@
  * cmd_ac.c
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -57,6 +58,10 @@
 #include "pgp/gpg.h"
 #endif
 
+#ifdef HAVE_OMEMO
+#include "omemo/omemo.h"
+#endif
+
 static char* _sub_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _notify_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _theme_autocomplete(ProfWin *window, const char *const input, gboolean previous);
@@ -69,6 +74,7 @@ static char* _group_autocomplete(ProfWin *window, const char *const input, gbool
 static char* _bookmark_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _otr_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _pgp_autocomplete(ProfWin *window, const char *const input, gboolean previous);
+static char* _omemo_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _connect_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _alias_autocomplete(ProfWin *window, const char *const input, gboolean previous);
 static char* _join_autocomplete(ProfWin *window, const char *const input, gboolean previous);
@@ -157,6 +163,9 @@ static Autocomplete bookmark_property_ac;
 static Autocomplete otr_ac;
 static Autocomplete otr_log_ac;
 static Autocomplete otr_policy_ac;
+static Autocomplete omemo_ac;
+static Autocomplete omemo_log_ac;
+static Autocomplete omemo_policy_ac;
 static Autocomplete connect_property_ac;
 static Autocomplete tls_property_ac;
 static Autocomplete alias_ac;
@@ -175,6 +184,9 @@ static Autocomplete form_field_multi_ac;
 static Autocomplete occupants_ac;
 static Autocomplete occupants_default_ac;
 static Autocomplete occupants_show_ac;
+static Autocomplete occupants_header_ac;
+static Autocomplete occupants_header_char_ac;
+static Autocomplete occupants_char_ac;
 static Autocomplete time_ac;
 static Autocomplete time_format_ac;
 static Autocomplete resource_ac;
@@ -237,6 +249,7 @@ cmd_ac_init(void)
     autocomplete_add(prefs_ac, "presence");
     autocomplete_add(prefs_ac, "otr");
     autocomplete_add(prefs_ac, "pgp");
+    autocomplete_add(prefs_ac, "omemo");
 
     notify_ac = autocomplete_new();
     autocomplete_add(notify_ac, "chat");
@@ -574,6 +587,27 @@ cmd_ac_init(void)
     autocomplete_add(otr_policy_ac, "opportunistic");
     autocomplete_add(otr_policy_ac, "always");
 
+    omemo_ac = autocomplete_new();
+    autocomplete_add(omemo_ac, "gen");
+    autocomplete_add(omemo_ac, "log");
+    autocomplete_add(omemo_ac, "start");
+    autocomplete_add(omemo_ac, "end");
+    autocomplete_add(omemo_ac, "trust");
+    autocomplete_add(omemo_ac, "untrust");
+    autocomplete_add(omemo_ac, "fingerprint");
+    autocomplete_add(omemo_ac, "clear_device_list");
+    autocomplete_add(omemo_ac, "policy");
+
+    omemo_log_ac = autocomplete_new();
+    autocomplete_add(omemo_log_ac, "on");
+    autocomplete_add(omemo_log_ac, "off");
+    autocomplete_add(omemo_log_ac, "redact");
+
+    omemo_policy_ac = autocomplete_new();
+    autocomplete_add(omemo_policy_ac, "manual");
+    autocomplete_add(omemo_policy_ac, "automatic");
+    autocomplete_add(omemo_policy_ac, "always");
+
     connect_property_ac = autocomplete_new();
     autocomplete_add(connect_property_ac, "server");
     autocomplete_add(connect_property_ac, "port");
@@ -653,6 +687,10 @@ cmd_ac_init(void)
     autocomplete_add(occupants_ac, "hide");
     autocomplete_add(occupants_ac, "default");
     autocomplete_add(occupants_ac, "size");
+    autocomplete_add(occupants_ac, "indent");
+    autocomplete_add(occupants_ac, "header");
+    autocomplete_add(occupants_ac, "wrap");
+    autocomplete_add(occupants_ac, "char");
 
     occupants_default_ac = autocomplete_new();
     autocomplete_add(occupants_default_ac, "show");
@@ -661,6 +699,15 @@ cmd_ac_init(void)
     occupants_show_ac = autocomplete_new();
     autocomplete_add(occupants_show_ac, "jid");
 
+    occupants_char_ac = autocomplete_new();
+    autocomplete_add(occupants_char_ac, "none");
+
+    occupants_header_ac = autocomplete_new();
+    autocomplete_add(occupants_header_ac, "char");
+
+    occupants_header_char_ac = autocomplete_new();
+    autocomplete_add(occupants_header_char_ac, "none");
+
     time_ac = autocomplete_new();
     autocomplete_add(time_ac, "console");
     autocomplete_add(time_ac, "chat");
@@ -670,6 +717,7 @@ cmd_ac_init(void)
     autocomplete_add(time_ac, "xml");
     autocomplete_add(time_ac, "statusbar");
     autocomplete_add(time_ac, "lastactivity");
+    autocomplete_add(time_ac, "all");
 
     time_format_ac = autocomplete_new();
     autocomplete_add(time_format_ac, "set");
@@ -983,6 +1031,9 @@ cmd_ac_reset(ProfWin *window)
 #ifdef HAVE_LIBGPGME
     p_gpg_autocomplete_key_reset();
 #endif
+#ifdef HAVE_OMEMO
+    omemo_fingerprint_autocomplete_reset();
+#endif
     autocomplete_reset(help_ac);
     autocomplete_reset(help_commands_ac);
     autocomplete_reset(notify_ac);
@@ -1052,6 +1103,9 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(otr_ac);
     autocomplete_reset(otr_log_ac);
     autocomplete_reset(otr_policy_ac);
+    autocomplete_reset(omemo_ac);
+    autocomplete_reset(omemo_log_ac);
+    autocomplete_reset(omemo_policy_ac);
     autocomplete_reset(connect_property_ac);
     autocomplete_reset(tls_property_ac);
     autocomplete_reset(alias_ac);
@@ -1068,8 +1122,11 @@ cmd_ac_reset(ProfWin *window)
     autocomplete_reset(form_ac);
     autocomplete_reset(form_field_multi_ac);
     autocomplete_reset(occupants_ac);
+    autocomplete_reset(occupants_char_ac);
     autocomplete_reset(occupants_default_ac);
     autocomplete_reset(occupants_show_ac);
+    autocomplete_reset(occupants_header_ac);
+    autocomplete_reset(occupants_header_char_ac);
     autocomplete_reset(time_ac);
     autocomplete_reset(time_format_ac);
     autocomplete_reset(resource_ac);
@@ -1179,6 +1236,9 @@ cmd_ac_uninit(void)
     autocomplete_free(otr_ac);
     autocomplete_free(otr_log_ac);
     autocomplete_free(otr_policy_ac);
+    autocomplete_free(omemo_ac);
+    autocomplete_free(omemo_log_ac);
+    autocomplete_free(omemo_policy_ac);
     autocomplete_free(connect_property_ac);
     autocomplete_free(tls_property_ac);
     autocomplete_free(alias_ac);
@@ -1195,8 +1255,10 @@ cmd_ac_uninit(void)
     autocomplete_free(form_ac);
     autocomplete_free(form_field_multi_ac);
     autocomplete_free(occupants_ac);
+    autocomplete_free(occupants_char_ac);
     autocomplete_free(occupants_default_ac);
     autocomplete_free(occupants_show_ac);
+    autocomplete_free(occupants_header_ac);
     autocomplete_free(time_ac);
     autocomplete_free(time_format_ac);
     autocomplete_free(resource_ac);
@@ -1228,11 +1290,15 @@ cmd_ac_uninit(void)
     autocomplete_free(statusbar_show_ac);
 }
 
+static void
+_filepath_item_free(char **ptr) {
+    char *item = *ptr;
+    free(item);
+}
+
 char*
 cmd_ac_complete_filepath(const char *const input, char *const startstr, gboolean previous)
 {
-    static char* last_directory = NULL;
-
     unsigned int output_off = 0;
 
     char *result = NULL;
@@ -1260,11 +1326,13 @@ cmd_ac_complete_filepath(const char *const input, char *const startstr, gboolean
     // expand ~ to $HOME
     if (inpcp[0] == '~' && inpcp[1] == '/') {
         if (asprintf(&tmp, "%s/%sfoo", getenv("HOME"), inpcp+2) == -1) {
+            free(inpcp);
             return NULL;
         }
         output_off = strlen(getenv("HOME"))+1;
     } else {
         if (asprintf(&tmp, "%sfoo", inpcp) == -1) {
+            free(inpcp);
             return NULL;
         }
     }
@@ -1277,56 +1345,61 @@ cmd_ac_complete_filepath(const char *const input, char *const startstr, gboolean
     free(inpcp);
     free(inpcp2);
 
-    if (!last_directory || strcmp(last_directory, directory) != 0) {
-        free(last_directory);
-        last_directory = directory;
-        autocomplete_reset(filepath_ac);
-
-        struct dirent *dir;
-
-        DIR *d = opendir(directory);
-        if (d) {
-            while ((dir = readdir(d)) != NULL) {
-                if (strcmp(dir->d_name, ".") == 0) {
-                    continue;
-                } else if (strcmp(dir->d_name, "..") == 0) {
-                    continue;
-                } else if (*(dir->d_name) == '.' && *foofile != '.') {
-                    // only show hidden files on explicit request
-                    continue;
+    struct dirent *dir;
+    GArray *files = g_array_new(TRUE, FALSE, sizeof(char *));
+    g_array_set_clear_func(files, (GDestroyNotify)_filepath_item_free);
+
+    DIR *d = opendir(directory);
+    if (d) {
+        while ((dir = readdir(d)) != NULL) {
+            if (strcmp(dir->d_name, ".") == 0) {
+                continue;
+            } else if (strcmp(dir->d_name, "..") == 0) {
+                continue;
+            } else if (*(dir->d_name) == '.' && *foofile != '.') {
+                // only show hidden files on explicit request
+                continue;
+            }
+
+            char *acstring;
+            if (output_off) {
+                if (asprintf(&tmp, "%s/%s", directory, dir->d_name) == -1) {
+                    free(directory);
+                    free(foofile);
+                    return NULL;
+                }
+                if (asprintf(&acstring, "~/%s", tmp+output_off) == -1) {
+                    free(directory);
+                    free(foofile);
+                   return NULL;
+                }
+                free(tmp);
+            } else if (strcmp(directory, "/") == 0) {
+                if (asprintf(&acstring, "/%s", dir->d_name) == -1) {
+                    free(directory);
+                    free(foofile);
+                    return NULL;
                 }
-                char * acstring;
-                if (output_off) {
-                    if (asprintf(&tmp, "%s/%s", directory, dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                    if (asprintf(&acstring, "~/%s", tmp+output_off) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                    free(tmp);
-                } else if (strcmp(directory, "/") == 0) {
-                    if (asprintf(&acstring, "/%s", dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
-                } else {
-                    if (asprintf(&acstring, "%s/%s", directory, dir->d_name) == -1) {
-                        free(foofile);
-                        return NULL;
-                    }
+            } else {
+                if (asprintf(&acstring, "%s/%s", directory, dir->d_name) == -1) {
+                    free(directory);
+                    free(foofile);
+                    return NULL;
                 }
-                autocomplete_add(filepath_ac, acstring);
-                free(acstring);
             }
-            closedir(d);
+
+            char *acstring_cpy = strdup(acstring);
+            g_array_append_val(files, acstring_cpy);
+            free(acstring);
         }
-    } else {
-        free(directory);
+        closedir(d);
     }
+    free(directory);
     free(foofile);
 
+    autocomplete_update(filepath_ac, (char **)files->data);
+    g_array_free(files, TRUE);
+
     result = autocomplete_param_with_ac(input, startstr, filepath_ac, TRUE, previous);
     if (result) {
         return result;
@@ -1438,6 +1511,7 @@ _cmd_ac_complete_params(ProfWin *window, const char *const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/autoconnect",   _autoconnect_autocomplete);
     g_hash_table_insert(ac_funcs, "/otr",           _otr_autocomplete);
     g_hash_table_insert(ac_funcs, "/pgp",           _pgp_autocomplete);
+    g_hash_table_insert(ac_funcs, "/omemo",         _omemo_autocomplete);
     g_hash_table_insert(ac_funcs, "/connect",       _connect_autocomplete);
     g_hash_table_insert(ac_funcs, "/alias",         _alias_autocomplete);
     g_hash_table_insert(ac_funcs, "/join",          _join_autocomplete);
@@ -2118,6 +2192,63 @@ _pgp_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 }
 
 static char*
+_omemo_autocomplete(ProfWin *window, const char *const input, gboolean previous)
+{
+    char *found = NULL;
+
+    found = autocomplete_param_with_ac(input, "/omemo log", omemo_log_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/omemo policy", omemo_policy_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    jabber_conn_status_t conn_status = connection_get_status();
+
+    if (conn_status == JABBER_CONNECTED) {
+        found = autocomplete_param_with_func(input, "/omemo start", roster_contact_autocomplete, previous);
+        if (found) {
+            return found;
+        }
+
+        found = autocomplete_param_with_func(input, "/omemo fingerprint", roster_contact_autocomplete, previous);
+        if (found) {
+            return found;
+        }
+
+
+#ifdef HAVE_OMEMO
+        if (window->type == WIN_CHAT) {
+            found = autocomplete_param_with_func(input, "/omemo trust", omemo_fingerprint_autocomplete, previous);
+            if (found) {
+                return found;
+            }
+        } else {
+            found = autocomplete_param_with_func(input, "/omemo trust", roster_contact_autocomplete, previous);
+            if (found) {
+                return found;
+            }
+
+            found = autocomplete_param_no_with_func(input, "/omemo trust", 4, omemo_fingerprint_autocomplete, previous);
+            if (found) {
+                return found;
+            }
+        }
+#endif
+    }
+
+    found = autocomplete_param_with_ac(input, "/omemo", omemo_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    return NULL;
+}
+
+static char*
 _plugins_autocomplete(ProfWin *window, const char *const input, gboolean previous)
 {
     char *result = NULL;
@@ -2453,6 +2584,11 @@ _occupants_autocomplete(ProfWin *window, const char *const input, gboolean previ
         return found;
     }
 
+    found = autocomplete_param_with_ac(input, "/occupants char", occupants_char_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
     found = autocomplete_param_with_ac(input, "/occupants default hide", occupants_show_ac, TRUE, previous);
     if (found) {
         return found;
@@ -2473,6 +2609,21 @@ _occupants_autocomplete(ProfWin *window, const char *const input, gboolean previ
         return found;
     }
 
+    found = autocomplete_param_with_ac(input, "/occupants header char", roster_char_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_ac(input, "/occupants header", occupants_header_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
+    found = autocomplete_param_with_func(input, "/occupants wrap", prefs_autocomplete_boolean_choice, previous);
+    if (found) {
+        return found;
+    }
+
     found = autocomplete_param_with_ac(input, "/occupants", occupants_ac, TRUE, previous);
     if (found) {
         return found;
@@ -2526,6 +2677,11 @@ _time_autocomplete(ProfWin *window, const char *const input, gboolean previous)
         return found;
     }
 
+    found = autocomplete_param_with_ac(input, "/time all", time_format_ac, TRUE, previous);
+    if (found) {
+        return found;
+    }
+
     found = autocomplete_param_with_ac(input, "/time", time_ac, TRUE, previous);
     if (found) {
         return found;
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 4447020b..e7ece238 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -2,6 +2,7 @@
  * cmd_defs.c
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -372,7 +373,7 @@ static struct cmd_t command_defs[] =
             { "resource indent <indent>",   "Indent resource line by <indent> spaces (0 to 10)." },
             { "resource join on|off",       "Join resource with previous line when only one available resource." },
             { "presence indent <indent>",   "Indent presence line by <indent> spaces (-1 to 10), a value of -1 will show presence on the previous line." },
-            { "size <precent>",             "Percentage of the screen taken up by the roster (1-99)." },
+            { "size <percent>",             "Percentage of the screen taken up by the roster (1-99)." },
             { "wrap on|off",                "Enable or disable line wrapping in roster panel." },
             { "add <jid> [<nick>]",         "Add a new item to the roster." },
             { "remove <jid>",               "Removes an item from the roster." },
@@ -401,12 +402,14 @@ static struct cmd_t command_defs[] =
             "/blocked add [<jid>]",
             "/blocked remove <jid>")
         CMD_DESC(
-            "Manage blocked users, calling with no arguments shows the current list of blocked users.")
+            "Manage blocked users (XEP-0191), calling with no arguments shows the current list of blocked users."
+            "To blog a certain user in a MUC use the following as jid: room@conference.example.org/spammy-user")
         CMD_ARGS(
             { "add [<jid>]",    "Block the specified Jabber ID. If in a chat window and no jid is specified, the current recipient will be blocked." },
             { "remove <jid>",   "Remove the specified Jabber ID from the blocked list." })
         CMD_EXAMPLES(
-            "/blocked add spammer@spam.org")
+            "/blocked add spammer@spam.org",
+            "/blocked add profanity@rooms.dismail.de/spammy-user")
     },
 
     { "/group",
@@ -570,10 +573,10 @@ static struct cmd_t command_defs[] =
             { "password <password>", "Password if the room requires one." })
         CMD_EXAMPLES(
             "/join",
-            "/join jdev@conference.jabber.org",
-            "/join jdev@conference.jabber.org nick mynick",
+            "/join profanity@rooms.dismail.de",
+            "/join profanity@rooms.dismail.de nick mynick",
             "/join private@conference.jabber.org nick mynick password mypassword",
-            "/join jdev")
+            "/join mychannel")
     },
 
     { "/leave",
@@ -752,18 +755,28 @@ static struct cmd_t command_defs[] =
             CMD_TAG_UI)
         CMD_SYN(
             "/occupants show|hide [jid]",
+            "/occupants char <char>|none",
             "/occupants default show|hide [jid]",
-            "/occupants size [<percent>]")
+            "/occupants size [<percent>]",
+            "/occupants indent <indent>",
+            "/occupants header char <char>|none",
+            "/occupants wrap on|off")
         CMD_DESC(
             "Show or hide room occupants, and occupants panel display settings.")
         CMD_ARGS(
             { "show",                  "Show the occupants panel in current room." },
+            { "char <char>",           "Prefix occupants with specified character." },
+            { "char none",             "Remove occupants character prefix." },
             { "hide",                  "Hide the occupants panel in current room." },
             { "show jid",              "Show jid in the occupants panel in current room." },
             { "hide jid",              "Hide jid in the occupants panel in current room." },
             { "default show|hide",     "Whether occupants are shown by default in new rooms." },
             { "default show|hide jid", "Whether occupants jids are shown by default in new rooms." },
-            { "size <percent>",        "Percentage of the screen taken by the occupants list in rooms (1-99)." })
+            { "size <percent>",        "Percentage of the screen taken by the occupants list in rooms (1-99)." },
+            { "indent <indent>",       "Indent contact line by <indent> spaces (0 to 10)." },
+            { "header char <char>",    "Prefix occupants headers with specified character." },
+            { "header char none",      "Remove occupants header character prefix." },
+            { "wrap on|off",           "Enable or disable line wrapping in occupants panel." })
         CMD_NOEXAMPLES
     },
 
@@ -1027,7 +1040,7 @@ static struct cmd_t command_defs[] =
         CMD_ARGS(
             { "<url>", "The url to make tiny." })
         CMD_EXAMPLES(
-            "Example: /tiny http://www.profanity.im")
+            "Example: /tiny https://profanity-im.github.io/")
     },
 
     { "/who",
@@ -1266,8 +1279,8 @@ static struct cmd_t command_defs[] =
         CMD_TAGS(
             CMD_TAG_UI)
         CMD_SYN(
-            "/time console|chat|muc|config|private|xml set <format>",
-            "/time console|chat|muc|config|private|xml off",
+            "/time all|console|chat|muc|config|private|xml set <format>",
+            "/time all|console|chat|muc|config|private|xml off",
             "/time statusbar set <format>",
             "/time statusbar off",
             "/time lastactivity set <format>")
@@ -1292,13 +1305,16 @@ static struct cmd_t command_defs[] =
             { "xml off",                   "Do not show time in XML console window." },
             { "statusbar set <format>",    "Change time format in statusbar." },
             { "statusbar off",             "Do not show time in status bar." },
-            { "lastactivity set <format>", "Change time format for last activity." })
+            { "lastactivity set <format>", "Change time format for last activity." },
+            { "all set <format>",          "Set time for: console, chat, muc, config, private and xml windows." },
+            { "all off",                   "Do not show time for: console, chat, muc, config, private and xml windows." })
         CMD_EXAMPLES(
             "/time console set %H:%M:%S",
             "/time chat set \"%d-%m-%y %H:%M:%S\"",
             "/time xml off",
             "/time statusbar set %H:%M",
-            "/time lastactivity set \"%d-%m-%y %H:%M:%S\"")
+            "/time lastactivity set \"%d-%m-%y %H:%M:%S\"",
+            "/time all set \"%d-%m-%y %H:%M:%S\"")
     },
 
     { "/inpblock",
@@ -2134,7 +2150,7 @@ static struct cmd_t command_defs[] =
         CMD_MAINFUNC(cmd_prefs)
         CMD_NOTAGS
         CMD_SYN(
-            "/prefs [ui|desktop|chat|log|conn|presence|otr|pgp]")
+            "/prefs [ui|desktop|chat|log|conn|presence|otr|pgp|omemo]")
         CMD_DESC(
             "Show preferences for different areas of functionality. "
             "Passing no arguments shows all preferences.")
@@ -2146,7 +2162,8 @@ static struct cmd_t command_defs[] =
             { "conn",     "Connection handling preferences." },
             { "presence", "Chat presence preferences." },
             { "otr",      "Off The Record preferences." },
-            { "pgp",      "OpenPGP preferences." })
+            { "pgp",      "OpenPGP preferences." },
+            { "omemo",    "OMEMO preferences." })
         CMD_NOEXAMPLES
     },
 
@@ -2328,7 +2345,82 @@ static struct cmd_t command_defs[] =
         CMD_EXAMPLES(
             "/cmd list",
             "/cmd exec ping")
-    }
+    },
+
+    { "/omemo",
+        parse_args, 1, 3, NULL,
+        CMD_SUBFUNCS(
+            { "gen", cmd_omemo_gen },
+            { "log", cmd_omemo_log },
+            { "start", cmd_omemo_start },
+            { "end", cmd_omemo_end },
+            { "trust", cmd_omemo_trust },
+            { "untrust", cmd_omemo_untrust },
+            { "fingerprint", cmd_omemo_fingerprint },
+            { "char", cmd_omemo_char },
+            { "policy", cmd_omemo_policy },
+            { "clear_device_list", cmd_omemo_clear_device_list })
+        CMD_NOMAINFUNC
+        CMD_TAGS(
+            CMD_TAG_CHAT,
+            CMD_TAG_UI)
+        CMD_SYN(
+            "/omemo gen",
+            "/omemo log on|off|redact",
+            "/omemo start [<contact>]",
+            "/omemo trust [<contact>] <fingerprint>",
+            "/omemo end",
+            "/omemo fingerprint [<contact>]",
+            "/omemo char <char>",
+            "/omemo policy manual|automatic|always",
+            "/omemo clear_device_list")
+        CMD_DESC(
+            "OMEMO commands to manage keys, and perform encryption during chat sessions.")
+        CMD_ARGS(
+            { "gen",                     "Generate OMEMO crytographic materials for current account." },
+            { "start [<contact>]",       "Start an OMEMO session with contact, or current recipient if omitted." },
+            { "end",                     "End the current OMEMO session." },
+            { "log on|off",              "Enable or disable plaintext logging of OMEMO encrypted messages." },
+            { "log redact",              "Log OMEMO encrypted messages, but replace the contents with [redacted]. This is the default." },
+            { "fingerprint [<contact>]", "Show contact fingerprints, or current recipient if omitted." },
+            { "char <char>",             "Set the character to be displayed next to OMEMO encrypted messages." },
+            { "policy manual",           "Set the global OMEMO policy to manual, OMEMO sessions must be started manually." },
+            { "policy automatic",        "Set the global OMEMO policy to opportunistic, an OMEMO session will be attempted upon starting a conversation." },
+            { "policy always",           "Set the global OMEMO policy to always, an error will be displayed if an OMEMO session cannot be initiated upon starting a conversation." },
+            { "clear_device_list",       "Clear your own device list on server side. Each client will reannounce itself when connected back."})
+        CMD_EXAMPLES(
+            "/omemo gen",
+            "/omemo start buddy@buddychat.org",
+            "/omemo trust c4f9c875-144d7a3b-0c4a05b6-ca3be51a-a037f329-0bd3ae62-07f99719-55559d2a",
+            "/omemo untrust buddy@buddychat.org c4f9c875-144d7a3b-0c4a05b6-ca3be51a-a037f329-0bd3ae62-07f99719-55559d2a",
+            "/omemo char *")
+    },
+
+    { "/save",
+        parse_args, 0, 0, NULL,
+        CMD_NOSUBFUNCS
+        CMD_MAINFUNC(cmd_save)
+        CMD_NOTAGS
+        CMD_SYN(
+            "/save")
+        CMD_DESC(
+            "Save preferences to configuration file.")
+        CMD_NOARGS
+        CMD_NOEXAMPLES
+    },
+
+    { "/reload",
+        parse_args, 0, 0, NULL,
+        CMD_NOSUBFUNCS
+        CMD_MAINFUNC(cmd_reload)
+        CMD_NOTAGS
+        CMD_SYN(
+            "/reload")
+        CMD_DESC(
+            "Reload preferences from configuration file.")
+        CMD_NOARGS
+        CMD_NOEXAMPLES
+    },
 };
 
 static GHashTable *search_index;
@@ -2363,7 +2455,6 @@ _cmd_index(Command *cmd) {
     g_string_free(index_source, TRUE);
 
     GString *index = g_string_new("");
-    i = 0;
     for (i = 0; i < g_strv_length(tokens); i++) {
         index = g_string_append(index, tokens[i]);
         index = g_string_append(index, " ");
diff --git a/src/command/cmd_defs.h b/src/command/cmd_defs.h
index e6ce1053..fb3a12c4 100644
--- a/src/command/cmd_defs.h
+++ b/src/command/cmd_defs.h
@@ -2,6 +2,7 @@
  * cmd_defs.h
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 82d8dccc..afec87fe 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -2,6 +2,7 @@
  * cmd_funcs.c
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -85,6 +86,11 @@
 #include "pgp/gpg.h"
 #endif
 
+#ifdef HAVE_OMEMO
+#include "omemo/omemo.h"
+#include "xmpp/omemo.h"
+#endif
+
 #ifdef HAVE_GTK
 #include "ui/tray.h"
 #endif
@@ -387,6 +393,7 @@ cmd_connect(ProfWin *window, const char *const command, gchar **args)
     }
 
     char *jid;
+    user = strdup(user);
     g_free(def);
 
     // connect with account
@@ -413,8 +420,8 @@ cmd_connect(ProfWin *window, const char *const command, gchar **args)
                 account->password = NULL;
             } else {
                 cons_show("Error evaluating password, see logs for details.");
-                g_free(user);
                 account_free(account);
+                free(user);
                 return TRUE;
             }
 
@@ -444,6 +451,7 @@ cmd_connect(ProfWin *window, const char *const command, gchar **args)
 
     options_destroy(options);
     free(jid);
+    free(user);
 
     return TRUE;
 }
@@ -1673,6 +1681,10 @@ cmd_prefs(ProfWin *window, const char *const command, gchar **args)
         cons_show("");
         cons_show_pgp_prefs();
         cons_show("");
+    } else if (strcmp(args[0], "omemo") == 0) {
+        cons_show("");
+        cons_show_omemo_prefs();
+        cons_show("");
     } else {
         cons_bad_cmd_usage(command);
     }
@@ -2140,17 +2152,67 @@ cmd_msg(ProfWin *window, const char *const command, gchar **args)
         }
         ui_focus_win((ProfWin*)chatwin);
 
+#ifdef HAVE_OMEMO
+#ifndef HAVE_LIBOTR
+        if (omemo_automatic_start(barejid)) {
+            omemo_start_session(barejid);
+            chatwin->is_omemo = TRUE;
+        }
+
+        if (msg) {
+            cl_ev_send_msg(chatwin, msg, NULL);
+        }
+
+        return TRUE;
+#endif
+#endif
+
+#ifdef HAVE_OMEMO
+#ifdef HAVE_LIBOTR
+        if (omemo_automatic_start(barejid) && otr_is_secure(barejid)) {
+            win_println(window, THEME_DEFAULT, '!', "Chat could be either OMEMO or OTR encrypted. Use '/omemo start %s' or '/otr start %s' to start a session.", usr, usr);
+            return TRUE;
+        } else if (omemo_automatic_start(barejid)) {
+            omemo_start_session(barejid);
+            chatwin->is_omemo = TRUE;
+        }
+
         if (msg) {
             cl_ev_send_msg(chatwin, msg, NULL);
         } else {
+            if (otr_is_secure(barejid)) {
+                chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
+            }
+        }
+
+        return TRUE;
+#endif
+#endif
+
+#ifndef HAVE_OMEMO
 #ifdef HAVE_LIBOTR
+        if (msg) {
+            cl_ev_send_msg(chatwin, msg, NULL);
+        } else {
             if (otr_is_secure(barejid)) {
                 chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
             }
+        }
+
+        return TRUE;
+#endif
 #endif
+
+#ifndef HAVE_OMEMO
+#ifndef HAVE_LIBOTR
+        if (msg) {
+            cl_ev_send_msg(chatwin, msg, NULL);
         }
 
         return TRUE;
+#endif
+#endif
+
     }
 }
 
@@ -3154,6 +3216,16 @@ cmd_resource(ProfWin *window, const char *const command, gchar **args)
     }
 }
 
+static void
+_cmd_status_show_status(char* usr)
+{
+    char *usr_jid = roster_barejid_from_name(usr);
+    if (usr_jid == NULL) {
+        usr_jid = usr;
+    }
+    cons_show_status(usr_jid);
+}
+
 gboolean
 cmd_status(ProfWin *window, const char *const command, gchar **args)
 {
@@ -3184,7 +3256,7 @@ cmd_status(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_CHAT:
             if (usr) {
-                win_println(window, THEME_DEFAULT, '-', "No parameter required when in chat.");
+                _cmd_status_show_status(usr);
             } else {
                 ProfChatWin *chatwin = (ProfChatWin*)window;
                 assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
@@ -3198,7 +3270,7 @@ cmd_status(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_PRIVATE:
             if (usr) {
-                win_println(window, THEME_DEFAULT, '-', "No parameter required when in chat.");
+                _cmd_status_show_status(usr);
             } else {
                 ProfPrivateWin *privatewin = (ProfPrivateWin*)window;
                 assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
@@ -3214,11 +3286,7 @@ cmd_status(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_CONSOLE:
             if (usr) {
-                char *usr_jid = roster_barejid_from_name(usr);
-                if (usr_jid == NULL) {
-                    usr_jid = usr;
-                }
-                cons_show_status(usr_jid);
+                _cmd_status_show_status(usr);
             } else {
                 cons_bad_cmd_usage(command);
             }
@@ -3230,6 +3298,21 @@ cmd_status(ProfWin *window, const char *const command, gchar **args)
     return TRUE;
 }
 
+static void
+_cmd_info_show_contact(char *usr)
+{
+    char *usr_jid = roster_barejid_from_name(usr);
+    if (usr_jid == NULL) {
+        usr_jid = usr;
+    }
+    PContact pcontact = roster_get_contact(usr_jid);
+    if (pcontact) {
+        cons_show_info(pcontact);
+    } else {
+        cons_show("No such contact \"%s\" in roster.", usr);
+    }
+}
+
 gboolean
 cmd_info(ProfWin *window, const char *const command, gchar **args)
 {
@@ -3264,7 +3347,7 @@ cmd_info(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_CHAT:
             if (usr) {
-                win_println(window, THEME_DEFAULT, '-', "No parameter required when in chat.");
+                _cmd_info_show_contact(usr);
             } else {
                 ProfChatWin *chatwin = (ProfChatWin*)window;
                 assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
@@ -3278,7 +3361,7 @@ cmd_info(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_PRIVATE:
             if (usr) {
-                win_println(window, THEME_DEFAULT, '-', "No parameter required when in chat.");
+                _cmd_info_show_contact(usr);
             } else {
                 ProfPrivateWin *privatewin = (ProfPrivateWin*)window;
                 assert(privatewin->memcheck == PROFPRIVATEWIN_MEMCHECK);
@@ -3294,16 +3377,7 @@ cmd_info(ProfWin *window, const char *const command, gchar **args)
             break;
         case WIN_CONSOLE:
             if (usr) {
-                char *usr_jid = roster_barejid_from_name(usr);
-                if (usr_jid == NULL) {
-                    usr_jid = usr;
-                }
-                PContact pcontact = roster_get_contact(usr_jid);
-                if (pcontact) {
-                    cons_show_info(pcontact);
-                } else {
-                    cons_show("No such contact \"%s\" in roster.", usr);
-                }
+                _cmd_info_show_contact(usr);
             } else {
                 cons_bad_cmd_usage(command);
             }
@@ -4247,12 +4321,6 @@ cmd_room(ProfWin *window, const char *const command, gchar **args)
 
     ProfMucWin *mucwin = (ProfMucWin*)window;
     assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
-    int num = wins_get_num(window);
-
-    int ui_index = num;
-    if (ui_index == 10) {
-        ui_index = 0;
-    }
 
     if (g_strcmp0(args[0], "accept") == 0) {
         gboolean requires_config = muc_requires_config(mucwin->roomjid);
@@ -4310,6 +4378,55 @@ cmd_occupants(ProfWin *window, const char *const command, gchar **args)
         }
     }
 
+    if (g_strcmp0(args[0], "indent") == 0) {
+        if (!args[1]) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        } else {
+            int intval = 0;
+            char *err_msg = NULL;
+            gboolean res = strtoi_range(args[1], &intval, 0, 10, &err_msg);
+            if (res) {
+                prefs_set_occupants_indent(intval);
+                cons_show("Occupants indent set to: %d", intval);
+
+                occupantswin_occupants_all();
+            } else {
+                cons_show(err_msg);
+                free(err_msg);
+            }
+            return TRUE;
+        }
+    }
+
+    if (g_strcmp0(args[0], "wrap") == 0) {
+        if (!args[1]) {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        } else {
+            _cmd_set_boolean_preference(args[1], command, "Occupants panel line wrap", PREF_OCCUPANTS_WRAP);
+             occupantswin_occupants_all();
+            return TRUE;
+        }
+    }
+
+    if (g_strcmp0(args[0], "char") == 0) {
+        if (!args[1]) {
+            cons_bad_cmd_usage(command);
+        } else if (g_strcmp0(args[1], "none") == 0) {
+            prefs_clear_occupants_char();
+            cons_show("Occupants char removed.");
+
+            occupantswin_occupants_all();
+        } else {
+            prefs_set_occupants_char(args[1][0]);
+            cons_show("Occupants char set to %c.", args[1][0]);
+
+            occupantswin_occupants_all();
+        }
+        return TRUE;
+    }
+
     if (g_strcmp0(args[0], "default") == 0) {
         if (g_strcmp0(args[1], "show") == 0) {
             if (g_strcmp0(args[2], "jid") == 0) {
@@ -4335,6 +4452,28 @@ cmd_occupants(ProfWin *window, const char *const command, gchar **args)
         }
     }
 
+    // header settings
+    if (g_strcmp0(args[0], "header") == 0) {
+        if (g_strcmp0(args[1], "char") == 0) {
+            if (!args[2]) {
+                cons_bad_cmd_usage(command);
+            } else if (g_strcmp0(args[2], "none") == 0) {
+                prefs_clear_occupants_header_char();
+                cons_show("Occupants header char removed.");
+
+                occupantswin_occupants_all();
+            } else {
+                prefs_set_occupants_header_char(args[2][0]);
+                cons_show("Occupants header char set to %c.", args[2][0]);
+
+                occupantswin_occupants_all();
+            }
+        } else {
+            cons_bad_cmd_usage(command);
+        }
+        return TRUE;
+    }
+
     jabber_conn_status_t conn_status = connection_get_status();
     if (conn_status != JABBER_CONNECTED) {
         cons_show("You are not currently connected.");
@@ -5228,6 +5367,44 @@ cmd_time(ProfWin *window, const char *const command, gchar **args)
             cons_bad_cmd_usage(command);
             return TRUE;
         }
+    } else if (g_strcmp0(args[0], "all") == 0) {
+        if (args[1] == NULL) {
+            cons_time_setting();
+            return TRUE;
+        } else if (g_strcmp0(args[1], "set") == 0 && args[2] != NULL) {
+            prefs_set_string(PREF_TIME_CONSOLE, args[2]);
+            cons_show("Console time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_CHAT, args[2]);
+            cons_show("Chat time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_MUC, args[2]);
+            cons_show("MUC time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_CONFIG, args[2]);
+            cons_show("config time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_PRIVATE, args[2]);
+            cons_show("Private chat time format set to '%s'.", args[2]);
+            prefs_set_string(PREF_TIME_XMLCONSOLE, args[2]);
+            cons_show("XML Console time format set to '%s'.", args[2]);
+            wins_resize_all();
+            return TRUE;
+        } else if (g_strcmp0(args[1], "off") == 0) {
+            prefs_set_string(PREF_TIME_CONSOLE, "off");
+            cons_show("Console time display disabled.");
+            prefs_set_string(PREF_TIME_CHAT, "off");
+            cons_show("Chat time display disabled.");
+            prefs_set_string(PREF_TIME_MUC, "off");
+            cons_show("MUC time display disabled.");
+            prefs_set_string(PREF_TIME_CONFIG, "off");
+            cons_show("config time display disabled.");
+            prefs_set_string(PREF_TIME_PRIVATE, "off");
+            cons_show("config time display disabled.");
+            prefs_set_string(PREF_TIME_XMLCONSOLE, "off");
+            cons_show("XML Console time display disabled.");
+            ui_redraw();
+            return TRUE;
+        } else {
+            cons_bad_cmd_usage(command);
+            return TRUE;
+        }
     } else {
         cons_bad_cmd_usage(command);
         return TRUE;
@@ -5237,6 +5414,10 @@ cmd_time(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_states(ProfWin *window, const char *const command, gchar **args)
 {
+    if (args[0] == NULL) {
+        return FALSE;
+    }
+
     _cmd_set_boolean_preference(args[0], command, "Sending chat states", PREF_STATES);
 
     // if disabled, disable outtype and gone
@@ -5270,6 +5451,10 @@ cmd_wintitle(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_outtype(ProfWin *window, const char *const command, gchar **args)
 {
+    if (args[0] == NULL) {
+        return FALSE;
+    }
+
     _cmd_set_boolean_preference(args[0], command, "Sending typing notifications", PREF_OUTTYPE);
 
     // if enabled, enable states
@@ -6424,17 +6609,29 @@ cmd_autoconnect(ProfWin *window, const char *const command, gchar **args)
         prefs_set_string(PREF_CONNECT_ACCOUNT, NULL);
         cons_show("Autoconnect account disabled.");
     } else if (strcmp(args[0], "set") == 0) {
-        prefs_set_string(PREF_CONNECT_ACCOUNT, args[1]);
-        cons_show("Autoconnect account set to: %s.", args[1]);
+        if (args[1] == NULL || strlen(args[1]) == 0) {
+            cons_bad_cmd_usage(command);
+        } else {
+            if (accounts_account_exists(args[1])) {
+                prefs_set_string(PREF_CONNECT_ACCOUNT, args[1]);
+                cons_show("Autoconnect account set to: %s.", args[1]);
+            } else {
+                cons_show_error("Account '%s' does not exist.", args[1]);
+            }
+        }
     } else {
         cons_bad_cmd_usage(command);
     }
-    return true;
+    return TRUE;
 }
 
 gboolean
 cmd_chlog(ProfWin *window, const char *const command, gchar **args)
 {
+    if (args[0] == NULL) {
+        return FALSE;
+    }
+
     _cmd_set_boolean_preference(args[0], command, "Chat logging", PREF_CHLOG);
 
     // if set to off, disable history
@@ -6456,6 +6653,10 @@ cmd_grlog(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_history(ProfWin *window, const char *const command, gchar **args)
 {
+    if (args[0] == NULL) {
+        return FALSE;
+    }
+
     _cmd_set_boolean_preference(args[0], command, "Chat history", PREF_HISTORY);
 
     // if set to on, set chlog
@@ -6469,6 +6670,10 @@ cmd_history(ProfWin *window, const char *const command, gchar **args)
 gboolean
 cmd_carbons(ProfWin *window, const char *const command, gchar **args)
 {
+    if (args[0] == NULL) {
+        return FALSE;
+    }
+
     _cmd_set_boolean_preference(args[0], command, "Message carbons preference", PREF_CARBONS);
 
     jabber_conn_status_t conn_status = connection_get_status();
@@ -6579,11 +6784,13 @@ cmd_plugins_sourcepath(ProfWin *window, const char *const command, gchar **args)
 
         if (!is_dir(path)) {
             cons_show("Plugins sourcepath must be a directory.");
+            free(path);
             return TRUE;
         }
 
         cons_show("Setting plugins sourcepath: %s", path);
         prefs_set_string(PREF_PLUGINS_SOURCEPATH, path);
+        free(path);
         return TRUE;
     }
 
@@ -6661,6 +6868,8 @@ cmd_plugins_install(ProfWin *window, const char *const command, gchar **args)
     } else {
         cons_show("Argument must be a file or directory.");
     }
+
+    free(path);
     return TRUE;
 }
 
@@ -6765,12 +6974,14 @@ cmd_plugins_load(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    gboolean res = plugins_load(args[1]);
+    GString* error_message = g_string_new(NULL);
+    gboolean res = plugins_load(args[1], error_message);
     if (res) {
         cons_show("Loaded plugin: %s", args[1]);
     } else {
-        cons_show("Failed to load plugin: %s", args[1]);
+        cons_show("Failed to load plugin: %s. %s", args[1], error_message->str);
     }
+    g_string_free(error_message, TRUE);
 
     return TRUE;
 }
@@ -6807,12 +7018,14 @@ cmd_plugins_reload(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
-    gboolean res = plugins_reload(args[1]);
+    GString* error_message = g_string_new(NULL);
+    gboolean res = plugins_reload(args[1], error_message);
     if (res) {
         cons_show("Reloaded plugin: %s", args[1]);
     } else {
-        cons_show("Failed to reload plugin: %s", args[1]);
+        cons_show("Failed to reload plugin: %s, %s", args[1], error_message);
     }
+    g_string_free(error_message, TRUE);
 
     return TRUE;
 }
@@ -7295,6 +7508,11 @@ cmd_otr_start(ProfWin *window, const char *const command, gchar **args)
             return TRUE;
         }
 
+        if (chatwin->is_omemo) {
+            win_println(window, THEME_DEFAULT, '!', "You must disable OMEMO before starting an OTR session.");
+            return TRUE;
+        }
+
         if (chatwin->is_otr) {
             win_println(window, THEME_DEFAULT, '!', "You are already in an OTR session.");
             return TRUE;
@@ -7624,6 +7842,11 @@ cmd_command_exec(ProfWin *window, const char *const command, gchar **args)
         return TRUE;
     }
 
+    if (args[1] == NULL) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
     char *jid = args[2];
     if (jid == NULL) {
         switch (window->type) {
@@ -7863,3 +8086,470 @@ _cmd_set_boolean_preference(gchar *arg, const char *const command,
     g_string_free(enabled, TRUE);
     g_string_free(disabled, TRUE);
 }
+
+gboolean
+cmd_omemo_gen(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to initialize OMEMO.");
+        return TRUE;
+    }
+
+    if (omemo_loaded()) {
+        cons_show("OMEMO crytographic materials have already been generated.");
+        return TRUE;
+    }
+
+    cons_show("Generating OMEMO crytographic materials, it may take a while...");
+    ui_update();
+    ProfAccount *account = accounts_get_account(session_get_account_name());
+    omemo_generate_crypto_materials(account);
+    cons_show("OMEMO crytographic materials generated.");
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_start(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to load OMEMO information.");
+        return TRUE;
+    }
+
+    if (!omemo_loaded()) {
+        win_println(window, THEME_DEFAULT, '!', "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
+        return TRUE;
+    }
+
+    ProfChatWin *chatwin = NULL;
+
+    // recipient supplied
+    if (args[1]) {
+        char *contact = args[1];
+        char *barejid = roster_barejid_from_name(contact);
+        if (barejid == NULL) {
+            barejid = contact;
+        }
+
+        chatwin = wins_get_chat(barejid);
+        if (!chatwin) {
+            chatwin = chatwin_new(barejid);
+        }
+        ui_focus_win((ProfWin*)chatwin);
+    } else {
+      if (window->type == WIN_CHAT) {
+        chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+      }
+    }
+
+    if (chatwin) {
+        if (chatwin->pgp_send) {
+            win_println((ProfWin*)chatwin, THEME_DEFAULT, '!', "You must disable PGP encryption before starting an OMEMO session.");
+            return TRUE;
+        }
+
+        if (chatwin->is_otr) {
+            win_println((ProfWin*)chatwin, THEME_DEFAULT, '!', "You must disable OTR encryption before starting an OMEMO session.");
+            return TRUE;
+        }
+
+        if (chatwin->is_omemo) {
+            win_println((ProfWin*)chatwin, THEME_DEFAULT, '!', "You are already in an OMEMO session.");
+            return TRUE;
+        }
+
+        accounts_add_omemo_state(session_get_account_name(), chatwin->barejid, TRUE);
+        omemo_start_session(chatwin->barejid);
+        chatwin->is_omemo = TRUE;
+    } else if (window->type == WIN_MUC) {
+        ProfMucWin *mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+
+        if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
+            accounts_add_omemo_state(session_get_account_name(), mucwin->roomjid, TRUE);
+            omemo_start_muc_sessions(mucwin->roomjid);
+            mucwin->is_omemo = TRUE;
+        } else {
+            win_println(window, THEME_DEFAULT, '!', "MUC must be non-anonymous (i.e. be configured to present real jid to anyone) in order to support OMEMO.");
+        }
+    } else {
+        win_println(window, THEME_DEFAULT, '-', "You must be in a regular chat window to start an OMEMO session.");
+    }
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_char(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (args[1] == NULL) {
+        cons_bad_cmd_usage(command);
+    } else if (strlen(args[1]) != 1) {
+        cons_bad_cmd_usage(command);
+    } else {
+        prefs_set_omemo_char(args[1][0]);
+        cons_show("OMEMO char set to %c.", args[1][0]);
+    }
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_log(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    char *choice = args[1];
+    if (g_strcmp0(choice, "on") == 0) {
+        prefs_set_string(PREF_OMEMO_LOG, "on");
+        cons_show("OMEMO messages will be logged as plaintext.");
+        if (!prefs_get_boolean(PREF_CHLOG)) {
+            cons_show("Chat logging is currently disabled, use '/chlog on' to enable.");
+        }
+    } else if (g_strcmp0(choice, "off") == 0) {
+        prefs_set_string(PREF_OMEMO_LOG, "off");
+        cons_show("OMEMO message logging disabled.");
+    } else if (g_strcmp0(choice, "redact") == 0) {
+        prefs_set_string(PREF_OMEMO_LOG, "redact");
+        cons_show("OMEMO messages will be logged as '[redacted]'.");
+        if (!prefs_get_boolean(PREF_CHLOG)) {
+            cons_show("Chat logging is currently disabled, use '/chlog on' to enable.");
+        }
+    } else {
+        cons_bad_cmd_usage(command);
+    }
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_end(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to load OMEMO information.");
+        return TRUE;
+    }
+
+    if (window->type == WIN_CHAT) {
+        ProfChatWin *chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+
+        if (!chatwin->is_omemo) {
+            win_println(window, THEME_DEFAULT, '!', "You are not currently in an OMEMO session.");
+            return TRUE;
+        }
+
+        chatwin->is_omemo = FALSE;
+        accounts_add_omemo_state(session_get_account_name(), chatwin->barejid, FALSE);
+    } else if (window->type == WIN_MUC) {
+        ProfMucWin *mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+
+        if (!mucwin->is_omemo) {
+            win_println(window, THEME_DEFAULT, '!', "You are not currently in an OMEMO session.");
+            return TRUE;
+        }
+
+        mucwin->is_omemo = FALSE;
+        accounts_add_omemo_state(session_get_account_name(), mucwin->roomjid, FALSE);
+    } else {
+        win_println(window, THEME_DEFAULT, '-', "You must be in a regular chat window to start an OMEMO session.");
+        return TRUE;
+    }
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_fingerprint(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to load OMEMO information.");
+        return TRUE;
+    }
+
+    if (!omemo_loaded()) {
+        win_println(window, THEME_DEFAULT, '!', "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
+        return TRUE;
+    }
+
+    Jid *jid;
+    if (!args[1]) {
+        if (window->type == WIN_CONSOLE) {
+            char *fingerprint = omemo_own_fingerprint(TRUE);
+            cons_show("Your OMEMO fingerprint: %s", fingerprint);
+            free(fingerprint);
+            jid = jid_create(connection_get_fulljid());
+        } else if (window->type == WIN_CHAT) {
+            ProfChatWin *chatwin = (ProfChatWin*)window;
+            jid = jid_create(chatwin->barejid);
+        } else {
+            win_println(window, THEME_DEFAULT, '-', "You must be in a regular chat window to print fingerprint without providing the contact.");
+            return TRUE;
+        }
+    } else {
+        char *barejid = roster_barejid_from_name(args[1]);
+        if (barejid) {
+            jid = jid_create(barejid);
+        } else {
+            jid = jid_create(args[1]);
+            if (!jid) {
+                cons_show("%s is not a valid jid", args[1]);
+                return TRUE;
+            }
+        }
+    }
+
+    GList *fingerprints = omemo_known_device_identities(jid->barejid);
+    GList *fingerprint;
+
+    if (!fingerprints) {
+        win_println(window, THEME_DEFAULT, '-', "There is no known fingerprints for %s", jid->barejid);
+        return TRUE;
+    }
+
+    for (fingerprint = fingerprints; fingerprint != NULL; fingerprint = fingerprint->next) {
+        char *formatted_fingerprint = omemo_format_fingerprint(fingerprint->data);
+        gboolean trusted = omemo_is_trusted_identity(jid->barejid, fingerprint->data);
+
+        win_println(window, THEME_DEFAULT, '-', "%s's OMEMO fingerprint: %s%s", jid->barejid, formatted_fingerprint, trusted ? " (trusted)" : "");
+
+        free(formatted_fingerprint);
+    }
+
+    g_list_free(fingerprints);
+
+    win_println(window, THEME_DEFAULT, '-', "You can trust it with '/omemo trust <fingerprint>'");
+    win_println(window, THEME_DEFAULT, '-', "You can untrust it with '/omemo untrust <fingerprint>'");
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_trust(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to load OMEMO information.");
+        return TRUE;
+    }
+
+    if (!args[1]) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (!omemo_loaded()) {
+        win_println(window, THEME_DEFAULT, '!', "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
+        return TRUE;
+    }
+
+    char *fingerprint;
+    char *barejid;
+
+    /* Contact not provided */
+    if (!args[2]) {
+        fingerprint = args[1];
+
+        if (window->type != WIN_CHAT) {
+            win_println(window, THEME_DEFAULT, '-', "You must be in a regular chat window to trust a device without providing the contact.");
+            return TRUE;
+        }
+
+        ProfChatWin *chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+        barejid = chatwin->barejid;
+    } else {
+        fingerprint = args[2];
+        char *contact = args[1];
+        barejid = roster_barejid_from_name(contact);
+        if (barejid == NULL) {
+            barejid = contact;
+        }
+    }
+
+    omemo_trust(barejid, fingerprint);
+
+    char *unformatted_fingerprint = malloc(strlen(fingerprint));
+    int i;
+    int j;
+    for (i = 0, j = 0; fingerprint[i] != '\0'; i++) {
+        if (!g_ascii_isxdigit(fingerprint[i])) {
+            continue;
+        }
+        unformatted_fingerprint[j++] = fingerprint[i];
+    }
+
+    unformatted_fingerprint[j] = '\0';
+    gboolean trusted = omemo_is_trusted_identity(barejid, unformatted_fingerprint);
+
+    win_println(window, THEME_DEFAULT, '-', "%s's OMEMO fingerprint: %s%s", barejid, fingerprint, trusted ? " (trusted)" : "");
+
+    free(unformatted_fingerprint);
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_untrust(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to load OMEMO information.");
+        return TRUE;
+    }
+
+    if (!args[1]) {
+        cons_bad_cmd_usage(command);
+        return TRUE;
+    }
+
+    if (!omemo_loaded()) {
+        win_println(window, THEME_DEFAULT, '!', "You have not generated or loaded a cryptographic materials, use '/omemo gen'");
+        return TRUE;
+    }
+
+    char *fingerprint;
+    char *barejid;
+
+    /* Contact not provided */
+    if (!args[2]) {
+        fingerprint = args[1];
+
+        if (window->type != WIN_CHAT) {
+            win_println(window, THEME_DEFAULT, '-', "You must be in a regular chat window to trust a device without providing the contact.");
+            return TRUE;
+        }
+
+        ProfChatWin *chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+        barejid = chatwin->barejid;
+    } else {
+        fingerprint = args[2];
+        char *contact = args[1];
+        barejid = roster_barejid_from_name(contact);
+        if (barejid == NULL) {
+            barejid = contact;
+        }
+    }
+
+    omemo_untrust(barejid, fingerprint);
+
+    char *unformatted_fingerprint = malloc(strlen(fingerprint));
+    int i;
+    int j;
+    for (i = 0, j = 0; fingerprint[i] != '\0'; i++) {
+        if (!g_ascii_isxdigit(fingerprint[i])) {
+            continue;
+        }
+        unformatted_fingerprint[j++] = fingerprint[i];
+    }
+
+    unformatted_fingerprint[j] = '\0';
+    gboolean trusted = omemo_is_trusted_identity(barejid, unformatted_fingerprint);
+
+    win_println(window, THEME_DEFAULT, '-', "%s's OMEMO fingerprint: %s%s", barejid, fingerprint, trusted ? " (trusted)" : "");
+
+    free(unformatted_fingerprint);
+
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_clear_device_list(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (connection_get_status() != JABBER_CONNECTED) {
+        cons_show("You must be connected with an account to initialize OMEMO.");
+        return TRUE;
+    }
+
+    omemo_devicelist_publish(NULL);
+    cons_show("Cleared OMEMO device list");
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_omemo_policy(ProfWin *window, const char *const command, gchar **args)
+{
+#ifdef HAVE_OMEMO
+    if (args[1] == NULL) {
+        char *policy = prefs_get_string(PREF_OMEMO_POLICY);
+        cons_show("OMEMO policy is now set to: %s", policy);
+        prefs_free_string(policy);
+        return TRUE;
+    }
+
+    char *choice = args[1];
+    if ((g_strcmp0(choice, "manual") != 0) &&
+            (g_strcmp0(choice, "automatic") != 0) &&
+            (g_strcmp0(choice, "always") != 0)) {
+        cons_show("OMEMO policy can be set to: manual, automatic or always.");
+        return TRUE;
+    }
+
+    prefs_set_string(PREF_OMEMO_POLICY, choice);
+    cons_show("OMEMO policy is now set to: %s", choice);
+    return TRUE;
+#else
+    cons_show("This version of Profanity has not been built with OMEMO support enabled");
+    return TRUE;
+#endif
+}
+
+gboolean
+cmd_save(ProfWin *window, const char *const command, gchar **args)
+{
+    log_info("Saving preferences to configuration file");
+    cons_show("Saving preferences.");
+    prefs_save();
+    return TRUE;
+}
+
+gboolean
+cmd_reload(ProfWin *window, const char *const command, gchar **args)
+{
+    log_info("Reloading preferences");
+    cons_show("Reloading preferences.");
+    prefs_reload();
+    return TRUE;
+}
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index 89166ba1..9102e1e1 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -2,6 +2,7 @@
  * cmd_funcs.h
  *
  * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
+ * Copyright (C) 2019 Michael Vetter <jubalh@iodoru.org>
  *
  * This file is part of Profanity.
  *
@@ -214,4 +215,17 @@ gboolean cmd_wins_swap(ProfWin *window, const char *const command, gchar **args)
 
 gboolean cmd_form_field(ProfWin *window, char *tag, gchar **args);
 
+gboolean cmd_omemo_gen(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_char(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_log(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_start(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_end(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_fingerprint(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_trust(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_untrust(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_policy(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_omemo_clear_device_list(ProfWin *window, const char *const command, gchar **args);
+
+gboolean cmd_save(ProfWin *window, const char *const command, gchar **args);
+gboolean cmd_reload(ProfWin *window, const char *const command, gchar **args);
 #endif