about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE3
-rw-r--r--SPONSORS.md2
-rw-r--r--src/command/cmd_ac.c114
-rw-r--r--src/command/cmd_defs.c23
-rw-r--r--src/command/cmd_funcs.c172
-rw-r--r--src/command/cmd_funcs.h1
-rw-r--r--src/config/files.h1
-rw-r--r--src/config/preferences.c16
-rw-r--r--src/config/preferences.h1
-rw-r--r--src/database.c13
-rw-r--r--src/log.c8
-rw-r--r--src/omemo/omemo.c105
-rw-r--r--src/omemo/store.c23
-rw-r--r--src/tools/aesgcm_download.c1
-rw-r--r--src/tools/http_upload.c2
-rw-r--r--src/ui/console.c83
-rw-r--r--src/ui/core.c32
-rw-r--r--src/ui/inputwin.c29
-rw-r--r--src/ui/mucwin.c22
-rw-r--r--src/ui/titlebar.c23
-rw-r--r--src/ui/ui.h6
-rw-r--r--src/ui/win_types.h2
-rw-r--r--src/ui/window.c36
-rw-r--r--src/ui/window_list.c121
-rw-r--r--src/ui/window_list.h2
-rw-r--r--src/xmpp/bookmark.c7
-rw-r--r--src/xmpp/iq.c13
-rw-r--r--src/xmpp/message.c31
-rw-r--r--src/xmpp/omemo.c39
-rw-r--r--src/xmpp/stanza.c51
-rw-r--r--src/xmpp/stanza.h6
-rw-r--r--src/xmpp/xmpp.h1
-rw-r--r--tests/unittests/omemo/stub_omemo.c5
-rw-r--r--tests/unittests/ui/stub_ui.c25
-rw-r--r--tests/unittests/xmpp/stub_xmpp.c6
35 files changed, 791 insertions, 234 deletions
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
index d580ae56..35ce44eb 100644
--- a/.github/ISSUE_TEMPLATE
+++ b/.github/ISSUE_TEMPLATE
@@ -1,5 +1,8 @@
 <!--- Provide a general summary of the issue in the Title above -->
 
+<!--- More than 50 issues open? Please don't file any new feature requests -->
+<!--- Help us reduce the work first :-) -->
+
 ## Expected Behavior
 <!--- If you're describing a bug, tell us what should happen -->
 <!--- If you're suggesting a change/improvement, tell us how it should work -->
