diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c index d7949bad..99c2ac36 100644 --- a/src/command/cmd_ac.c +++ b/src/command/cmd_ac.c @@ -47,6 +47,7 @@ static char* _notify_autocomplete(ProfWin* window, const char* const input, gboo static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _spellcheck_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _autoaway_autocomplete(ProfWin* window, const char* const input, gboolean previous); +static char* _autoping_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _autoconnect_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _account_autocomplete(ProfWin* window, const char* const input, gboolean previous); static char* _who_autocomplete(ProfWin* window, const char* const input, gboolean previous); @@ -1056,6 +1057,7 @@ cmd_ac_init(void) autocomplete_add(autoping_ac, "set"); autocomplete_add(autoping_ac, "timeout"); + autocomplete_add(autoping_ac, "warning"); autocomplete_add(plugins_ac, "install"); autocomplete_add(plugins_ac, "update"); @@ -1407,6 +1409,7 @@ cmd_ac_init(void) g_hash_table_insert(ac_funcs, "/alias", _alias_autocomplete); g_hash_table_insert(ac_funcs, "/autoaway", _autoaway_autocomplete); g_hash_table_insert(ac_funcs, "/autoconnect", _autoconnect_autocomplete); + g_hash_table_insert(ac_funcs, "/autoping", _autoping_autocomplete); g_hash_table_insert(ac_funcs, "/avatar", _avatar_autocomplete); g_hash_table_insert(ac_funcs, "/ban", _ban_autocomplete); g_hash_table_insert(ac_funcs, "/blocked", _blocked_autocomplete); @@ -1934,7 +1937,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ { "/prefs", prefs_ac }, { "/disco", disco_ac }, { "/room", room_ac }, - { "/autoping", autoping_ac }, { "/mainwin", winpos_ac }, { "/inputwin", winpos_ac }, }; @@ -4264,6 +4266,20 @@ _executable_autocomplete(ProfWin* window, const char* const input, gboolean prev return result; } +static char* +_autoping_autocomplete(ProfWin* window, const char* const input, gboolean previous) +{ + char* result = NULL; + + result = autocomplete_param_with_ac(input, "/autoping", autoping_ac, TRUE, previous); + if (result) { + return result; + } + + result = autocomplete_param_with_func(input, "/autoping warning", prefs_autocomplete_boolean_choice, previous, NULL); + return result; +} + static char* _lastactivity_autocomplete(ProfWin* window, const char* const input, gboolean previous) { diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c index e3e26ddf..ccd9f1da 100644 --- a/src/command/cmd_defs.c +++ b/src/command/cmd_defs.c @@ -1964,12 +1964,14 @@ static const struct cmd_t command_defs[] = { CMD_TAG_CONNECTION) CMD_SYN( "/autoping set ", - "/autoping timeout ") + "/autoping timeout ", + "/autoping warning on|off") CMD_DESC( "Set the interval between sending ping requests to the server to ensure the connection is kept alive.") CMD_ARGS( { "set ", "Number of seconds between sending pings, a value of 0 disables autoping." }, - { "timeout ", "Seconds to wait for autoping responses, after which the connection is considered broken." }) + { "timeout ", "Seconds to wait for autoping responses, after which the connection is considered broken." }, + { "warning on|off", "Enable or disable autoping availability warning." }) }, { CMD_PREAMBLE("/ping", diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 3fad000a..7fbc5f05 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -6374,6 +6374,8 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args) cons_bad_cmd_usage(command); } + } else if (g_strcmp0(cmd, "warning") == 0) { + _cmd_set_boolean_preference(value, "Autoping availability warning", PREF_AUTOPING_WARNING); } else { cons_bad_cmd_usage(command); } diff --git a/src/config/preferences.c b/src/config/preferences.c index 9f9db2e8..04d3dda5 100644 --- a/src/config/preferences.c +++ b/src/config/preferences.c @@ -2290,6 +2290,7 @@ _get_group(preference_t pref) case PREF_TRAY: case PREF_TRAY_READ: case PREF_ADV_NOTIFY_DISCO_OR_VERSION: + case PREF_AUTOPING_WARNING: return PREF_GROUP_NOTIFICATIONS; case PREF_DBLOG: case PREF_CHLOG: @@ -2646,6 +2647,8 @@ _get_key(preference_t pref) return "enabled"; case PREF_SPELLCHECK_LANG: return "lang"; + case PREF_AUTOPING_WARNING: + return "autoping.warning"; default: return NULL; } @@ -2699,6 +2702,7 @@ _get_default_boolean(preference_t pref) case PREF_MOOD: case PREF_STROPHE_SM_ENABLED: case PREF_STROPHE_SM_RESEND: + case PREF_AUTOPING_WARNING: return TRUE; case PREF_SPELLCHECK_ENABLE: case PREF_PGP_PUBKEY_AUTOIMPORT: diff --git a/src/config/preferences.h b/src/config/preferences.h index 962e607b..4e422a05 100644 --- a/src/config/preferences.h +++ b/src/config/preferences.h @@ -169,6 +169,7 @@ typedef enum { PREF_FORCE_ENCRYPTION_MODE, PREF_SPELLCHECK_ENABLE, PREF_SPELLCHECK_LANG, + PREF_AUTOPING_WARNING } preference_t; typedef struct prof_alias_t diff --git a/src/ui/console.c b/src/ui/console.c index a98577a7..de565d38 100644 --- a/src/ui/console.c +++ b/src/ui/console.c @@ -1703,6 +1703,11 @@ cons_notify_setting(void) else cons_show("Subscription requests (/notify sub) : OFF"); + if (prefs_get_boolean(PREF_AUTOPING_WARNING)) + cons_show("Autoping warning (/autoping warning): ON"); + else + cons_show("Autoping warning (/autoping warning): OFF"); + gint remind_period = prefs_get_notify_remind(); if (remind_period == 0) { cons_show("Reminder period (/notify remind) : OFF"); @@ -2003,21 +2008,26 @@ cons_autoping_setting(void) { gint autoping_interval = prefs_get_autoping(); if (autoping_interval == 0) { - cons_show("Autoping interval (/autoping) : OFF"); + cons_show("Autoping interval (/autoping) : OFF"); } else if (autoping_interval == 1) { - cons_show("Autoping interval (/autoping) : 1 second"); + cons_show("Autoping interval (/autoping) : 1 second"); } else { - cons_show("Autoping interval (/autoping) : %d seconds", autoping_interval); + cons_show("Autoping interval (/autoping) : %d seconds", autoping_interval); } gint autoping_timeout = prefs_get_autoping_timeout(); if (autoping_timeout == 0) { - cons_show("Autoping timeout (/autoping) : OFF"); + cons_show("Autoping timeout (/autoping) : OFF"); } else if (autoping_timeout == 1) { - cons_show("Autoping timeout (/autoping) : 1 second"); + cons_show("Autoping timeout (/autoping) : 1 second"); } else { - cons_show("Autoping timeout (/autoping) : %d seconds", autoping_timeout); + cons_show("Autoping timeout (/autoping) : %d seconds", autoping_timeout); } + + if (prefs_get_boolean(PREF_AUTOPING_WARNING)) + cons_show("Autoping warning (/autoping warning): ON"); + else + cons_show("Autoping warning (/autoping warning): OFF"); } void diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 377f444c..c3a2c13d 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -99,6 +99,7 @@ static void _disco_items_result_handler(xmpp_stanza_t* const stanza); static void _last_activity_get_handler(xmpp_stanza_t* const stanza); static void _version_get_handler(xmpp_stanza_t* const stanza); static void _ping_get_handler(xmpp_stanza_t* const stanza); +static void _disco_autoping_warning_message(GHashTable* features); static int _version_result_id_handler(xmpp_stanza_t* const stanza, void* const userdata); static int _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdata); @@ -2429,6 +2430,11 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con } child = xmpp_stanza_get_next(child); } + + // Prevent repetitions by avoiding checks of disco items (from connection_set_disco_items) + if (from && g_ascii_strcasecmp(from, connection_get_domain()) == 0) { + _disco_autoping_warning_message(features); + } } connection_features_received(from); @@ -2436,6 +2442,21 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con return 0; } +static void +_disco_autoping_warning_message(GHashTable* features) +{ + gboolean server_supports_ping = g_hash_table_contains(features, "urn:xmpp:ping"); + gboolean user_prefers_warning = prefs_get_boolean(PREF_AUTOPING_WARNING); + gboolean is_autoping_enabled = prefs_get_autoping() != 0; + + if (!is_autoping_enabled && server_supports_ping && user_prefers_warning) { + cons_show("This server supports XEP-0199: XMPP Ping (better keepalive detection),\n" + "but autoping feature is currently disabled in settings.\n" + "Consider enabling it (e.g., `/autoping set 30`) for improved connection stability.\n" + "Use `/autoping warning off` to disable this message."); + } +} + static int _http_upload_response_id_handler(xmpp_stanza_t* const stanza, void* const userdata) { diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c index 8102f928..394b31d9 100644 --- a/tests/functionaltests/functionaltests.c +++ b/tests/functionaltests/functionaltests.c @@ -150,6 +150,14 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(autoping_sends_ping_after_interval), PROF_FUNC_TEST(autoping_server_not_supporting_ping), + /* Autoping availability warning - negative cases wait ~3s */ + PROF_FUNC_TEST(autoping_warning_shown_when_disabled), + PROF_FUNC_TEST(autoping_warning_not_shown_when_server_unsupported), + PROF_FUNC_TEST(autoping_warning_not_shown_when_autoping_enabled), + PROF_FUNC_TEST(autoping_warning_not_shown_when_user_disabled), + PROF_FUNC_TEST(autoping_warning_not_shown_for_user_jid), + PROF_FUNC_TEST(autoping_warning_not_shown_for_subdomain_service), + /* Service Discovery - XEP-0030 */ PROF_FUNC_TEST(disco_info_shows_identity), PROF_FUNC_TEST(disco_info_shows_features), diff --git a/tests/functionaltests/test_autoping.c b/tests/functionaltests/test_autoping.c index 1e1a1332..c8853af1 100644 --- a/tests/functionaltests/test_autoping.c +++ b/tests/functionaltests/test_autoping.c @@ -112,3 +112,144 @@ autoping_server_not_supporting_ping(void** state) // Should show error about ping not being supported assert_true(prof_output_regex("Server ping not supported")); } + +/* + * Autoping availability warning. + * + * The warning fires from the on-connect disco handler, so the three inputs + * (server ping support, autoping interval, warning preference) must all be + * set before prof_connect(). + */ + +/* Stable substring of the warning text emitted by iq.c. */ +#define AUTOPING_WARNING_MATCH "This server supports XEP-0199" + +static void +_stub_server_disco(gboolean with_ping) +{ + if (with_ping) { + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" + "" + "" + "" + "" + "" + ); + } else { + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" + "" + "" + "" + "" + ); + } +} + +void +autoping_warning_shown_when_disabled(void** state) +{ + // All conditions met: server supports ping, autoping off, warning on (default). + _stub_server_disco(TRUE); + + prof_input("/autoping set 0"); + assert_true(prof_output_exact("Autoping disabled.")); + + prof_connect(); + + assert_true(prof_output_regex(AUTOPING_WARNING_MATCH)); +} + +void +autoping_warning_not_shown_when_server_unsupported(void** state) +{ + // Server does not advertise urn:xmpp:ping -> no warning even with autoping off. + _stub_server_disco(FALSE); + + prof_input("/autoping set 0"); + assert_true(prof_output_exact("Autoping disabled.")); + + prof_connect(); + + prof_timeout(NEGATIVE_ASSERT_TIMEOUT); + assert_false(prof_output_regex(AUTOPING_WARNING_MATCH)); +} + +void +autoping_warning_not_shown_when_autoping_enabled(void** state) +{ + // Warning is suppressed while autoping is enabled. + _stub_server_disco(TRUE); + + prof_input("/autoping set 30"); + assert_true(prof_output_exact("Autoping interval set to 30 seconds.")); + + prof_connect(); + + prof_timeout(NEGATIVE_ASSERT_TIMEOUT); + assert_false(prof_output_regex(AUTOPING_WARNING_MATCH)); +} + +void +autoping_warning_not_shown_when_user_disabled(void** state) +{ + // User opted out -> no warning. + _stub_server_disco(TRUE); + + prof_input("/autoping set 0"); + assert_true(prof_output_exact("Autoping disabled.")); + + prof_input("/autoping warning off"); + assert_true(prof_output_exact("Autoping availability warning disabled.")); + + prof_connect(); + + prof_timeout(NEGATIVE_ASSERT_TIMEOUT); + assert_false(prof_output_regex(AUTOPING_WARNING_MATCH)); +} + +void +autoping_warning_not_shown_for_user_jid(void** state) +{ + // disco#info from a user JID (with '@') should not trigger the warning, + // only server responses (no '@') should. + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" + "" + "" + "" + "" + "" + ); + + prof_input("/autoping set 0"); + assert_true(prof_output_exact("Autoping disabled.")); + + prof_connect(); + + prof_timeout(NEGATIVE_ASSERT_TIMEOUT); + assert_false(prof_output_regex(AUTOPING_WARNING_MATCH)); +} + +void +autoping_warning_not_shown_for_subdomain_service(void** state) +{ + // disco#info from a subdomain service should not trigger the warning, only the server's own domain should + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" + "" + "" + "" + "" + "" + ); + + prof_input("/autoping set 0"); + assert_true(prof_output_exact("Autoping disabled.")); + + prof_connect(); + + prof_timeout(NEGATIVE_ASSERT_TIMEOUT); + assert_false(prof_output_regex(AUTOPING_WARNING_MATCH)); +} diff --git a/tests/functionaltests/test_autoping.h b/tests/functionaltests/test_autoping.h index 9456a50d..3226f9ee 100644 --- a/tests/functionaltests/test_autoping.h +++ b/tests/functionaltests/test_autoping.h @@ -4,3 +4,9 @@ void autoping_timeout_set(void** state); void autoping_timeout_zero_disables(void** state); void autoping_sends_ping_after_interval(void** state); void autoping_server_not_supporting_ping(void** state); +void autoping_warning_shown_when_disabled(void** state); +void autoping_warning_not_shown_when_server_unsupported(void** state); +void autoping_warning_not_shown_when_autoping_enabled(void** state); +void autoping_warning_not_shown_when_user_disabled(void** state); +void autoping_warning_not_shown_for_user_jid(void** state); +void autoping_warning_not_shown_for_subdomain_service(void** state);