diff options
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) { |