diff --git a/SPONSORS.md b/SPONSORS.md
index 09354074..47cbad87 100644
--- a/SPONSORS.md
+++ b/SPONSORS.md
@@ -16,6 +16,8 @@ Thank you very much to all of our sponsors and donors (including those that want
 
 [Julian Huhn](https://github.com/huhndev)
 
+[Matteo Bini](https://github.com/matteobin)
+
 ## Services
 [William Wennerström](https://github.com/wstrm/) for sponsoring our use of [sourcehut CI](https://builds.sr.ht/~wstrm/profanity?)
 
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index a4d70598..6b46d079 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -127,6 +127,7 @@ static char* _software_autocomplete(ProfWin* window, const char* const input, gb
 static char* _url_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 static char* _executable_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 static char* _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous);
+static char* _intype_autocomplete(ProfWin* window, const char* const input, gboolean previous);
 
 static char* _script_autocomplete_func(const char* const prefix, gboolean previous, void* context);
 
@@ -267,6 +268,7 @@ static Autocomplete correction_ac;
 static Autocomplete avatar_ac;
 static Autocomplete url_ac;
 static Autocomplete executable_ac;
+static Autocomplete intype_ac;
 
 /*!
  * \brief Initialization of auto completion for commands.
@@ -469,6 +471,7 @@ cmd_ac_init(void)
 
     wins_ac = autocomplete_new();
     autocomplete_add(wins_ac, "unread");
+    autocomplete_add(wins_ac, "attention");
     autocomplete_add(wins_ac, "prune");
     autocomplete_add(wins_ac, "swap");
 
@@ -1053,6 +1056,10 @@ cmd_ac_init(void)
     autocomplete_add(executable_ac, "urlopen");
     autocomplete_add(executable_ac, "urlsave");
     autocomplete_add(executable_ac, "editor");
+
+    intype_ac = autocomplete_new();
+    autocomplete_add(intype_ac, "console");
+    autocomplete_add(intype_ac, "titlebar");
 }
 
 void
@@ -1368,6 +1375,7 @@ cmd_ac_reset(ProfWin* window)
     autocomplete_reset(avatar_ac);
     autocomplete_reset(url_ac);
     autocomplete_reset(executable_ac);
+    autocomplete_reset(intype_ac);
 
     autocomplete_reset(script_ac);
     if (script_show_ac) {
@@ -1531,6 +1539,7 @@ cmd_ac_uninit(void)
     autocomplete_free(avatar_ac);
     autocomplete_free(url_ac);
     autocomplete_free(executable_ac);
+    autocomplete_free(intype_ac);
 }
 
 static void
@@ -1589,12 +1598,13 @@ cmd_ac_complete_filepath(const char* const input, char* const startstr, gboolean
     free(inpcp);
     free(inpcp2);
 
-    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) {
+        struct dirent* dir;
+
         while ((dir = readdir(d)) != NULL) {
             if (strcmp(dir->d_name, ".") == 0) {
                 continue;
@@ -1659,7 +1669,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
     jabber_conn_status_t conn_status = connection_get_status();
 
     // autocomplete boolean settings
-    gchar* boolean_choices[] = { "/beep", "/intype", "/states", "/outtype", "/flash", "/splash",
+    gchar* boolean_choices[] = { "/beep", "/states", "/outtype", "/flash", "/splash",
                                  "/history", "/vercheck", "/privileges", "/wrap", "/carbons", "/os", "/slashguard", "/mam" };
 
     for (int i = 0; i < ARRAY_SIZE(boolean_choices); i++) {
@@ -1793,6 +1803,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
     g_hash_table_insert(ac_funcs, "/url", _url_autocomplete);
     g_hash_table_insert(ac_funcs, "/executable", _executable_autocomplete);
     g_hash_table_insert(ac_funcs, "/lastactivity", _lastactivity_autocomplete);
+    g_hash_table_insert(ac_funcs, "/intype", _intype_autocomplete);
 
     int len = strlen(input);
     char parsed[len + 1];
@@ -2195,6 +2206,10 @@ _bookmark_autocomplete(ProfWin* window, const char* const input, gboolean previo
     if (found) {
         return found;
     }
+    found = autocomplete_param_with_func(input, "/bookmark list", bookmark_find, previous, NULL);
+    if (found) {
+        return found;
+    }
 
     found = autocomplete_param_with_ac(input, "/bookmark", bookmark_ac, TRUE, previous);
     return found;
@@ -3138,9 +3153,6 @@ _affiliation_autocomplete(ProfWin* window, const char* const input, gboolean pre
     }
 
     result = autocomplete_param_with_ac(input, "/affiliation", affiliation_cmd_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
     return result;
 }
@@ -3188,9 +3200,6 @@ _role_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_ac(input, "/role", role_cmd_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
     return result;
 }
@@ -3201,11 +3210,8 @@ _wins_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     char* result = NULL;
 
     result = autocomplete_param_with_ac(input, "/wins", wins_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3229,9 +3235,6 @@ _tls_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_ac(input, "/tls", tls_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
     return result;
 }
@@ -3252,9 +3255,6 @@ _titlebar_autocomplete(ProfWin* window, const char* const input, gboolean previo
     }
 
     result = autocomplete_param_with_ac(input, "/titlebar", titlebar_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
     return result;
 }
@@ -3275,11 +3275,8 @@ _receipts_autocomplete(ProfWin* window, const char* const input, gboolean previo
     }
 
     result = autocomplete_param_with_ac(input, "/receipts", receipts_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3293,11 +3290,8 @@ _alias_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_ac(input, "/alias", alias_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3465,11 +3459,7 @@ _help_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_ac(input, "/help", help_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3508,11 +3498,8 @@ _join_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     g_strfreev(args);
 
     found = autocomplete_param_with_func(input, "/join", bookmark_find, previous, NULL);
-    if (found) {
-        return found;
-    }
 
-    return NULL;
+    return found;
 }
 
 static char*
@@ -3534,11 +3521,7 @@ _console_autocomplete(ProfWin* window, const char* const input, gboolean previou
     }
 
     result = autocomplete_param_with_ac(input, "/console", console_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3590,11 +3573,8 @@ _subject_autocomplete(ProfWin* window, const char* const input, gboolean previou
     }
 
     result = autocomplete_param_with_ac(input, "/subject", subject_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
 
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3770,11 +3750,8 @@ _presence_autocomplete(ProfWin* window, const char* const input, gboolean previo
     }
 
     found = autocomplete_param_with_ac(input, "/presence", presence_ac, TRUE, previous);
-    if (found) {
-        return found;
-    }
 
-    return NULL;
+    return found;
 }
 
 static char*
@@ -3871,11 +3848,8 @@ _statusbar_autocomplete(ProfWin* window, const char* const input, gboolean previ
     }
 
     found = autocomplete_param_with_ac(input, "/statusbar room", statusbar_room_ac, TRUE, previous);
-    if (found) {
-        return found;
-    }
 
-    return NULL;
+    return found;
 }
 
 static char*
@@ -3889,11 +3863,7 @@ _clear_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_func(input, "/clear persist_history", prefs_autocomplete_boolean_choice, previous, NULL);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
+    return result;
 }
 
 static char*
@@ -3989,11 +3959,7 @@ _logging_autocomplete(ProfWin* window, const char* const input, gboolean previou
     }
 
     result = autocomplete_param_with_ac(input, "/logging group", logging_group_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
+    return result;
 }
 
 static char*
@@ -4007,11 +3973,7 @@ _color_autocomplete(ProfWin* window, const char* const input, gboolean previous)
     }
 
     result = autocomplete_param_with_ac(input, "/color", color_ac, TRUE, previous);
-    if (result) {
-        return result;
-    }
-
-    return NULL;
+    return result;
 }
 
 static char*
@@ -4173,3 +4135,21 @@ _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean pr
 
     return result;
 }
+
+static char*
+_intype_autocomplete(ProfWin* window, const char* const input, gboolean previous)
+{
+    char* result = NULL;
+    result = autocomplete_param_with_func(input, "/intype console", prefs_autocomplete_boolean_choice, previous, NULL);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_func(input, "/intype titlebar", prefs_autocomplete_boolean_choice, previous, NULL);
+    if (result) {
+        return result;
+    }
+
+    result = autocomplete_param_with_ac(input, "/intype", intype_ac, FALSE, previous);
+    return result;
+}
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 5c03cad3..e04ba2a1 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -822,7 +822,7 @@ static struct cmd_t command_defs[] = {
               CMD_TAG_GROUPCHAT)
       CMD_SYN(
               "/bookmark",
-              "/bookmark list",
+              "/bookmark list [<jid>]",
               "/bookmark add [<room>] [nick <nick>] [password <password>] [name <roomname>] [autojoin on|off]",
               "/bookmark update <room> [nick <nick>] [password <password>] [name <roomname>] [autojoin on|off]",
               "/bookmark remove [<room>]",
@@ -836,7 +836,7 @@ static struct cmd_t command_defs[] = {
               "If you are in a chat room and no arguments are supplied to `/bookmark add`, autojoin is set to \"on\". "
               "There is also an autojoin ignore list in case you want to autojoin in many clients but not on Profanity. ")
       CMD_ARGS(
-              { "list", "List all bookmarks." },
+              { "list [<jid>]", "List all bookmarks. Or the details of one." },
               { "add [<room>]", "Add a bookmark, passing no room will bookmark the current room, setting autojoin to \"on\"." },
               { "remove [<room>]", "Remove a bookmark, passing no room will remove the bookmark for the current room, if one exists." },
               { "update <room>", "Update the properties associated with a bookmark." },
@@ -853,6 +853,8 @@ static struct cmd_t command_defs[] = {
               "/bookmark join room@example.com",
               "/bookmark update room@example.com nick NEWNICK autojoin on",
               "/bookmark ignore room@example.com",
+              "/bookmark list",
+              "/bookmark list room@example.com",
               "/bookmark remove room@example.com")
     },
 
@@ -867,7 +869,8 @@ static struct cmd_t command_defs[] = {
               "/disco items [<jid>]")
       CMD_DESC(
               "Find out information about an entities supported services. "
-              "Calling with no arguments will query the server you are currently connected to.")
+              "Calling with no arguments will query the server you are currently connected to. "
+              "This includes discovering contact addresses for XMPP services (XEP-0157).")
       CMD_ARGS(
               { "info [<jid>]", "List protocols and features supported by an entity." },
               { "items [<jid>]", "List items associated with an entity." })
@@ -973,6 +976,7 @@ static struct cmd_t command_defs[] = {
       parse_args, 0, 3, NULL,
       CMD_SUBFUNCS(
               { "unread", cmd_wins_unread },
+              { "attention", cmd_wins_attention },
               { "prune", cmd_wins_prune },
               { "swap", cmd_wins_swap })
       CMD_MAINFUNC(cmd_wins)
@@ -981,6 +985,7 @@ static struct cmd_t command_defs[] = {
       CMD_SYN(
               "/wins",
               "/wins unread",
+              "/wins attention",
               "/wins prune",
               "/wins swap <source> <target>")
       CMD_DESC(
@@ -988,6 +993,7 @@ static struct cmd_t command_defs[] = {
               "Passing no argument will list all currently active windows and information about their usage.")
       CMD_ARGS(
               { "unread", "List windows with unread messages." },
+              { "attention", "List windows that have been marked with the attention flag (alt+f). You can toggle between marked windows with alt+m." },
               { "prune", "Close all windows with no unread messages." },
               { "swap <source> <target>", "Swap windows, target may be an empty position." })
       CMD_NOEXAMPLES
@@ -1514,18 +1520,19 @@ static struct cmd_t command_defs[] = {
     },
 
     { "/intype",
-      parse_args, 1, 1, &cons_intype_setting,
+      parse_args, 2, 2, &cons_intype_setting,
       CMD_NOSUBFUNCS
       CMD_MAINFUNC(cmd_intype)
       CMD_TAGS(
               CMD_TAG_UI,
               CMD_TAG_CHAT)
       CMD_SYN(
-              "/intype on|off")
+              "/intype console|titlebar on|off")
       CMD_DESC(
               "Show when a contact is typing in the console, and in active message window.")
       CMD_ARGS(
-              { "on|off", "Enable or disable contact typing messages." })
+              { "titlebar on|off", "Enable or disable contact typing messages notification in titlebar." },
+              { "console on|off", "Enable or disable contact typing messages notification in console window." })
       CMD_NOEXAMPLES
     },
 
@@ -2532,7 +2539,7 @@ static struct cmd_t command_defs[] = {
               { "urlopen default", "Restore to default settings." },
               { "urlsave set", "Set executable that is run by /url save. Takes a command template that replaces %u and %p with the URL and path respectively." },
               { "urlsave default", "Use the built-in download method for saving." },
-              { "editor set", "Set editor to be used with /editor. Needs full file path." })
+              { "editor set", "Set editor to be used with /editor. Needs a terminal editor or a script to run a graphical editor." })
       CMD_EXAMPLES(
               "/executable avatar xdg-open",
               "/executable urlopen set \"xdg-open %u\"",
@@ -2541,7 +2548,7 @@ static struct cmd_t command_defs[] = {
               "/executable urlsave set \"wget %u -O %p\"",
               "/executable urlsave set \"curl %u -o %p\"",
               "/executable urlsave default",
-              "/executable editor set /usr/bin/vim")
+              "/executable editor set vim")
     },
 
     { "/url",
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 351f7b98..e1108982 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -1299,6 +1299,13 @@ cmd_wins_unread(ProfWin* window, const char* const command, gchar** args)
 }
 
 gboolean
+cmd_wins_attention(ProfWin* window, const char* const command, gchar** args)
+{
+    cons_show_wins_attention();
+    return TRUE;
+}
+
+gboolean
 cmd_wins_prune(ProfWin* window, const char* const command, gchar** args)
 {
     ui_prune_wins();
@@ -2108,6 +2115,33 @@ cmd_who(ProfWin* window, const char* const command, gchar** args)
     return TRUE;
 }
 
+static void
+_cmd_msg_chatwin(const char* const barejid, const char* const msg)
+{
+    ProfChatWin* chatwin = wins_get_chat(barejid);
+    if (!chatwin) {
+        // NOTE: This will also start the new OMEMO session and send a MAM request.
+        chatwin = chatwin_new(barejid);
+    }
+    ui_focus_win((ProfWin*)chatwin);
+
+    if (msg) {
+        // NOTE: In case the message is OMEMO encrypted, we can't be sure
+        // whether the key bundles of the recipient have already been
+        // received. In the case that *no* bundles have been received yet,
+        // the message won't be sent, and an error is shown to the user.
+        // Other cases are not handled here.
+        cl_ev_send_msg(chatwin, msg, NULL);
+    } else {
+#ifdef HAVE_LIBOTR
+        // Start the OTR session after this (i.e. the first) message was sent
+        if (otr_is_secure(barejid)) {
+            chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
+        }
+#endif // HAVE_LIBOTR
+    }
+}
+
 gboolean
 cmd_msg(ProfWin* window, const char* const command, gchar** args)
 {
@@ -2125,22 +2159,36 @@ cmd_msg(ProfWin* window, const char* const command, gchar** args)
     if (window->type == WIN_MUC) {
         ProfMucWin* mucwin = (ProfMucWin*)window;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
-        if (muc_roster_contains_nick(mucwin->roomjid, usr)) {
-            GString* full_jid = g_string_new(mucwin->roomjid);
-            g_string_append(full_jid, "/");
-            g_string_append(full_jid, usr);
 
-            ProfPrivateWin* privwin = wins_get_private(full_jid->str);
-            if (!privwin) {
-                privwin = (ProfPrivateWin*)wins_new_private(full_jid->str);
-            }
-            ui_focus_win((ProfWin*)privwin);
+        Occupant* occupant = muc_roster_item(mucwin->roomjid, usr);
+        if (occupant) {
+            // in case of non-anon muc send regular chatmessage
+            if (muc_anonymity_type(mucwin->roomjid) == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
+                Jid* jidp = jid_create(occupant->jid);
 
-            if (msg) {
-                cl_ev_send_priv_msg(privwin, msg, NULL);
-            }
+                _cmd_msg_chatwin(jidp->barejid, msg);
+                win_println(window, THEME_DEFAULT, "-", "Starting direct message with occupant \"%s\" from room \"%s\" as \"%s\".", usr, mucwin->roomjid, jidp->barejid);
+                cons_show("Starting direct message with occupant \"%s\" from room \"%s\" as \"%s\".", usr, mucwin->roomjid, jidp->barejid);
 
-            g_string_free(full_jid, TRUE);
+                jid_destroy(jidp);
+            } else {
+                // otherwise send mucpm
+                GString* full_jid = g_string_new(mucwin->roomjid);
+                g_string_append(full_jid, "/");
+                g_string_append(full_jid, usr);
+
+                ProfPrivateWin* privwin = wins_get_private(full_jid->str);
+                if (!privwin) {
+                    privwin = (ProfPrivateWin*)wins_new_private(full_jid->str);
+                }
+                ui_focus_win((ProfWin*)privwin);
+
+                if (msg) {
+                    cl_ev_send_priv_msg(privwin, msg, NULL);
+                }
+
+                g_string_free(full_jid, TRUE);
+            }
 
         } else {
             win_println(window, THEME_DEFAULT, "-", "No such participant \"%s\" in room.", usr);
@@ -2155,28 +2203,7 @@ cmd_msg(ProfWin* window, const char* const command, gchar** args)
             barejid = usr;
         }
 
-        ProfChatWin* chatwin = wins_get_chat(barejid);
-        if (!chatwin) {
-            // NOTE: This will also start the new OMEMO session and send a MAM request.
-            chatwin = chatwin_new(barejid);
-        }
-        ui_focus_win((ProfWin*)chatwin);
-
-        if (msg) {
-            // NOTE: In case the message is OMEMO encrypted, we can't be sure
-            // whether the key bundles of the recipient have already been
-            // received. In the case that *no* bundles have been received yet,
-            // the message won't be sent, and an error is shown to the user.
-            // Other cases are not handled here.
-            cl_ev_send_msg(chatwin, msg, NULL);
-        } else {
-#ifdef HAVE_LIBOTR
-            // Start the OTR session after this (i.e. the first) message was sent
-            if (otr_is_secure(barejid)) {
-                chatwin_otr_secured(chatwin, otr_is_trusted(barejid));
-            }
-#endif // HAVE_LIBOTR
-        }
+        _cmd_msg_chatwin(barejid, msg);
 
         return TRUE;
     }
@@ -4634,9 +4661,18 @@ cmd_bookmark(ProfWin* window, const char* const command, gchar** args)
     }
 
     if (strcmp(cmd, "list") == 0) {
-        GList* bookmarks = bookmark_get_list();
-        cons_show_bookmarks(bookmarks);
-        g_list_free(bookmarks);
+        char* bookmark_jid = args[1];
+        if (bookmark_jid == NULL) {
+            // list all bookmarks
+            GList* bookmarks = bookmark_get_list();
+            cons_show_bookmarks(bookmarks);
+            g_list_free(bookmarks);
+        } else {
+             // list one bookmark
+            Bookmark *bookmark = bookmark_get_by_jid(bookmark_jid);
+            cons_show_bookmark(bookmark);
+        }
+
         return TRUE;
     }
 
@@ -5107,14 +5143,14 @@ cmd_clear(ProfWin* window, const char* const command, gchar** args)
 
             if (args[1] != NULL) {
                 if ((g_strcmp0(args[1], "on") == 0) || (g_strcmp0(args[1], "off") == 0)) {
-                    _cmd_set_boolean_preference(args[1], command, "Persistant history", PREF_CLEAR_PERSIST_HISTORY);
+                    _cmd_set_boolean_preference(args[1], command, "Persistent history", PREF_CLEAR_PERSIST_HISTORY);
                     return TRUE;
                 }
             } else {
                 if (prefs_get_boolean(PREF_CLEAR_PERSIST_HISTORY)) {
-                    win_println(window, THEME_DEFAULT, "!", "  Persistantly clear screen  : ON");
+                    win_println(window, THEME_DEFAULT, "!", "  Persistently clear screen  : ON");
                 } else {
-                    win_println(window, THEME_DEFAULT, "!", "  Persistantly clear screen  : OFF");
+                    win_println(window, THEME_DEFAULT, "!", "  Persistently clear screen  : OFF");
                 }
                 return TRUE;
             }
@@ -6719,7 +6755,14 @@ cmd_tray(ProfWin* window, const char* const command, gchar** args)
 gboolean
 cmd_intype(ProfWin* window, const char* const command, gchar** args)
 {
-    _cmd_set_boolean_preference(args[0], command, "Show contact typing", PREF_INTYPE);
+    if (g_strcmp0(args[0], "console") == 0) {
+        _cmd_set_boolean_preference(args[1], command, "Show contact typing in console", PREF_INTYPE_CONSOLE);
+    } else if (g_strcmp0(args[0], "titlebar") == 0) {
+        _cmd_set_boolean_preference(args[1], command, "Show contact typing in titlebar", PREF_INTYPE);
+    } else {
+        cons_bad_cmd_usage(command);
+    }
+
     return TRUE;
 }
 
@@ -8388,7 +8431,7 @@ cmd_omemo_gen(ProfWin* window, const char* const command, gchar** args)
     ui_update();
     ProfAccount* account = accounts_get_account(session_get_account_name());
     omemo_generate_crypto_materials(account);
-    cons_show("OMEMO crytographic materials generated.");
+    cons_show("OMEMO crytographic materials generated. Your Device ID is %d.", omemo_device_id());
     return TRUE;
 #else
     cons_show("This version of Profanity has not been built with OMEMO support enabled");
@@ -9360,30 +9403,33 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args)
 gboolean
 cmd_editor(ProfWin* window, const char* const command, gchar** args)
 {
-    xmpp_ctx_t* const ctx = connection_get_ctx();
-    if (!ctx) {
-        log_debug("Editor: no connection");
-        return TRUE;
-    }
+    jabber_conn_status_t conn_status = connection_get_status();
 
-    // build temp file name. Example: /tmp/profanity-f2f271dd-98c8-4118-8d47-3bd49c8e2e63.md
-    char* uuid = xmpp_uuid_gen(ctx);
-    char* filename = g_strdup_printf("%s%s%s.md", g_get_tmp_dir(), "/profanity-", uuid);
-    if (uuid) {
-        xmpp_free(ctx, uuid);
+    if (conn_status != JABBER_CONNECTED) {
+        cons_show("You are currently not connected.");
+        return TRUE;
     }
 
-    // Check if file exists and create file
-    if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
-        cons_show("Editor: temp file exists already");
+    // create editor dir if not present
+    char *jid = connection_get_barejid();
+    gchar *path = files_get_account_data_path(DIR_EDITOR, jid);
+    if (g_mkdir_with_parents(path, S_IRWXU) != 0) {
+        cons_show_error("Failed to create directory at '%s' with error '%s'", path, strerror(errno));
+        free(jid);
+        g_free(path);
         return TRUE;
     }
+    // build temp file name. Example: /home/user/.local/share/profanity/editor/jid/compose.md
+    char* filename = g_strdup_printf("%s/compose.md", path);
+    free(jid);
+    g_free(path);
 
     GError* creation_error = NULL;
     GFile* file = g_file_new_for_path(filename);
-    GFileOutputStream* fos = g_file_create(file,
-                                           G_FILE_CREATE_PRIVATE, NULL,
-                                           &creation_error);
+    GFileOutputStream* fos = g_file_create(file, G_FILE_CREATE_PRIVATE, NULL, &creation_error);
+
+    free(filename);
+
     if (creation_error) {
         cons_show_error("Editor: could not create temp file");
         return TRUE;
@@ -9391,15 +9437,11 @@ cmd_editor(ProfWin* window, const char* const command, gchar** args)
     g_object_unref(fos);
 
     char* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
-    if (!g_file_test(editor, G_FILE_TEST_EXISTS)) {
-        cons_show_error("Editor: binary %s not exist", editor);
-        return TRUE;
-    }
 
     // Fork / exec
     pid_t pid = fork();
     if (pid == 0) {
-        int x = execl(editor, editor, g_file_get_path(file), (char*)NULL);
+        int x = execlp(editor, editor, g_file_get_path(file), (char*)NULL);
         if (x == -1) {
             cons_show_error("Editor:Failed to exec %s", editor);
         }
@@ -9417,8 +9459,6 @@ cmd_editor(ProfWin* window, const char* const command, gchar** args)
         if (size_read > 0 && size_read <= COUNT) {
             buf[size_read - 1] = '\0';
             GString* text = g_string_new(buf);
-            ProfWin* win = wins_get_current();
-            win_println(win, THEME_DEFAULT, "!", "EDITOR PREVIEW: %s", text->str);
             rl_insert_text(text->str);
             g_string_free(text, TRUE);
         }
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index 0785963b..aadcb55f 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -209,6 +209,7 @@ gboolean cmd_otr_sendfile(ProfWin* window, const char* const command, gchar** ar
 
 gboolean cmd_wins(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_wins_unread(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_wins_attention(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_wins_prune(ProfWin* window, const char* const command, gchar** args);
 gboolean cmd_wins_swap(ProfWin* window, const char* const command, gchar** args);
 
diff --git a/src/config/files.h b/src/config/files.h
index 42499663..a6b5a730 100644
--- a/src/config/files.h
+++ b/src/config/files.h
@@ -58,6 +58,7 @@
 #define DIR_PLUGINS   "plugins"
 #define DIR_DATABASE  "database"
 #define DIR_DOWNLOADS "downloads"
+#define DIR_EDITOR    "editor"
 
 void files_create_directories(void);
 
diff --git a/src/config/preferences.c b/src/config/preferences.c
index f4c86b2d..3e1ba585 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -1803,6 +1803,7 @@ _get_group(preference_t pref)
     case PREF_WINTITLE_GOODBYE:
     case PREF_FLASH:
     case PREF_INTYPE:
+    case PREF_INTYPE_CONSOLE:
     case PREF_HISTORY:
     case PREF_OCCUPANTS:
     case PREF_OCCUPANTS_JID:
@@ -1967,6 +1968,8 @@ _get_key(preference_t pref)
         return "adv.notify.discoversion";
     case PREF_INTYPE:
         return "intype";
+    case PREF_INTYPE_CONSOLE:
+        return "intype.console";
     case PREF_HISTORY:
         return "history";
     case PREF_CARBONS:
@@ -2210,14 +2213,6 @@ _get_default_boolean(preference_t pref)
     case PREF_AUTOAWAY_CHECK:
     case PREF_LOG_ROTATE:
     case PREF_LOG_SHARED:
-    case PREF_NOTIFY_CHAT:
-    case PREF_NOTIFY_CHAT_CURRENT:
-    case PREF_NOTIFY_ROOM:
-    case PREF_NOTIFY_ROOM_CURRENT:
-    case PREF_NOTIFY_TYPING:
-    case PREF_NOTIFY_TYPING_CURRENT:
-    case PREF_NOTIFY_SUB:
-    case PREF_NOTIFY_INVITE:
     case PREF_SPLASH:
     case PREF_OCCUPANTS:
     case PREF_MUC_PRIVILEGES:
@@ -2238,7 +2233,6 @@ _get_default_boolean(preference_t pref)
     case PREF_ROSTER_ROOMS_SERVER:
     case PREF_TLS_SHOW:
     case PREF_LASTACTIVITY:
-    case PREF_NOTIFY_MENTION_WHOLE_WORD:
     case PREF_TRAY_READ:
     case PREF_BOOKMARK_INVITE:
     case PREF_ROOM_LIST_CACHE:
@@ -2252,6 +2246,8 @@ _get_default_boolean(preference_t pref)
     case PREF_OUTTYPE:
     case PREF_TITLEBAR_MUC_TITLE_NAME:
     case PREF_COLOR_NICK_OWN:
+    case PREF_INTYPE:
+    case PREF_INTYPE_CONSOLE:
         return TRUE;
     default:
         return FALSE;
@@ -2335,7 +2331,7 @@ _get_default_string(preference_t pref)
     case PREF_URL_OPEN_CMD:
         return "xdg-open %u";
     case PREF_COMPOSE_EDITOR:
-        return "/usr/bin/vim";
+        return "vim";
     case PREF_URL_SAVE_CMD:
         return NULL; // Default to built-in method.
     default:
diff --git a/src/config/preferences.h b/src/config/preferences.h
index d58cf8b1..d8573349 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -59,6 +59,7 @@ typedef enum {
     PREF_TRAY_READ,
     PREF_ADV_NOTIFY_DISCO_OR_VERSION,
     PREF_INTYPE,
+    PREF_INTYPE_CONSOLE,
     PREF_HISTORY,
     PREF_CARBONS,
     PREF_RECEIPTS_SEND,
diff --git a/src/database.c b/src/database.c
index 5a213d7d..00aff314 100644
--- a/src/database.c
+++ b/src/database.c
@@ -217,7 +217,7 @@ log_database_get_previous_chat(const gchar* const contact_barejid)
     if (!myjid)
         return NULL;
 
-    query = g_strdup_printf("SELECT * FROM (SELECT `message`, `timestamp`, `from_jid`, `type` from `ChatLogs` WHERE (`from_jid` = '%s' AND `to_jid` = '%s') OR (`from_jid` = '%s' AND `to_jid` = '%s') ORDER BY `timestamp` DESC LIMIT 10) ORDER BY `timestamp` ASC;", contact_barejid, myjid->barejid, myjid->barejid, contact_barejid);
+    query = sqlite3_mprintf("SELECT * FROM (SELECT `message`, `timestamp`, `from_jid`, `type` from `ChatLogs` WHERE (`from_jid` = '%q' AND `to_jid` = '%q') OR (`from_jid` = '%q' AND `to_jid` = '%q') ORDER BY `timestamp` DESC LIMIT 10) ORDER BY `timestamp` ASC;", contact_barejid, myjid->barejid, myjid->barejid, contact_barejid);
     if (!query) {
         log_error("log_database_get_previous_chat(): SQL query. could not allocate memory");
         return NULL;
@@ -250,7 +250,7 @@ log_database_get_previous_chat(const gchar* const contact_barejid)
         history = g_slist_append(history, msg);
     }
     sqlite3_finalize(stmt);
-    g_free(query);
+    sqlite3_free(query);
 
     return history;
 }
@@ -328,14 +328,12 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
         type = (char*)_get_message_type_str(message->type);
     }
 
-    char* escaped_message = str_replace(message->plain, "'", "''");
-
-    query = g_strdup_printf("INSERT INTO `ChatLogs` (`from_jid`, `from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`, `stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) SELECT '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' WHERE NOT EXISTS (SELECT 1 FROM `ChatLogs` WHERE `archive_id` = '%s')",
+    query = sqlite3_mprintf("INSERT INTO `ChatLogs` (`from_jid`, `from_resource`, `to_jid`, `to_resource`, `message`, `timestamp`, `stanza_id`, `archive_id`, `replace_id`, `type`, `encryption`) SELECT '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q', '%q' WHERE NOT EXISTS (SELECT 1 FROM `ChatLogs` WHERE `archive_id` = '%q')",
             from_jid->barejid,
             from_jid->resourcepart ? from_jid->resourcepart : "",
             to_jid->barejid,
             to_jid->resourcepart ? to_jid->resourcepart : "",
-            escaped_message ? escaped_message : "",
+            message->plain ? message->plain : "",
             date_fmt ? date_fmt : "",
             message->id ? message->id : "",
             message->stanzaid ? message->stanzaid : "",
@@ -347,7 +345,6 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
         log_error("log_database_add(): SQL query. could not allocate memory");
         return;
     }
-    free(escaped_message);
     g_free(date_fmt);
 
     if (SQLITE_OK != sqlite3_exec(g_chatlog_database, query, NULL, 0, &err_msg)) {
@@ -358,5 +355,5 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji
             log_error("Unknown SQLite error");
         }
     }
-    g_free(query);
+    sqlite3_free(query);
 }
diff --git a/src/log.c b/src/log.c
index 6dfc089a..66fe8abe 100644
--- a/src/log.c
+++ b/src/log.c
@@ -246,18 +246,12 @@ _rotate_log_file(void)
             break;
     }
 
-    char* lf = strdup(mainlogfile);
-    char* start = strrchr(lf, '/') + 1;
-    char* end = strstr(start, ".log");
-    *end = '\0';
-
     log_close();
 
     rename(log_file, log_file_new);
 
-    log_init(log_get_filter(), start);
+    log_init(log_get_filter(), log_file);
 
-    free(lf);
     free(log_file_new);
     free(log_file);
     log_info("Log has been rotated");
diff --git a/src/omemo/omemo.c b/src/omemo/omemo.c
index 8c7a1276..560a3473 100644
--- a/src/omemo/omemo.c
+++ b/src/omemo/omemo.c
@@ -367,6 +367,8 @@ omemo_generate_crypto_materials(ProfAccount* account)
 void
 omemo_publish_crypto_materials(void)
 {
+    log_debug("[OMEMO] publish crypto materials");
+
     if (loaded != TRUE) {
         cons_show("OMEMO: cannot publish crypto materials before they are generated");
         log_error("[OMEMO] cannot publish crypto materials before they are generated");
@@ -404,12 +406,18 @@ void
 omemo_start_session(const char* const barejid)
 {
     if (omemo_loaded()) {
-        log_info("[OMEMO] start session with %s", barejid);
+        log_debug("[OMEMO] start session with %s", barejid);
         GList* device_list = g_hash_table_lookup(omemo_ctx.device_list, barejid);
         if (!device_list) {
-            log_info("[OMEMO] missing device list for %s", barejid);
+            log_debug("[OMEMO] missing device list for %s", barejid);
+            // Own devices are handled by _handle_own_device_list
+            // We won't add _handle_device_list_start_session for ourself
+            char* mybarejid = connection_get_barejid();
+            if( g_strcmp0(mybarejid, barejid ) != 0 ) {
+                g_hash_table_insert(omemo_ctx.device_list_handler, strdup(barejid), _handle_device_list_start_session);
+            }
+            free(mybarejid);
             omemo_devicelist_request(barejid);
-            g_hash_table_insert(omemo_ctx.device_list_handler, strdup(barejid), _handle_device_list_start_session);
             return;
         }
 
@@ -525,6 +533,7 @@ omemo_prekeys(GList** prekeys, GList** ids, GList** lengths)
 void
 omemo_set_device_list(const char* const from, GList* device_list)
 {
+    log_debug("[OMEMO] Setting device list for %s", from);
     Jid* jid;
     if (from) {
         jid = jid_create(from);
@@ -540,15 +549,17 @@ omemo_set_device_list(const char* const from, GList* device_list)
         if (!keep) {
             g_hash_table_remove(omemo_ctx.device_list_handler, jid->barejid);
         }
+    } else {
+        log_debug("[OMEMO] No Device List Handler for %s", from);
     }
 
     // OMEMO trustmode ToFu
     if (g_strcmp0(prefs_get_string(PREF_OMEMO_TRUST_MODE), "firstusage") == 0) {
-        log_info("[OMEMO] Checking firstusage state for %s", jid->barejid);
+        log_debug("[OMEMO] Checking firstusage state for %s", jid->barejid);
         GHashTable* trusted = g_hash_table_lookup(omemo_ctx.identity_key_store.trusted, jid->barejid);
         if (trusted) {
             if (g_hash_table_size(trusted) > 0) {
-                log_info("[OMEMO] Found trusted device for %s - skip firstusage", jid->barejid);
+                log_debug("[OMEMO] Found trusted device for %s - skip firstusage", jid->barejid);
                 return;
             }
         } else {
@@ -638,6 +649,7 @@ omemo_start_device_session(const char* const jid, uint32_t device_id,
                            const unsigned char* const signature, size_t signature_len,
                            const unsigned char* const identity_key_raw, size_t identity_key_len)
 {
+    log_debug("[OMEMO] Starting device session for %s with device %d", jid, device_id);
     signal_protocol_address address = {
         .name = jid,
         .name_len = strlen(jid),
@@ -649,6 +661,7 @@ omemo_start_device_session(const char* const jid, uint32_t device_id,
     _cache_device_identity(jid, device_id, identity_key);
 
     gboolean trusted = is_trusted_identity(&address, (uint8_t*)identity_key_raw, identity_key_len, &omemo_ctx.identity_key_store);
+    log_debug("[OMEMO] Trust %s (%d): %d", jid, device_id, trusted);
 
     if ((g_strcmp0(prefs_get_string(PREF_OMEMO_TRUST_MODE), "blind") == 0) && !trusted) {
         char* fp = _omemo_fingerprint(identity_key, TRUE);
@@ -659,10 +672,12 @@ omemo_start_device_session(const char* const jid, uint32_t device_id,
     }
 
     if (!trusted) {
+        log_debug("[OMEMO] We don't trust device %d for %s\n", device_id,jid);
         goto out;
     }
 
     if (!contains_session(&address, omemo_ctx.session_store)) {
+        log_debug("[OMEMO] There is no Session for %s ( %d) ,... building session.", address.name, address.device_id );
         int res;
         session_pre_key_bundle* bundle;
         signal_protocol_address* address;
@@ -701,7 +716,9 @@ omemo_start_device_session(const char* const jid, uint32_t device_id,
             goto out;
         }
 
-        log_info("[OMEMO] create session with %s device %d", jid, device_id);
+        log_debug("[OMEMO] create session with %s device %d", jid, device_id);
+    } else {
+        log_debug("[OMEMO] session with %s device %d exists", jid, device_id);
     }
 
 out:
@@ -734,7 +751,7 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
 
     res = aes128gcm_encrypt(ciphertext, &ciphertext_len, tag, &tag_len, (const unsigned char* const)message, strlen(message), iv, key);
     if (res != 0) {
-        log_error("[OMEMO] cannot encrypt message");
+        log_error("[OMEMO][SEND] cannot encrypt message");
         goto out;
     }
 
@@ -770,7 +787,8 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
         GList* recipient_device_id = NULL;
         recipient_device_id = g_hash_table_lookup(omemo_ctx.device_list, recipients_iter->data);
         if (!recipient_device_id) {
-            log_warning("[OMEMO] cannot find device ids for %s", recipients_iter->data);
+            log_warning("[OMEMO][SEND] cannot find device ids for %s", recipients_iter->data);
+            win_println(win, THEME_ERROR, "!", "Can't find a OMEMO device id for %s.\n", recipients_iter->data);
             continue;
         }
 
@@ -784,16 +802,30 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
                 .device_id = GPOINTER_TO_INT(device_ids_iter->data)
             };
 
+            // Don't encrypt for this device (according to
+            // <https://xmpp.org/extensions/xep-0384.html#encrypt>).
+            // Yourself as recipients in case of MUC
+            char* mybarejid = connection_get_barejid();
+            if ( !g_strcmp0(mybarejid, recipients_iter->data) ) {
+                if (GPOINTER_TO_INT(device_ids_iter->data) == omemo_ctx.device_id) {
+                    free(mybarejid);
+                    log_debug("[OMEMO][SEND] Skipping %d (my device) ", GPOINTER_TO_INT(device_ids_iter->data));
+                    continue;
+                }
+            }
+            free(mybarejid);
+
+            log_debug("[OMEMO][SEND] recipients with device id %d for %s", GPOINTER_TO_INT(device_ids_iter->data), recipients_iter->data);
             res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
-            if (res != 0) {
-                log_error("[OMEMO] cannot create cipher for %s device id %d", address.name, address.device_id);
+            if (res != SG_SUCCESS ) {
+                log_error("[OMEMO][SEND] cannot create cipher for %s device id %d - code: %d", address.name, address.device_id, res);
                 continue;
             }
 
             res = session_cipher_encrypt(cipher, key_tag, AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH, &ciphertext);
             session_cipher_free(cipher);
-            if (res != 0) {
-                log_error("[OMEMO] cannot encrypt key for %s device id %d", address.name, address.device_id);
+            if (res != SG_SUCCESS ) {
+                log_info("[OMEMO][SEND] cannot encrypt key for %s device id %d - code: %d", address.name, address.device_id,res);
                 continue;
             }
             signal_buffer* buffer = ciphertext_message_get_serialized(ciphertext);
@@ -823,6 +855,7 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
     // Encrypt keys for the sender
     if (!muc) {
         GList* sender_device_id = g_hash_table_lookup(omemo_ctx.device_list, jid->barejid);
+
         for (device_ids_iter = sender_device_id; device_ids_iter != NULL; device_ids_iter = device_ids_iter->next) {
             int res;
             ciphertext_message* ciphertext;
@@ -832,7 +865,7 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
                 .name_len = strlen(jid->barejid),
                 .device_id = GPOINTER_TO_INT(device_ids_iter->data)
             };
-
+            log_debug("[OMEMO][SEND][Sender] Sending to device %d for %s ", address.device_id, address.name);
             // Don't encrypt for this device (according to
             // <https://xmpp.org/extensions/xep-0384.html#encrypt>).
             if (address.device_id == omemo_ctx.device_id) {
@@ -841,14 +874,14 @@ omemo_on_message_send(ProfWin* win, const char* const message, gboolean request_
 
             res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
             if (res != 0) {
-                log_error("[OMEMO] cannot create cipher for %s device id %d", address.name, address.device_id);
+                log_info("[OMEMO][SEND][Sender] cannot create cipher for %s device id %d", address.name, address.device_id);
                 continue;
             }
 
             res = session_cipher_encrypt(cipher, key_tag, AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH, &ciphertext);
             session_cipher_free(cipher);
             if (res != 0) {
-                log_error("[OMEMO] cannot encrypt key for %s device id %d", address.name, address.device_id);
+                log_info("[OMEMO][SEND][Sender] cannot encrypt key for %s device id %d", address.name, address.device_id);
                 continue;
             }
             signal_buffer* buffer = ciphertext_message_get_serialized(ciphertext);
@@ -895,7 +928,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
     Jid* sender = NULL;
     Jid* from = jid_create(from_jid);
     if (!from) {
-        log_error("Invalid jid %s", from_jid);
+        log_error("[OMEMO][RECV] Invalid jid %s", from_jid);
         goto out;
     }
 
@@ -910,7 +943,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
     }
 
     if (!key) {
-        log_warning("[OMEMO] received a message with no corresponding key");
+        log_warning("[OMEMO][RECV] received a message with no corresponding key");
         goto out;
     }
 
@@ -926,7 +959,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
         }
         g_list_free(roster);
         if (!sender) {
-            log_warning("[OMEMO] cannot find MUC message sender fulljid");
+            log_warning("[OMEMO][RECV] cannot find MUC message sender fulljid");
             goto out;
         }
     } else {
@@ -943,12 +976,12 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
 
     res = session_cipher_create(&cipher, omemo_ctx.store, &address, omemo_ctx.signal);
     if (res != 0) {
-        log_error("[OMEMO] cannot create session cipher");
+        log_error("[OMEMO][RECV] cannot create session cipher");
         goto out;
     }
 
     if (key->prekey) {
-        log_debug("[OMEMO] decrypting message with prekey");
+        log_debug("[OMEMO][RECV] decrypting message with prekey");
         pre_key_signal_message* message;
         ec_public_key* their_identity_key;
         signal_buffer* identity_buffer = NULL;
@@ -981,16 +1014,17 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
 
         if (res == 0) {
             /* Start a new session */
+            log_debug("[OMEMO][RECV] Res is 0 => omemo_bundle_request");
             omemo_bundle_request(sender->barejid, sid, omemo_start_device_session_handle_bundle, free, strdup(sender->barejid));
         }
     } else {
-        log_debug("[OMEMO] decrypting message with existing session");
+        log_debug("[OMEMO][RECV] decrypting message with existing session");
         signal_message* message = NULL;
 
         res = signal_message_deserialize(&message, key->data, key->length, omemo_ctx.signal);
 
         if (res < 0) {
-            log_error("[OMEMO] cannot deserialize message");
+            log_error("[OMEMO][RECV] cannot deserialize message");
         } else {
             res = session_cipher_decrypt_signal_message(cipher, message, NULL, &plaintext_key);
             *trusted = true;
@@ -1000,12 +1034,12 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
 
     session_cipher_free(cipher);
     if (res != 0) {
-        log_error("[OMEMO] cannot decrypt message key");
+        log_error("[OMEMO][RECV] cannot decrypt message key");
         goto out;
     }
 
     if (signal_buffer_len(plaintext_key) != AES128_GCM_KEY_LENGTH + AES128_GCM_TAG_LENGTH) {
-        log_error("[OMEMO] invalid key length");
+        log_error("[OMEMO][RECV] invalid key length");
         signal_buffer_free(plaintext_key);
         goto out;
     }
@@ -1017,7 +1051,7 @@ omemo_on_message_recv(const char* const from_jid, uint32_t sid,
                             signal_buffer_data(plaintext_key) + AES128_GCM_KEY_LENGTH);
     signal_buffer_free(plaintext_key);
     if (res != 0) {
-        log_error("[OMEMO] cannot decrypt message: %s", gcry_strerror(res));
+        log_error("[OMEMO][RECV] cannot decrypt message: %s", gcry_strerror(res));
         free(plaintext);
         plaintext = NULL;
         goto out;
@@ -1112,6 +1146,7 @@ omemo_is_trusted_identity(const char* const jid, const char* const fingerprint)
     buffer = signal_buffer_append(buffer, fingerprint_raw, fingerprint_len);
 
     gboolean trusted = is_trusted_identity(&address, signal_buffer_data(buffer), signal_buffer_len(buffer), &omemo_ctx.identity_key_store);
+    log_debug("[OMEMO] Device trusted %s (%d): %d", jid, GPOINTER_TO_INT(device_id), trusted);
 
     free(fingerprint_raw);
     signal_buffer_free(buffer);
@@ -1305,17 +1340,17 @@ _omemo_log(int level, const char* message, size_t len, void* user_data)
 {
     switch (level) {
     case SG_LOG_ERROR:
-        log_error("[OMEMO] %s", message);
+        log_error("[OMEMO][SIGNAL] %s", message);
         break;
     case SG_LOG_WARNING:
-        log_warning("[OMEMO] %s", message);
+        log_warning("[OMEMO][SIGNAL] %s", message);
         break;
     case SG_LOG_NOTICE:
     case SG_LOG_INFO:
-        log_info("[OMEMO] %s", message);
+        log_debug("[OMEMO][SIGNAL] %s", message);
         break;
     case SG_LOG_DEBUG:
-        log_debug("[OMEMO] %s", message);
+        log_debug("[OMEMO][SIGNAL] %s", message);
         break;
     }
 }
@@ -1323,13 +1358,17 @@ _omemo_log(int level, const char* message, size_t len, void* user_data)
 static gboolean
 _handle_own_device_list(const char* const jid, GList* device_list)
 {
+    // We didn't find the own device id -> publish
     if (!g_list_find(device_list, GINT_TO_POINTER(omemo_ctx.device_id))) {
+        cons_show("Could not find own OMEMO device ID. Going to publish own device ID: %d", GINT_TO_POINTER(omemo_ctx.device_id));
+        log_debug("[OMEMO] No device ID for our device. Publishing device list");
         device_list = g_list_copy(device_list);
         device_list = g_list_append(device_list, GINT_TO_POINTER(omemo_ctx.device_id));
         g_hash_table_insert(omemo_ctx.device_list, strdup(jid), device_list);
         omemo_devicelist_publish(device_list);
     }
 
+    log_debug("[OMEMO] Request OMEMO Bundles for our devices");
     GList* device_id;
     for (device_id = device_list; device_id != NULL; device_id = device_id->next) {
         omemo_bundle_request(jid, GPOINTER_TO_INT(device_id->data), omemo_start_device_session_handle_bundle, free, strdup(jid));
@@ -1341,6 +1380,7 @@ _handle_own_device_list(const char* const jid, GList* device_list)
 static gboolean
 _handle_device_list_start_session(const char* const jid, GList* device_list)
 {
+    log_debug("[OMEMO] Start session for %s - device_list", jid);
     omemo_start_session(jid);
 
     return FALSE;
@@ -1446,7 +1486,7 @@ static gboolean
 _load_identity(void)
 {
     GError* error = NULL;
-    log_info("Loading OMEMO identity");
+    log_info("[OMEMO] Loading OMEMO identity");
 
     /* Device ID */
     error = NULL;
@@ -1455,7 +1495,7 @@ _load_identity(void)
         log_error("[OMEMO] cannot load device id: %s", error->message);
         return FALSE;
     }
-    log_info("[OMEMO] device id: %d", omemo_ctx.device_id);
+    log_debug("[OMEMO] device id: %d", omemo_ctx.device_id);
 
     /* Registration ID */
     error = NULL;
@@ -1658,7 +1698,7 @@ _cache_device_identity(const char* const jid, uint32_t device_id, ec_public_key*
     }
 
     char* fingerprint = _omemo_fingerprint(identity, FALSE);
-    log_info("[OMEMO] cache identity for %s:%d: %s", jid, device_id, fingerprint);
+    log_debug("[OMEMO] cache identity for %s:%d: %s", jid, device_id, fingerprint);
     g_hash_table_insert(known_identities, strdup(fingerprint), GINT_TO_POINTER(device_id));
 
     char* device_id_str = g_strdup_printf("%d", device_id);
@@ -1822,6 +1862,7 @@ omemo_parse_aesgcm_url(const char* aesgcm_url,
     }
 
     if (strlen(*fragment) != AESGCM_URL_NONCE_LEN + AESGCM_URL_KEY_LEN) {
+        ret = 1;
         goto out;
     }
 
diff --git a/src/omemo/store.c b/src/omemo/store.c
index 434483ed..70fd91d3 100644
--- a/src/omemo/store.c
+++ b/src/omemo/store.c
@@ -36,6 +36,7 @@
 #include <signal/signal_protocol.h>
 
 #include "config.h"
+#include "log.h"
 #include "omemo/omemo.h"
 #include "omemo/store.h"
 
@@ -80,15 +81,19 @@ load_session(signal_buffer** record, signal_buffer** user_record,
     GHashTable* session_store = (GHashTable*)user_data;
     GHashTable* device_store = NULL;
 
+    log_debug("[OMEMO][STORE] Looking for %s in session_store", address->name);
     device_store = g_hash_table_lookup(session_store, address->name);
     if (!device_store) {
         *record = NULL;
+        log_info("[OMEMO][STORE] No device store for %s found", address->name);
         return 0;
     }
 
+    log_debug("[OMEMO][STORE] Looking for device %d of %s ", address->device_id, address->name);
     signal_buffer* original = g_hash_table_lookup(device_store, GINT_TO_POINTER(address->device_id));
     if (!original) {
         *record = NULL;
+        log_warning("[OMEMO][STORE] No device (%d) store for %s found", address->device_id, address->name);
         return 0;
     }
     *record = signal_buffer_copy(original);
@@ -106,6 +111,7 @@ get_sub_device_sessions(signal_int_list** sessions, const char* name,
 
     device_store = g_hash_table_lookup(session_store, name);
     if (!device_store) {
+        log_debug("[OMEMO][STORE] What?");
         return SG_SUCCESS;
     }
 
@@ -133,6 +139,7 @@ store_session(const signal_protocol_address* address,
     GHashTable* session_store = (GHashTable*)user_data;
     GHashTable* device_store = NULL;
 
+    log_debug("[OMEMO][STORE] Store session for %s (%d)", address->name, address->device_id);
     device_store = g_hash_table_lookup(session_store, (void*)address->name);
     if (!device_store) {
         device_store = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)signal_buffer_free);
@@ -161,10 +168,12 @@ contains_session(const signal_protocol_address* address, void* user_data)
 
     device_store = g_hash_table_lookup(session_store, address->name);
     if (!device_store) {
+        log_debug("[OMEMO][STORE] No Device");
         return 0;
     }
 
     if (!g_hash_table_lookup(device_store, GINT_TO_POINTER(address->device_id))) {
+        log_debug("[OMEMO][STORE] No Session for %d ", address->device_id);
         return 0;
     }
 
@@ -200,6 +209,7 @@ delete_all_sessions(const char* name, size_t name_len, void* user_data)
 
     device_store = g_hash_table_lookup(session_store, name);
     if (!device_store) {
+        log_debug("[OMEMO][STORE] No device => no delete");
         return SG_SUCCESS;
     }
 
@@ -216,6 +226,7 @@ load_pre_key(signal_buffer** record, uint32_t pre_key_id, void* user_data)
 
     original = g_hash_table_lookup(pre_key_store, GINT_TO_POINTER(pre_key_id));
     if (original == NULL) {
+        log_error("[OMEMO][STORE] SG_ERR_INVALID_KEY_ID");
         return SG_ERR_INVALID_KEY_ID;
     }
 
@@ -269,6 +280,7 @@ remove_pre_key(uint32_t pre_key_id, void* user_data)
     if (ret > 0) {
         return SG_SUCCESS;
     } else {
+        log_error("[OMEMO][STORE] SG_ERR_INVALID_KEY_ID");
         return SG_ERR_INVALID_KEY_ID;
     }
 }
@@ -282,6 +294,7 @@ load_signed_pre_key(signal_buffer** record, uint32_t signed_pre_key_id,
 
     original = g_hash_table_lookup(signed_pre_key_store, GINT_TO_POINTER(signed_pre_key_id));
     if (!original) {
+        log_error("[OMEMO][STORE] SG_ERR_INVALID_KEY_ID");
         return SG_ERR_INVALID_KEY_ID;
     }
 
@@ -370,6 +383,7 @@ save_identity(const signal_protocol_address* address, uint8_t* key_data,
         int trusted = is_trusted_identity(address, key_data, key_len, user_data);
         identity_key_store->recv = true;
         if (trusted == 0) {
+            log_debug("[OMEMO][STORE] trusted 0");
             /* If not trusted we just don't save the identity */
             return SG_SUCCESS;
         }
@@ -402,12 +416,14 @@ is_trusted_identity(const signal_protocol_address* address, uint8_t* key_data,
 {
     int ret;
     identity_key_store_t* identity_key_store = (identity_key_store_t*)user_data;
-
+    log_debug("[OMEMO][STORE] Checking trust %s (%d)", address->name, address->device_id);
     GHashTable* trusted = g_hash_table_lookup(identity_key_store->trusted, address->name);
     if (!trusted) {
         if (identity_key_store->recv) {
+            log_debug("[OMEMO][STORE] identity_key_store->recv");
             return 1;
         } else {
+            log_debug("[OMEMO][STORE] !identity_key_store->recv");
             return 0;
         }
     }
@@ -415,13 +431,18 @@ is_trusted_identity(const signal_protocol_address* address, uint8_t* key_data,
     signal_buffer* buffer = signal_buffer_create(key_data, key_len);
     signal_buffer* original = g_hash_table_lookup(trusted, GINT_TO_POINTER(address->device_id));
 
+    if(!original) {
+        log_debug("[OMEMO][STORE] original not found %s (%d)", address->name, address->device_id);
+    }
     ret = original != NULL && signal_buffer_compare(buffer, original) == 0;
 
     signal_buffer_free(buffer);
 
     if (identity_key_store->recv) {
+        log_debug("[OMEMO][STORE] 1 identity_key_store->recv");
         return 1;
     } else {
+        log_debug("[OMEMO][STORE] Checking trust %s (%d): %d", address->name, address->device_id, ret);
         return ret;
     }
 }
diff --git a/src/tools/aesgcm_download.c b/src/tools/aesgcm_download.c
index 864da6b7..45aa7b66 100644
--- a/src/tools/aesgcm_download.c
+++ b/src/tools/aesgcm_download.c
@@ -70,6 +70,7 @@ aesgcm_file_get(void* userdata)
     // Convert the aesgcm:// URL to a https:// URL and extract the encoded key
     // and tag stored in the URL fragment.
     if (omemo_parse_aesgcm_url(aesgcm_dl->url, &https_url, &fragment) != 0) {
+        cons_show_error("Download failed: Cannot parse URL '%s'.", aesgcm_dl->url);
         http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->url,
                                    "Download failed: Cannot parse URL '%s'.",
                                    aesgcm_dl->url);
diff --git a/src/tools/http_upload.c b/src/tools/http_upload.c
index 17eca188..d1360b46 100644
--- a/src/tools/http_upload.c
+++ b/src/tools/http_upload.c
@@ -194,7 +194,7 @@ http_file_put(void* userdata)
 
     struct curl_slist* headers = NULL;
     content_type_header = g_strdup_printf("Content-Type: %s", upload->mime_type);
-    if (content_type_header) {
+    if (!content_type_header) {
         content_type_header = g_strdup(FALLBACK_CONTENTTYPE_HEADER);
     }
     headers = curl_slist_append(headers, content_type_header);
diff --git a/src/ui/console.c b/src/ui/console.c
index 9f4d6248..4cb041a8 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -507,6 +507,26 @@ cons_show_wins(gboolean unread)
 }
 
 void
+cons_show_wins_attention() {
+    ProfWin* console = wins_get_console();
+    cons_show("");
+    GSList* window_strings = wins_create_summary_attention();
+
+    GSList* curr = window_strings;
+    while (curr) {
+        if (g_strstr_len(curr->data, strlen(curr->data), " unread") > 0) {
+            win_println(console, THEME_CMD_WINS_UNREAD, "-", "%s", curr->data);
+        } else {
+            win_println(console, THEME_DEFAULT, "-", "%s", curr->data);
+        }
+        curr = g_slist_next(curr);
+    }
+    g_slist_free_full(window_strings, free);
+
+    cons_alert(NULL);
+}
+
+void
 cons_show_room_invites(GList* invites)
 {
     cons_show("");
@@ -725,6 +745,34 @@ cons_show_bookmarks(const GList* list)
 }
 
 void
+cons_show_bookmark(Bookmark* item)
+{
+    cons_show("");
+    if (!item) {
+        cons_show("No such bookmark");
+    } else {
+        cons_show("Bookmark details:");
+        cons_show("Room jid           : %s", item->barejid);
+        if (item->name) {
+            cons_show("name               : %s", item->name);
+        }
+        if (item->nick) {
+            cons_show("nick               : %s", item->nick);
+        }
+        if (item->password) {
+            cons_show("password           : %s", item->password);
+        }
+        if (item->autojoin) {
+            cons_show("autojoin           : ON");
+        } else {
+            cons_show("autojoin           : OFF");
+        }
+    }
+
+    cons_alert(NULL);
+}
+
+void
 cons_show_disco_info(const char* jid, GSList* identities, GSList* features)
 {
     if ((identities && (g_slist_length(identities) > 0)) || (features && (g_slist_length(features) > 0))) {
@@ -789,6 +837,28 @@ cons_show_disco_items(GSList* items, const char* const jid)
     cons_alert(NULL);
 }
 
+static void _cons_print_contact_information_item(gpointer data, gpointer user_data)
+{
+    cons_show("    %s", (char*)data);
+}
+
+static void _cons_print_contact_information_hashlist_item(gpointer key, gpointer value, gpointer userdata)
+{
+    cons_show("  %s:", (char*)key);
+    g_slist_foreach((GSList*)value, _cons_print_contact_information_item, NULL);
+}
+
+void
+cons_show_disco_contact_information(GHashTable* addresses)
+{
+    if (addresses && g_hash_table_size(addresses) > 0) {
+        cons_show("");
+        cons_show("Server contact information:");
+
+        g_hash_table_foreach(addresses, _cons_print_contact_information_hashlist_item, NULL);
+    }
+}
+
 void
 cons_show_status(const char* const barejid)
 {
@@ -1716,9 +1786,14 @@ void
 cons_intype_setting(void)
 {
     if (prefs_get_boolean(PREF_INTYPE))
-        cons_show("Show typing (/intype)         : ON");
+        cons_show("Show typing in titlebar (/intype titlebar)       : ON");
     else
-        cons_show("Show typing (/intype)         : OFF");
+        cons_show("Show typing in titlebar (/intype titlebar)       : OFF");
+
+    if (prefs_get_boolean(PREF_INTYPE_CONSOLE))
+        cons_show("Show typing in console (/intype console)         : ON");
+    else
+        cons_show("Show typing in console (/intype console)         : OFF");
 }
 
 void
@@ -2077,6 +2152,10 @@ cons_executable_setting(void)
     }
     cons_show("Default '/url save' command (/executable urlsave)                        : %s", urlsave);
     g_free(urlsave);
+
+    gchar* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
+    cons_show("Default '/editor' command (/executable editor)                           : %s", editor);
+    g_free(editor);
 }
 
 void
diff --git a/src/ui/core.c b/src/ui/core.c
index 931417c4..f4ee4a86 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -276,17 +276,21 @@ ui_contact_typing(const char* const barejid, const char* const resource)
     ProfWin* window = (ProfWin*)chatwin;
     ChatSession* session = chat_session_get(barejid);
 
-    if (prefs_get_boolean(PREF_INTYPE)) {
-        // no chat window for user
-        if (chatwin == NULL) {
+    // no chat window for user
+    if (chatwin == NULL) {
+        if (prefs_get_boolean(PREF_INTYPE_CONSOLE)) {
             cons_show_typing(barejid);
+        }
 
-            // have chat window but not currently in it
-        } else if (!wins_is_current(window)) {
+        // have chat window but not currently in it
+    } else if (!wins_is_current(window)) {
+        if (prefs_get_boolean(PREF_INTYPE_CONSOLE)) {
             cons_show_typing(barejid);
+        }
 
-            // in chat window with user, no session or session with resource
-        } else if (!session || (session && g_strcmp0(session->resource, resource) == 0)) {
+        // in chat window with user, no session or session with resource
+    } else if (!session || (session && g_strcmp0(session->resource, resource) == 0)) {
+        if (prefs_get_boolean(PREF_INTYPE)) {
             title_bar_set_typing(TRUE);
 
             int num = wins_get_num(window);
@@ -992,6 +996,20 @@ ui_win_unread(int index)
     }
 }
 
+gboolean
+ui_win_has_attention(int index)
+{
+    gboolean ret = FALSE;
+
+    ProfWin* window = wins_get_by_num(index);
+    if (window) {
+        ret = win_has_attention(window);
+    }
+
+    return ret;
+}
+
+
 char*
 ui_ask_password(gboolean confirm)
 {
diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c
index 74fcfbb4..b2b2ea33 100644
--- a/src/ui/inputwin.c
+++ b/src/ui/inputwin.c
@@ -125,6 +125,8 @@ static int _inp_rl_win_20_handler(int count, int key);
 static int _inp_rl_win_prev_handler(int count, int key);
 static int _inp_rl_win_next_handler(int count, int key);
 static int _inp_rl_win_next_unread_handler(int count, int key);
+static int _inp_rl_win_attention_handler(int count, int key);
+static int _inp_rl_win_attention_next_handler(int count, int key);
 static int _inp_rl_win_pageup_handler(int count, int key);
 static int _inp_rl_win_pagedown_handler(int count, int key);
 static int _inp_rl_subwin_pageup_handler(int count, int key);
@@ -480,6 +482,8 @@ _inp_rl_startup_hook(void)
     rl_bind_keyseq("\\e\\e[C", _inp_rl_win_next_handler);
 
     rl_bind_keyseq("\\ea", _inp_rl_win_next_unread_handler);
+    rl_bind_keyseq("\\ef", _inp_rl_win_attention_handler);
+    rl_bind_keyseq("\\em", _inp_rl_win_attention_next_handler);
 
     rl_bind_keyseq("\\e\\e[5~", _inp_rl_subwin_pageup_handler);
     rl_bind_keyseq("\\e[5;3~", _inp_rl_subwin_pageup_handler);
@@ -807,6 +811,31 @@ _inp_rl_win_next_unread_handler(int count, int key)
 }
 
 static int
+_inp_rl_win_attention_handler(int count, int key) {
+    ProfWin* current = wins_get_current();
+    if ( current ) {
+        gboolean attention = win_toggle_attention(current);
+        if (attention) {
+            win_println(current, THEME_DEFAULT, "!", "Attention flag has been activated");
+        } else {
+            win_println(current, THEME_DEFAULT, "!", "Attention flag has been deactivated");
+        }
+        win_redraw(current);
+    }
+    return 0;
+}
+
+static int
+_inp_rl_win_attention_next_handler(int count, int key) {
+    ProfWin* window = wins_get_next_attention();
+    if (window) {
+        ui_focus_win(window);
+    }
+    return 0;
+}
+
+
+static int
 _inp_rl_win_pageup_handler(int count, int key)
 {
     ProfWin* current = wins_get_current();
diff --git a/src/ui/mucwin.c b/src/ui/mucwin.c
index bd67b53f..54778acb 100644
--- a/src/ui/mucwin.c
+++ b/src/ui/mucwin.c
@@ -389,26 +389,36 @@ _mucwin_print_mention(ProfWin* window, const char* const message, const char* co
     while (curr) {
         pos = GPOINTER_TO_INT(curr->data);
 
-        char* before_str = g_strndup(message + last_pos, pos - last_pos);
+        char *before_str = g_utf8_substring(message, last_pos, last_pos + pos - last_pos);
+
         if (strncmp(before_str, "/me ", 4) == 0) {
             win_print_them(window, THEME_ROOMMENTION, ch, flags, "");
             win_append_highlight(window, THEME_ROOMMENTION, "*%s ", from);
             win_append_highlight(window, THEME_ROOMMENTION, "%s", before_str + 4);
         } else {
-            win_print_them(window, THEME_ROOMMENTION, ch, flags, from);
+            // print time and nick only once at beginning of the line
+            if (last_pos == 0) {
+                win_print_them(window, THEME_ROOMMENTION, ch, flags, from);
+            }
             win_append_highlight(window, THEME_ROOMMENTION, "%s", before_str);
         }
         g_free(before_str);
-        char* mynick_str = g_strndup(message + pos, strlen(mynick));
+
+        glong mynick_len = g_utf8_strlen(mynick, -1);
+        char* mynick_str = g_utf8_substring(message, pos, pos + mynick_len);
         win_append_highlight(window, THEME_ROOMMENTION_TERM, "%s", mynick_str);
         g_free(mynick_str);
 
-        last_pos = pos + strlen(mynick);
+        last_pos = pos + mynick_len;
 
         curr = g_slist_next(curr);
     }
-    if (last_pos < strlen(message)) {
-        win_appendln_highlight(window, THEME_ROOMMENTION, "%s", &message[last_pos]);
+
+    glong message_len = g_utf8_strlen(message, -1);
+    if (last_pos < message_len) {
+        char* rest = g_utf8_substring(message, last_pos, last_pos + message_len);
+        win_appendln_highlight(window, THEME_ROOMMENTION, "%s", rest);
+        g_free(rest);
     } else {
         win_appendln_highlight(window, THEME_ROOMMENTION, "");
     }
diff --git a/src/ui/titlebar.c b/src/ui/titlebar.c
index dee6a7e2..98af2b24 100644
--- a/src/ui/titlebar.c
+++ b/src/ui/titlebar.c
@@ -67,6 +67,7 @@ static void _show_contact_presence(ProfChatWin* chatwin, int pos, int maxpos);
 static void _show_privacy(ProfChatWin* chatwin);
 static void _show_muc_privacy(ProfMucWin* mucwin);
 static void _show_scrolled(ProfWin* current);
+static void _show_attention(ProfWin* current, gboolean attention);
 
 void
 create_title_bar(void)
@@ -210,6 +211,7 @@ _title_bar_draw(void)
         assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
         _show_contact_presence(chatwin, pos, maxrightpos);
         _show_privacy(chatwin);
+        _show_attention(current, chatwin->has_attention);
         _show_scrolled(current);
 
         if (typing) {
@@ -219,6 +221,7 @@ _title_bar_draw(void)
         ProfMucWin* mucwin = (ProfMucWin*)current;
         assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
         _show_muc_privacy(mucwin);
+        _show_attention(current, mucwin->has_attention);
         _show_scrolled(current);
     }
 
@@ -229,6 +232,26 @@ _title_bar_draw(void)
 }
 
 static void
+_show_attention(ProfWin* current, gboolean attention)
+{
+    int bracket_attrs = theme_attrs(THEME_TITLE_BRACKET);
+    int text_attrs = theme_attrs(THEME_TITLE_TEXT);
+
+    if (attention) {
+        wprintw(win, " ");
+        wattron(win, bracket_attrs);
+        wprintw(win, "[");
+        wattroff(win, bracket_attrs);
+        wattron(win, text_attrs);
+        wprintw(win, "ATTENTION");
+        wattroff(win, text_attrs);
+        wattron(win, bracket_attrs);
+        wprintw(win, "]");
+        wattroff(win, bracket_attrs);
+    }
+}
+
+static void
 _show_scrolled(ProfWin* current)
 {
     if (current && current->layout->paged == 1) {
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 391b906c..441c9adf 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -75,6 +75,9 @@ int ui_close_all_wins(void);
 int ui_close_read_wins(void);
 void ui_close_win(int index);
 int ui_win_unread(int index);
+gboolean ui_win_has_attention(int index);
+gboolean win_has_attention(ProfWin* window); 
+gboolean win_toggle_attention(ProfWin* window);
 char* ui_ask_password(gboolean confirm);
 char* ui_get_line(void);
 char* ui_ask_pgp_passphrase(const char* hint, int prev_fail);
@@ -256,6 +259,7 @@ void cons_show_contacts(GSList* list);
 void cons_show_roster(GSList* list);
 void cons_show_roster_group(const char* const group, GSList* list);
 void cons_show_wins(gboolean unread);
+void cons_show_wins_attention();
 char* cons_get_string(ProfConsoleWin* conswin);
 void cons_show_status(const char* const barejid);
 void cons_show_info(PContact pcontact);
@@ -267,10 +271,12 @@ void cons_show_aliases(GList* aliases);
 void cons_show_login_success(ProfAccount* account, gboolean secured);
 void cons_show_account_list(gchar** accounts);
 void cons_show_room_list(GSList* room, const char* const conference_node);
+void cons_show_bookmark(Bookmark* item);
 void cons_show_bookmarks(const GList* list);
 void cons_show_bookmarks_ignore(gchar** list, gsize len);
 void cons_show_disco_items(GSList* items, const char* const jid);
 void cons_show_disco_info(const char* from, GSList* identities, GSList* features);
+void cons_show_disco_contact_information(GHashTable* addresses);
 void cons_show_room_invite(const char* const invitor, const char* const room, const char* const reason);
 void cons_check_version(gboolean not_available_msg);
 void cons_show_typing(const char* const barejid);
diff --git a/src/ui/win_types.h b/src/ui/win_types.h
index e740b543..f2237a79 100644
--- a/src/ui/win_types.h
+++ b/src/ui/win_types.h
@@ -176,6 +176,7 @@ typedef struct prof_chat_win_t
     // For LMC
     char* last_message;
     char* last_msg_id;
+    gboolean has_attention;
 } ProfChatWin;
 
 typedef struct prof_muc_win_t
@@ -196,6 +197,7 @@ typedef struct prof_muc_win_t
     // For LMC
     char* last_message;
     char* last_msg_id;
+    gboolean has_attention;
 } ProfMucWin;
 
 typedef struct prof_conf_win_t ProfConfWin;
diff --git a/src/ui/window.c b/src/ui/window.c
index b6676774..4fb7d5f7 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -158,7 +158,7 @@ win_create_chat(const char* const barejid)
     new_win->outgoing_char = NULL;
     new_win->last_message = NULL;
     new_win->last_msg_id = NULL;
-
+    new_win->has_attention = FALSE;
     new_win->memcheck = PROFCHATWIN_MEMCHECK;
 
     return &new_win->window;
@@ -213,6 +213,7 @@ win_create_muc(const char* const roomjid)
     new_win->is_omemo = FALSE;
     new_win->last_message = NULL;
     new_win->last_msg_id = NULL;
+    new_win->has_attention = FALSE;
 
     new_win->memcheck = PROFMUCWIN_MEMCHECK;
 
@@ -1841,6 +1842,39 @@ win_unread(ProfWin* window)
     }
 }
 
+gboolean
+win_has_attention(ProfWin* window) 
+{
+    if (window->type == WIN_CHAT) {
+        ProfChatWin* chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+        return chatwin->has_attention;
+    } else if (window->type == WIN_MUC) {
+        ProfMucWin* mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+        return mucwin->has_attention;
+    }
+    return FALSE;
+}
+
+gboolean 
+win_toggle_attention(ProfWin* window) 
+{
+    if (window->type == WIN_CHAT) {
+        ProfChatWin* chatwin = (ProfChatWin*)window;
+        assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+        chatwin->has_attention = !chatwin->has_attention;
+        return chatwin->has_attention;
+    } else if (window->type == WIN_MUC) {
+        ProfMucWin* mucwin = (ProfMucWin*)window;
+        assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+        mucwin->has_attention = !mucwin->has_attention;
+        return mucwin->has_attention;
+    }
+    return FALSE;
+}
+
+
 void
 win_sub_print(WINDOW* win, char* msg, gboolean newline, gboolean wrap, int indent)
 {
diff --git a/src/ui/window_list.c b/src/ui/window_list.c
index a90c7141..d698d778 100644
--- a/src/ui/window_list.c
+++ b/src/ui/window_list.c
@@ -52,6 +52,10 @@
 #include "xmpp/roster_list.h"
 #include "tools/http_upload.h"
 
+#ifdef HAVE_OMEMO
+#include "omemo/omemo.h"
+#endif
+
 static GHashTable* windows;
 static int current;
 static Autocomplete wins_ac;
@@ -864,6 +868,24 @@ wins_reestablished_connection(void)
         if (window->type != WIN_CONSOLE) {
             win_println(window, THEME_TEXT, "-", "Connection re-established.");
 
+#ifdef HAVE_OMEMO
+            if (window->type == WIN_CHAT) {
+                ProfChatWin* chatwin = (ProfChatWin*)window;
+                assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+                if (chatwin->is_omemo) {
+                    win_println(window, THEME_TEXT, "-", "Restarted OMEMO session.");
+                    omemo_start_session(chatwin->barejid);
+                }
+            } else if (window->type == WIN_MUC) {
+                ProfMucWin* mucwin = (ProfMucWin*)window;
+                assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+                if (mucwin->is_omemo) {
+                    win_println(window, THEME_TEXT, "-", "Restarted OMEMO session.");
+                    omemo_start_muc_sessions(mucwin->roomjid);
+                }
+            }
+#endif
+
             // if current win, set current_win_dirty
             if (wins_is_current(window)) {
                 win_update_virtual(window);
@@ -1102,6 +1124,51 @@ wins_create_summary(gboolean unread)
     return result;
 }
 
+GSList*
+wins_create_summary_attention()
+{
+    GSList* result = NULL;
+
+    GList* keys = g_hash_table_get_keys(windows);
+    keys = g_list_sort(keys, _wins_cmp_num);
+    GList* curr = keys;
+
+    while (curr) {
+        ProfWin* window = g_hash_table_lookup(windows, curr->data);
+        gboolean has_attention = FALSE;
+        if (window->type == WIN_CHAT) {
+            ProfChatWin* chatwin = (ProfChatWin*)window;
+            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
+            has_attention = chatwin->has_attention;
+        } else if (window->type == WIN_MUC) {
+            ProfMucWin* mucwin = (ProfMucWin*)window;
+            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
+            has_attention = mucwin->has_attention;
+        }
+        if (has_attention) {
+            GString* line = g_string_new("");
+
+            int ui_index = GPOINTER_TO_INT(curr->data);
+            char* winstring = win_to_string(window);
+            if (!winstring) {
+                g_string_free(line, TRUE);
+                continue;
+            }
+
+            g_string_append_printf(line, "%d: %s", ui_index, winstring);
+            free(winstring);
+
+            result = g_slist_append(result, strdup(line->str));
+            g_string_free(line, TRUE);
+        }
+        curr = g_list_next(curr);
+    }
+
+    g_list_free(keys);
+
+    return result;
+}
+
 char*
 win_autocomplete(const char* const search_str, gboolean previous, void* context)
 {
@@ -1138,21 +1205,69 @@ ProfWin*
 wins_get_next_unread(void)
 {
     // get and sort win nums
+    GList* values = g_hash_table_get_keys(windows);
+    values = g_list_sort(values, _wins_cmp_num);
+    GList* curr = values;
+
+    while (curr) {
+        int curr_win_num = GPOINTER_TO_INT(curr->data); 
+        ProfWin* window = wins_get_by_num(curr_win_num);
+
+        // test if window has unread messages
+        if (win_unread(window) > 0) {
+            g_list_free(values);
+            return window;
+        }
+
+        curr = g_list_next(curr);
+    }
+
+    g_list_free(values);
+    return NULL;
+}
+
+ProfWin*
+wins_get_next_attention(void)
+{
+    // get and sort win nums
     GList* values = g_hash_table_get_values(windows);
     values = g_list_sort(values, _wins_cmp_num);
     GList* curr = values;
 
+    ProfWin* current_window = wins_get_by_num(current);
+
+    // search the current window
     while (curr) {
-        if (current == GPOINTER_TO_INT(curr->data)) {
+        ProfWin* window = curr->data;
+        if (current_window == window) {
+            current_window = window;
+            curr = g_list_next(curr);
             break;
         }
+        curr = g_list_next(curr);
+    }
 
+    // Start from current window
+    while (current_window && curr) {
         ProfWin* window = curr->data;
-        if (win_unread(window) > 0) {
+        if (win_has_attention(window)) {
+            g_list_free(values);
+            return window;
+        }
+        curr = g_list_next(curr);
+    }
+    // Start from begin
+    curr = values;
+    while (current_window && curr) {
+        ProfWin* window = curr->data;
+        if (current_window == window) {
+            // we are at current again
+            break;
+        }
+        if (win_has_attention(window)) {
             g_list_free(values);
             return window;
         }
-
         curr = g_list_next(curr);
     }
 
diff --git a/src/ui/window_list.h b/src/ui/window_list.h
index e02358f9..06ae6d7e 100644
--- a/src/ui/window_list.h
+++ b/src/ui/window_list.h
@@ -74,6 +74,7 @@ ProfWin* wins_get_by_string(const char* str);
 ProfWin* wins_get_next(void);
 ProfWin* wins_get_previous(void);
 ProfWin* wins_get_next_unread(void);
+ProfWin* wins_get_next_attention(void);
 int wins_get_num(ProfWin* window);
 int wins_get_current_num(void);
 void wins_close_by_num(int i);
@@ -87,6 +88,7 @@ void wins_lost_connection(void);
 void wins_reestablished_connection(void);
 gboolean wins_tidy(void);
 GSList* wins_create_summary(gboolean unread);
+GSList* wins_create_summary_attention();
 void wins_destroy(void);
 GList* wins_get_nums(void);
 void wins_swap(int source_win, int target_win);
diff --git a/src/xmpp/bookmark.c b/src/xmpp/bookmark.c
index 65c3bd01..4e40e3ec 100644
--- a/src/xmpp/bookmark.c
+++ b/src/xmpp/bookmark.c
@@ -225,6 +225,13 @@ bookmark_remove(const char* jid)
     return TRUE;
 }
 
+Bookmark*
+bookmark_get_by_jid(const char* jid)
+{
+    Bookmark* bookmark = g_hash_table_lookup(bookmarks, jid);
+    return bookmark;
+}
+
 GList*
 bookmark_get_list(void)
 {
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index beecb97e..19b620e2 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -2308,6 +2308,8 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
         GSList* features = NULL;
         while (child) {
             const char* stanza_name = xmpp_stanza_get_name(child);
+            const char* child_type = xmpp_stanza_get_type(child);
+
             if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) {
                 const char* var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR);
                 if (var) {
@@ -2315,10 +2317,9 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
                 }
             } else if (g_strcmp0(stanza_name, STANZA_NAME_IDENTITY) == 0) {
                 const char* name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
-                const char* type = xmpp_stanza_get_type(child);
                 const char* category = xmpp_stanza_get_attribute(child, STANZA_ATTR_CATEGORY);
 
-                if (name || category || type) {
+                if (name || category || child_type) {
                     DiscoIdentity* identity = malloc(sizeof(struct disco_identity_t));
 
                     if (identity) {
@@ -2332,8 +2333,8 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
                         } else {
                             identity->category = NULL;
                         }
-                        if (type) {
-                            identity->type = strdup(type);
+                        if (child_type) {
+                            identity->type = strdup(child_type);
                         } else {
                             identity->type = NULL;
                         }
@@ -2341,6 +2342,10 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat
                         identities = g_slist_append(identities, identity);
                     }
                 }
+            } else if (g_strcmp0(child_type, STANZA_TYPE_RESULT) == 0) {
+                GHashTable *adr = stanza_get_service_contact_addresses(connection_get_ctx(), child);
+                cons_show_disco_contact_information(adr);
+                g_hash_table_destroy(adr);
             }
 
             child = xmpp_stanza_get_next(child);
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 4f093bcf..e14ae07d 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -93,6 +93,7 @@ static void _send_message_stanza(xmpp_stanza_t* const stanza);
 static gboolean _handle_mam(xmpp_stanza_t* const stanza);
 static void _handle_pubsub(xmpp_stanza_t* const stanza, xmpp_stanza_t* const event);
 static gboolean _handle_form(xmpp_stanza_t* const stanza);
+static gboolean _handle_jingle_message(xmpp_stanza_t* const stanza);
 
 #ifdef HAVE_LIBGPGME
 static xmpp_stanza_t* _openpgp_signcrypt(xmpp_ctx_t* ctx, const char* const to, const char* const text);
@@ -167,9 +168,14 @@ _message_handler(xmpp_conn_t* const conn, xmpp_stanza_t* const stanza, void* con
         _handle_groupchat(stanza);
     } else if (type && g_strcmp0(type, STANZA_TYPE_HEADLINE) == 0) {
         _handle_headline(stanza);
-    } else if (type == NULL || g_strcmp0(type, STANZA_TYPE_CHAT) != 0 || g_strcmp0(type, STANZA_TYPE_NORMAL) != 0) {
+    } else if (type == NULL || g_strcmp0(type, STANZA_TYPE_CHAT) == 0 || g_strcmp0(type, STANZA_TYPE_NORMAL) == 0) {
         // type: chat, normal (==NULL)
 
+        // XEP-0353: Jingle Message Initiation
+        if (_handle_jingle_message(stanza)) {
+            return 1;
+        }
+
         // XEP-0045: Multi-User Chat 8.6 Voice Requests
         if (_handle_form(stanza)) {
             return 1;
@@ -624,6 +630,7 @@ message_send_chat_omemo(const char* const jid, uint32_t sid, GList* keys,
     xmpp_stanza_t* header = xmpp_stanza_new(ctx);
     xmpp_stanza_set_name(header, "header");
     char* sid_text = g_strdup_printf("%d", sid);
+    log_debug("[OMEMO] Sending from device sid %s", sid_text);
     xmpp_stanza_set_attribute(header, "sid", sid_text);
     g_free(sid_text);
 
@@ -634,6 +641,7 @@ message_send_chat_omemo(const char* const jid, uint32_t sid, GList* keys,
         xmpp_stanza_t* key_stanza = xmpp_stanza_new(ctx);
         xmpp_stanza_set_name(key_stanza, "key");
         char* rid = g_strdup_printf("%d", key->device_id);
+        log_debug("[OMEMO] Sending to device rid %s", rid == NULL ? "NULL" : rid );
         xmpp_stanza_set_attribute(key_stanza, "rid", rid);
         g_free(rid);
         if (key->prekey) {
@@ -1084,7 +1092,7 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
 #endif
 
     if (!message->plain && !message->body) {
-        log_error("Message received without body for room: %s", from_jid->str);
+        log_info("Message received without body for room: %s", from_jid->str);
         goto out;
     } else if (!message->plain) {
         message->plain = strdup(message->body);
@@ -1242,7 +1250,7 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza)
     message->body = xmpp_message_get_body(stanza);
 
     if (!message->plain && !message->body) {
-        log_error("Message received without body from: %s", message->from_jid->str);
+        log_info("Message received without body from: %s", message->from_jid->str);
         goto out;
     } else if (!message->plain) {
         message->plain = strdup(message->body);
@@ -1662,3 +1670,20 @@ message_request_voice(const char* const roomjid)
     _send_message_stanza(stanza);
     xmpp_stanza_release(stanza);
 }
+
+static gboolean
+_handle_jingle_message(xmpp_stanza_t* const stanza)
+{
+    xmpp_stanza_t* propose = xmpp_stanza_get_child_by_name_and_ns(stanza, STANZA_NAME_PROPOSE, STANZA_NS_JINGLE_MESSAGE);
+
+    if (propose) {
+        xmpp_stanza_t* description = xmpp_stanza_get_child_by_ns(propose, STANZA_NS_JINGLE_RTP);
+        if (description) {
+            const char* const from = xmpp_stanza_get_from(stanza);
+            cons_show("Ring ring: %s is trying to call you", from);
+            cons_alert(NULL);
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c
index be81ba62..54cd12ad 100644
--- a/src/xmpp/omemo.c
+++ b/src/xmpp/omemo.c
@@ -51,6 +51,8 @@ static int _omemo_bundle_publish_result(xmpp_stanza_t* const stanza, void* const
 static int _omemo_bundle_publish_configure(xmpp_stanza_t* const stanza, void* const userdata);
 static int _omemo_bundle_publish_configure_result(xmpp_stanza_t* const stanza, void* const userdata);
 
+static int _omemo_device_list_publish_result(xmpp_stanza_t* const stanza, void* const userdata);
+
 void
 omemo_devicelist_subscribe(void)
 {
@@ -65,12 +67,14 @@ omemo_devicelist_publish(GList* device_list)
     xmpp_ctx_t* const ctx = connection_get_ctx();
     xmpp_stanza_t* iq = stanza_create_omemo_devicelist_publish(ctx, device_list);
 
-    log_info("[OMEMO] publish device list");
+    log_debug("[OMEMO] publish device list");
 
     if (connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) {
         stanza_attach_publish_options(ctx, iq, "pubsub#access_model", "open");
     }
 
+    iq_id_handler_add(xmpp_stanza_get_id(iq), _omemo_device_list_publish_result, NULL, NULL);
+
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
 }
@@ -81,7 +85,7 @@ omemo_devicelist_request(const char* const jid)
     xmpp_ctx_t* const ctx = connection_get_ctx();
     char* id = connection_create_stanza_id();
 
-    log_info("[OMEMO] request device list for jid: %s", jid);
+    log_debug("[OMEMO] request device list for jid: %s", jid);
 
     xmpp_stanza_t* iq = stanza_create_omemo_devicelist_request(ctx, id, jid);
     iq_id_handler_add(id, _omemo_receive_devicelist, NULL, NULL);
@@ -95,7 +99,7 @@ omemo_devicelist_request(const char* const jid)
 void
 omemo_bundle_publish(gboolean first)
 {
-    log_info("[OMEMO] publish own OMEMO bundle");
+    log_debug("[OMEMO] publish own OMEMO bundle");
     xmpp_ctx_t* const ctx = connection_get_ctx();
     unsigned char* identity_key = NULL;
     size_t identity_key_length;
@@ -144,7 +148,7 @@ omemo_bundle_request(const char* const jid, uint32_t device_id, ProfIqCallback f
     xmpp_ctx_t* const ctx = connection_get_ctx();
     char* id = connection_create_stanza_id();
 
-    log_info("[OMEMO] request omemo bundle (jid: %s, deivce: %d)", jid, device_id);
+    log_debug("[OMEMO] request omemo bundle (jid: %s, device: %d)", jid, device_id);
 
     xmpp_stanza_t* iq = stanza_create_omemo_bundle_request(ctx, id, jid, device_id);
     iq_id_handler_add(id, func, free_func, userdata);
@@ -164,6 +168,12 @@ omemo_start_device_session_handle_bundle(xmpp_stanza_t* const stanza, void* cons
     char* from = NULL;
 
     const char* from_attr = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    log_debug("[OMEMO] omemo_start_device_session_handle_bundle: %s", from_attr);
+
+    const char* type = xmpp_stanza_get_type(stanza);
+    if ( g_strcmp0( type, "error") == 0 ) {
+        log_error("[OMEMO] Error to get key for a device from : %s", from_attr);
+    }
 
     if (!from_attr) {
         Jid* jid = jid_create(connection_get_fulljid());
@@ -193,6 +203,7 @@ omemo_start_device_session_handle_bundle(xmpp_stanza_t* const stanza, void* cons
     }
 
     uint32_t device_id = strtoul(++device_id_str, NULL, 10);
+    log_debug("[OMEMO] omemo_start_device_session_handle_bundle: %d", device_id);
 
     xmpp_stanza_t* item = xmpp_stanza_get_child_by_name(items, "item");
     if (!item) {
@@ -510,7 +521,7 @@ _omemo_bundle_publish_result(xmpp_stanza_t* const stanza, void* const userdata)
     const char* type = xmpp_stanza_get_type(stanza);
 
     if (g_strcmp0(type, STANZA_TYPE_RESULT) == 0) {
-        log_info("[OMEMO] bundle published successfully");
+        log_debug("[OMEMO] bundle published successfully");
         return 0;
     }
 
@@ -519,12 +530,12 @@ _omemo_bundle_publish_result(xmpp_stanza_t* const stanza, void* const userdata)
         return 0;
     }
 
-    log_info("[OMEMO] cannot publish bundle with open access model, trying to configure node");
+    log_debug("[OMEMO] cannot publish bundle with open access model, trying to configure node");
     xmpp_ctx_t* const ctx = connection_get_ctx();
     Jid* jid = jid_create(connection_get_fulljid());
     char* id = connection_create_stanza_id();
     char* node = g_strdup_printf("%s:%d", STANZA_NS_OMEMO_BUNDLES, omemo_device_id());
-    log_info("[OMEMO] node: %s", node);
+    log_debug("[OMEMO] node: %s", node);
 
     xmpp_stanza_t* iq = stanza_create_pubsub_configure_request(ctx, id, jid->barejid, node);
     g_free(node);
@@ -594,10 +605,22 @@ _omemo_bundle_publish_configure_result(xmpp_stanza_t* const stanza, void* const
         return 0;
     }
 
-    log_info("[OMEMO] node configured");
+    log_debug("[OMEMO] node configured");
 
     // Try to publish again
     omemo_bundle_publish(TRUE);
 
     return 0;
 }
+
+static int
+_omemo_device_list_publish_result(xmpp_stanza_t* const stanza, void* const userdata)
+{
+    const char* type = xmpp_stanza_get_type(stanza);
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        cons_show_error("Unable to publish own OMEMO device list");
+        log_error("[OMEMO] Publishing device list failed");
+        return 0;
+    }
+    return 0;
+}
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 235a7dee..604d4003 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -2838,3 +2838,54 @@ stanza_create_muc_register_nick(xmpp_ctx_t* ctx, const char* const id, const cha
 
     return iq;
 }
+
+static void
+_contact_addresses_list_free(GSList* list)
+{
+    if (list) {
+        g_slist_free_full(list, g_free);
+    }
+}
+
+GHashTable*
+stanza_get_service_contact_addresses(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza)
+{
+    GHashTable* addresses = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_contact_addresses_list_free);
+
+    xmpp_stanza_t* fields = xmpp_stanza_get_children(stanza);
+    while (fields) {
+        const char* child_name = xmpp_stanza_get_name(fields);
+        const char* child_type = xmpp_stanza_get_type(fields);
+
+        if (g_strcmp0(child_name, STANZA_NAME_FIELD) == 0 && g_strcmp0(child_type, STANZA_TYPE_LIST_MULTI) == 0) {
+            // extract key (eg 'admin-addresses')
+            const char* var = xmpp_stanza_get_attribute(fields, STANZA_ATTR_VAR );
+
+            // extract values (a list of contact addresses eg mailto:xmpp@shakespeare.lit, xmpp:admins@shakespeare.lit)
+            xmpp_stanza_t* values = xmpp_stanza_get_children(fields);
+            GSList* val_list = NULL;
+            while (values) {
+                const char* value_name = xmpp_stanza_get_name(values);
+                if (value_name && (g_strcmp0(value_name, STANZA_NAME_VALUE) == 0)) {
+                    char* value_text = xmpp_stanza_get_text(values);
+                    if (value_text) {
+                        val_list = g_slist_append(val_list, g_strdup(value_text));
+
+                        xmpp_free(ctx, value_text);
+                    }
+                }
+
+                values = xmpp_stanza_get_next(values);
+            }
+
+            // add to list
+            if (g_slist_length(val_list) > 0) {
+                g_hash_table_insert(addresses, g_strdup(var), val_list);
+            }
+        }
+
+        fields = xmpp_stanza_get_next(fields);
+    }
+
+    return addresses;
+}
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 06b2815a..aeddf6a2 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -121,6 +121,7 @@
 #define STANZA_NAME_LAST             "last"
 #define STANZA_NAME_AFTER            "after"
 #define STANZA_NAME_USERNAME         "username"
+#define STANZA_NAME_PROPOSE          "propose"
 
 // error conditions
 #define STANZA_NAME_BAD_REQUEST             "bad-request"
@@ -161,6 +162,7 @@
 #define STANZA_TYPE_SUBMIT       "submit"
 #define STANZA_TYPE_CANCEL       "cancel"
 #define STANZA_TYPE_MODIFY       "modify"
+#define STANZA_TYPE_LIST_MULTI   "list-multi"
 
 #define STANZA_ATTR_TO             "to"
 #define STANZA_ATTR_FROM           "from"
@@ -238,6 +240,8 @@
 #define STANZA_NS_RSM                     "http://jabber.org/protocol/rsm"
 #define STANZA_NS_REGISTER                "jabber:iq:register"
 #define STANZA_NS_VOICEREQUEST            "http://jabber.org/protocol/muc#request"
+#define STANZA_NS_JINGLE_MESSAGE          "urn:xmpp:jingle-message:0"
+#define STANZA_NS_JINGLE_RTP              "urn:xmpp:jingle:apps:rtp:1"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -385,6 +389,8 @@ char* stanza_get_muc_destroy_reason(xmpp_stanza_t* stanza);
 const char* stanza_get_actor(xmpp_stanza_t* stanza);
 char* stanza_get_reason(xmpp_stanza_t* stanza);
 
+GHashTable* stanza_get_service_contact_addresses(xmpp_ctx_t* ctx, xmpp_stanza_t* stanza);
+
 Resource* stanza_resource_from_presence(XMPPPresence* presence);
 XMPPPresence* stanza_parse_presence(xmpp_stanza_t* stanza, int* err);
 void stanza_free_presence(XMPPPresence* presence);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 4229ddae..003c3e07 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -274,6 +274,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);
 GList* bookmark_get_list(void);
+Bookmark* bookmark_get_by_jid(const char* jid);
 char* bookmark_find(const char* const search_str, gboolean previous, void* context);
 void bookmark_autocomplete_reset(void);
 gboolean bookmark_exists(const char* const room);
diff --git a/tests/unittests/omemo/stub_omemo.c b/tests/unittests/omemo/stub_omemo.c
index f6cc4491..127787ec 100644
--- a/tests/unittests/omemo/stub_omemo.c
+++ b/tests/unittests/omemo/stub_omemo.c
@@ -114,3 +114,8 @@ omemo_encrypt_file(FILE* in, FILE* out, off_t file_size, int* gcry_res)
     return NULL;
 };
 void omemo_free(void* a){};
+
+uint32_t
+omemo_device_id() {
+	return 123;
+}
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index dc36678b..1cae8bde 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -210,6 +210,20 @@ ui_win_unread(int index)
     return 0;
 }
 
+gboolean
+ui_win_has_attention(int index) {
+	return FALSE;
+}
+
+gboolean
+win_has_attention(ProfWin* window){
+	return FALSE;
+}
+
+gboolean
+win_toggle_attention(ProfWin* window){
+	return FALSE;
+}
 char*
 ui_ask_password(gboolean confirm)
 {
@@ -828,6 +842,12 @@ void
 cons_show_wins(gboolean unread)
 {
 }
+
+void 
+cons_show_wins_attention()
+{
+}
+
 void
 cons_show_status(const char* const barejid)
 {
@@ -888,6 +908,11 @@ cons_show_bookmarks(const GList* list)
 }
 
 void
+cons_show_bookmark(Bookmark* item)
+{
+}
+
+void
 cons_show_disco_items(GSList* items, const char* const jid)
 {
 }
diff --git a/tests/unittests/xmpp/stub_xmpp.c b/tests/unittests/xmpp/stub_xmpp.c
index cffaae7f..ba72024c 100644
--- a/tests/unittests/xmpp/stub_xmpp.c
+++ b/tests/unittests/xmpp/stub_xmpp.c
@@ -487,6 +487,12 @@ bookmark_get_list(void)
     return mock_ptr_type(GList*);
 }
 
+Bookmark*
+bookmark_get_by_jid(const char* jid)
+{
+    return NULL;
+}
+
 char*
 bookmark_find(const char* const search_str, gboolean previous, void* context)
 {