Compare commits

..

2 Commits

Author SHA1 Message Date
36512d9dde fix(ui): measure dead pad space from the cursor, not the requested lines
All checks were successful
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Code Coverage (pull_request) Successful in 3m24s
CI Code / Linux (debian) (pull_request) Successful in 4m42s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m49s
CI Code / Linux (arch) (pull_request) Successful in 6m37s
_win_print_internal now calls _win_ensure_pad_capacity() with getcury() plus the estimated message height, so deriving 'dead' from lines_needed counted that lookahead reservation as dead space. Compute it from getcury(win) directly so the reclaim keys off the real cursor-vs-buffer gap.
2026-06-23 14:18:37 +03:00
e33874e6b5 fix(ui): reclaim pad by dead space instead of absolute height
All checks were successful
CI Code / Check spelling (pull_request) Successful in 17s
CI Code / Check coding style (pull_request) Successful in 31s
CI Code / Code Coverage (pull_request) Successful in 3m23s
CI Code / Linux (debian) (pull_request) Successful in 4m52s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m1s
CI Code / Linux (arch) (pull_request) Successful in 6m50s
Variant A (PAD_THRESHOLD=12000) bounds the pad but still re-enters win_redraw() from the print path on every message once the *live* buffer renders >= the threshold — reachable with a single oversized message (huge paste, narrow terminal, crafted incoming), reintroducing the per-message redraw storm.

Trigger the reclaim redraw on dead space (getcury - buffer->lines) instead of absolute pad height. After a redraw the cursor equals buffer->lines, so dead space is 0 regardless of buffer size: the reclaim can never fire while scrolling, and an oversized message no longer storms (and is shown in full rather than clipped). Dead space only accrues in a long append-only session that trimmed old entries — exactly when reclaiming is wanted.
2026-06-20 10:37:29 +00:00
34 changed files with 138 additions and 579 deletions

View File

@@ -186,22 +186,6 @@ ai_json_escape(const gchar* str)
return g_string_free(result, FALSE);
}
/* Keys emitted as fixed payload fields; custom settings must not duplicate them */
static gboolean
_is_reserved_payload_key(const gchar* key)
{
return g_strcmp0(key, "model") == 0 || g_strcmp0(key, "messages") == 0 || g_strcmp0(key, "stream") == 0;
}
/* RFC 8259 scalars (number/true/false/null) may be emitted bare in a JSON payload */
static gboolean
_is_json_bare_token(const gchar* value)
{
if (g_strcmp0(value, "true") == 0 || g_strcmp0(value, "false") == 0 || g_strcmp0(value, "null") == 0)
return TRUE;
return g_regex_match_simple("^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+-]?[0-9]+)?$", value, 0, 0);
}
/**
* Convert a 4-hex-digit sequence at @p to a 16-bit code point.
* Returns -1 on invalid hex.
@@ -408,7 +392,6 @@ ai_provider_new(const gchar* name, const gchar* api_url)
provider->models = NULL;
provider->models_fresh = FALSE;
provider->ref_count = 1;
pthread_mutex_init(&provider->settings_lock, NULL);
return provider;
}
@@ -436,7 +419,6 @@ ai_provider_unref(AIProvider* provider)
g_free(provider->project_id);
g_free(provider->default_model);
g_hash_table_destroy(provider->settings);
pthread_mutex_destroy(&provider->settings_lock);
GList* curr = provider->models;
while (curr) {
@@ -747,44 +729,31 @@ ai_get_provider_default_model(const gchar* provider_name)
return provider->default_model;
}
gboolean
void
ai_set_provider_setting(const gchar* provider_name, const gchar* setting, const gchar* value)
{
if (!provider_name || !setting)
return FALSE;
if (_is_reserved_payload_key(setting)) {
log_warning("Setting '%s' for provider '%s' rejected: reserved payload key", setting, provider_name);
return FALSE;
}
return;
AIProvider* provider = ai_get_provider(provider_name);
if (!provider) {
log_warning("Provider '%s' not found for setting '%s'", provider_name, setting);
return FALSE;
return;
}
/* settings is iterated by the request thread; mutate under settings_lock */
pthread_mutex_lock(&provider->settings_lock);
if (!provider->settings) {
provider->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
}
if (value) {
g_hash_table_insert(provider->settings, g_strdup(setting), g_strdup(value));
} else {
g_hash_table_remove(provider->settings, setting);
}
pthread_mutex_unlock(&provider->settings_lock);
if (value) {
prefs_ai_set_setting(provider_name, setting, value);
log_info("Setting '%s' for provider '%s' set to: %s", setting, provider_name, value);
} else {
g_hash_table_remove(provider->settings, setting);
prefs_ai_remove_setting(provider_name, setting);
log_info("Setting '%s' removed for provider '%s'", setting, provider_name);
}
return TRUE;
}
gchar*
@@ -797,10 +766,8 @@ ai_get_provider_setting(const gchar* provider_name, const gchar* setting)
if (!provider || !provider->settings)
return NULL;
pthread_mutex_lock(&provider->settings_lock);
gchar* value = g_strdup(g_hash_table_lookup(provider->settings, setting));
pthread_mutex_unlock(&provider->settings_lock);
return value;
gchar* value = g_hash_table_lookup(provider->settings, setting);
return g_strdup(value);
}
/* ========================================================================
@@ -1468,19 +1435,13 @@ ai_session_switch(AISession* session, const gchar* provider_name,
* This is the thread-safe variant used by _ai_request_thread() which holds
* the session lock while calling it.
*
* Custom provider settings are merged into the payload as additional JSON
* key-value pairs alongside "model" and "messages": scalar values
* (number/true/false/null) are emitted bare, anything else as a JSON string;
* reserved keys (model/messages/stream) are skipped.
*
* @param provider The AI provider (for custom settings)
* @param model The model identifier
* @param history GList of AIMessage* (must be held under session lock)
* @param prompt The new user prompt to append
* @return Newly allocated JSON string (caller must free)
*/
static gchar*
_build_json_payload_from_list(AIProvider* provider, const gchar* model, GList* history, const gchar* prompt)
_build_json_payload_from_list(const gchar* model, GList* history, const gchar* prompt)
{
GString* messages_json = g_string_new("");
GList* curr = history;
@@ -1507,50 +1468,11 @@ _build_json_payload_from_list(AIProvider* provider, const gchar* model, GList* h
g_string_append_printf(messages_json, "{\"role\":\"user\",\"content\":\"%s\"}", escaped_prompt);
auto_gchar gchar* escaped_model = ai_json_escape(model);
/* Build custom settings JSON fragment; settings is mutated on the main
* thread (/ai set custom), so iterate under settings_lock */
GString* custom_json = g_string_new("");
if (provider) {
pthread_mutex_lock(&provider->settings_lock);
if (provider->settings) {
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, provider->settings);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (_is_reserved_payload_key((gchar*)key)) {
continue; /* fixed payload fields stay authoritative */
}
auto_gchar gchar* escaped_key = ai_json_escape((gchar*)key);
if (custom_json->len > 0) {
g_string_append_c(custom_json, ',');
}
if (_is_json_bare_token((gchar*)value)) {
g_string_append_printf(custom_json, "\"%s\": %s", escaped_key, (gchar*)value);
} else {
/* non-scalar values must be quoted, else the payload is invalid JSON */
auto_gchar gchar* escaped_val = ai_json_escape((gchar*)value);
g_string_append_printf(custom_json, "\"%s\": \"%s\"", escaped_key, escaped_val);
}
}
}
pthread_mutex_unlock(&provider->settings_lock);
}
/* Assemble final payload: model, messages, custom settings, then fixed keys */
gchar* json_payload;
if (custom_json->len > 0) {
json_payload = g_strdup_printf(
"{\"model\":\"%s\",\"messages\":[%s],%s,\"stream\":false}",
escaped_model, messages_json->str, custom_json->str);
} else {
json_payload = g_strdup_printf(
"{\"model\":\"%s\",\"messages\":[%s],\"stream\":false}",
escaped_model, messages_json->str);
}
gchar* json_payload = g_strdup_printf(
"{\"model\":\"%s\",\"input\":[%s],\"stream\":false,\"store\":false}",
escaped_model, messages_json->str);
g_string_free(messages_json, TRUE);
g_string_free(custom_json, TRUE);
return json_payload;
}
@@ -1560,7 +1482,12 @@ ai_parse_response(const gchar* response_json)
if (!response_json || strlen(response_json) == 0)
return NULL;
/* OpenAI /v1/chat/completions: {"choices":[{"message":{"content":"..."}}]} */
/* Perplexity /v1/agent: {"output":[{"content":[{"text":"...","type":"output_text"}]}]}
* Legacy OpenAI: {"choices":[{"message":{"content":"..."}}]} */
gchar* text = _extract_json_string(response_json, "text");
if (text)
return text;
return _extract_json_string(response_json, "content");
}
@@ -1639,7 +1566,7 @@ _ai_request_thread(gpointer data)
/* Build JSON payload from history + prompt (under lock), then add user message to history */
log_debug("[AI-THREAD] Building JSON payload...");
pthread_mutex_lock(&session->lock);
auto_gchar gchar* json_payload = _build_json_payload_from_list(local_provider, local_model, session->history, prompt);
auto_gchar gchar* json_payload = _build_json_payload_from_list(local_model, session->history, prompt);
/* Add user message to history now (it's in JSON but not yet in history) */
AIMessage* user_msg = g_new0(AIMessage, 1);
@@ -1660,7 +1587,7 @@ _ai_request_thread(gpointer data)
/* Build request URL */
const gchar* api_url = local_provider->api_url;
auto_gchar gchar* request_url = g_strdup_printf("%s%sv1/chat/completions", api_url, g_str_has_suffix(api_url, "/") ? "" : "/");
auto_gchar gchar* request_url = g_strdup_printf("%s%sv1/responses", api_url, g_str_has_suffix(api_url, "/") ? "" : "/");
log_debug("[AI-THREAD] API URL: %s", api_url);
log_debug("[AI-THREAD] API Request URL: %s", request_url);
log_debug("[AI-THREAD] Model: %s", local_model);

View File

@@ -17,15 +17,14 @@ typedef struct ai_message_t
*/
typedef struct ai_provider_t
{
gchar* name; /* Provider name (e.g., "openai", "perplexity") */
gchar* api_url; /* API endpoint URL */
gchar* project_id; /* Optional project ID (for some providers) */
gchar* default_model; /* Default model for this provider */
pthread_mutex_t settings_lock; /* Protects settings (iterated by the request thread) */
GHashTable* settings; /* Extensible per-provider settings (e.g., temperature=0.7) */
GList* models; /* Cached models (gchar*) */
gboolean models_fresh; /* Whether models cache is current */
gint32 ref_count; /* Reference count (atomic) */
gchar* name; /* Provider name (e.g., "openai", "perplexity") */
gchar* api_url; /* API endpoint URL */
gchar* project_id; /* Optional project ID (for some providers) */
gchar* default_model; /* Default model for this provider */
GHashTable* settings; /* Extensible per-provider settings (e.g., tools=enabled, search=disabled) */
GList* models; /* Cached models (gchar*) */
gboolean models_fresh; /* Whether models cache is current */
gint32 ref_count; /* Reference count (atomic) */
} AIProvider;
/**
@@ -143,13 +142,11 @@ const gchar* ai_get_provider_default_model(const gchar* provider_name);
/**
* Set a custom setting for a provider.
* Reserved payload keys (model, messages, stream) are rejected.
* @param provider_name The provider name
* @param setting The setting key (e.g., "temperature")
* @param value The setting value, or NULL to remove
* @return TRUE on success, FALSE on unknown provider or reserved key
* @param setting The setting key (e.g., "tools", "search")
* @param value The setting value
*/
gboolean ai_set_provider_setting(const gchar* provider_name, const gchar* setting, const gchar* value);
void ai_set_provider_setting(const gchar* provider_name, const gchar* setting, const gchar* value);
/**
* Get a custom setting for a provider.
@@ -187,9 +184,9 @@ gboolean ai_models_are_fresh(const gchar* provider_name);
void ai_parse_models_from_json(AIProvider* provider, const gchar* json);
/**
* Extract assistant content from an OpenAI chat-completions response body
* ({"choices":[{"message":{"content":"..."}}]}). Decodes the \" and \n
* escape sequences only.
* Extract assistant content from an LLM response body. Tries Perplexity
* /v1/responses "text" first, falls back to OpenAI "content". Decodes
* the \" and \n escape sequences only.
* @param response_json The JSON body from the provider
* @return Newly allocated content string, or NULL if not found (caller frees)
*/

View File

@@ -42,13 +42,11 @@
#include "ai/ai_client.h"
static char* _caps_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _sub_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _notify_autocomplete(ProfWin* window, const char* const input, gboolean previous);
static char* _theme_autocomplete(ProfWin* window, const char* const input, gboolean previous);
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);
@@ -152,7 +150,6 @@ static Autocomplete account_clear_ac;
static Autocomplete account_default_ac;
static Autocomplete account_status_ac;
static Autocomplete disco_ac;
static Autocomplete caps_subcommands_ac;
static Autocomplete wins_ac;
static Autocomplete roster_ac;
static Autocomplete roster_show_ac;
@@ -323,7 +320,6 @@ static Autocomplete* all_acs[] = {
&account_default_ac,
&account_status_ac,
&disco_ac,
&caps_subcommands_ac,
&wins_ac,
&roster_ac,
&roster_show_ac,
@@ -600,8 +596,6 @@ cmd_ac_init(void)
autocomplete_add(disco_ac, "info");
autocomplete_add(disco_ac, "items");
autocomplete_add(caps_subcommands_ac, "clear");
autocomplete_add(account_ac, "list");
autocomplete_add(account_ac, "show");
autocomplete_add(account_ac, "add");
@@ -1062,7 +1056,6 @@ 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");
@@ -1414,12 +1407,10 @@ 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);
g_hash_table_insert(ac_funcs, "/bookmark", _bookmark_autocomplete);
g_hash_table_insert(ac_funcs, "/caps", _caps_autocomplete);
g_hash_table_insert(ac_funcs, "/clear", _clear_autocomplete);
g_hash_table_insert(ac_funcs, "/close", _close_autocomplete);
g_hash_table_insert(ac_funcs, "/cmd", _adhoc_cmd_autocomplete);
@@ -1943,6 +1934,7 @@ _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 },
};
@@ -1991,35 +1983,6 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
return NULL;
}
static char*
_caps_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
char* result = NULL;
result = autocomplete_param_with_ac(input, "/caps", caps_subcommands_ac, TRUE, previous);
if (result) {
return result;
}
if (window->type == WIN_MUC) {
ProfMucWin* mucwin = (ProfMucWin*)window;
assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
Autocomplete nick_ac = muc_roster_ac(mucwin->roomjid);
if (nick_ac) {
result = autocomplete_param_with_ac(input, "/caps", nick_ac, TRUE, previous);
}
} else if (connection_get_status() == JABBER_CONNECTED) {
result = autocomplete_param_with_func(input, "/caps", roster_contact_autocomplete, previous, NULL);
if (result) {
return result;
}
result = autocomplete_param_with_func(input, "/caps", roster_barejid_autocomplete, previous, NULL);
if (result) {
return result;
}
result = autocomplete_param_with_func(input, "/caps", roster_fulljid_autocomplete, previous, NULL);
}
return result;
}
static char*
_sub_autocomplete(ProfWin* window, const char* const input, gboolean previous)
{
@@ -4301,20 +4264,6 @@ _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)
{

View File

@@ -427,21 +427,17 @@ static const struct cmd_t command_defs[] = {
CMD_TAG_GROUPCHAT)
CMD_SYN(
"/caps",
"/caps <fulljid>|<nick>",
"/caps clear")
"/caps <fulljid>|<nick>")
CMD_DESC(
"Find out a contacts, or room members client software capabilities. "
"If in private chat initiated from a chat room, no parameter is required. "
"Use clear to remove all cached capabilities.")
"If in private chat initiated from a chat room, no parameter is required.")
CMD_ARGS(
{ "<fulljid>", "If in the console or a chat window, the full JID for which you wish to see capabilities." },
{ "<nick>", "If in a chat room, nickname for which you wish to see capabilities." },
{ "clear", "Clear all cached capabilities." })
{ "<nick>", "If in a chat room, nickname for which you wish to see capabilities." })
CMD_EXAMPLES(
"/caps ran@cold.sea.org/laptop",
"/caps ran@cold.sea.org/phone",
"/caps aegir",
"/caps clear")
"/caps aegir")
},
{ CMD_PREAMBLE("/software",
@@ -1968,14 +1964,12 @@ static const struct cmd_t command_defs[] = {
CMD_TAG_CONNECTION)
CMD_SYN(
"/autoping set <seconds>",
"/autoping timeout <seconds>",
"/autoping warning on|off")
"/autoping timeout <seconds>")
CMD_DESC(
"Set the interval between sending ping requests to the server to ensure the connection is kept alive.")
CMD_ARGS(
{ "set <seconds>", "Number of seconds between sending pings, a value of 0 disables autoping." },
{ "timeout <seconds>", "Seconds to wait for autoping responses, after which the connection is considered broken." },
{ "warning on|off", "Enable or disable autoping availability warning." })
{ "timeout <seconds>", "Seconds to wait for autoping responses, after which the connection is considered broken." })
},
{ CMD_PREAMBLE("/ping",
@@ -2837,7 +2831,7 @@ static const struct cmd_t command_defs[] = {
{ "set provider <name> <url>", "Add or update a provider with custom API endpoint" },
{ "set token <provider> <token>", "Set API token for a provider (e.g., openai, perplexity)" },
{ "set default-model <provider> <model>", "Set default model for a provider" },
{ "set custom <provider> <setting> <value>", "Set provider-level setting (e.g., temperature, max_tokens)" },
{ "set custom <provider> <setting> <value>", "Set provider-level setting (e.g., tools, search)" },
{ "remove provider <name>", "Remove a custom provider" },
{ "providers", "List configured providers with full details" },
{ "start <provider> [<model>]", "Start new AI chat (space-separated, uses defaults if omitted)" },
@@ -2850,7 +2844,7 @@ static const struct cmd_t command_defs[] = {
"/ai set token perplexity pplx-xxx",
"/ai set provider custom https://my-api.com/v1/chat/completions",
"/ai set default-model perplexity sonar",
"/ai set custom perplexity temperature 0.7",
"/ai set custom perplexity tools enabled",
"/ai remove provider custom",
"/ai start",
"/ai start perplexity",

View File

@@ -70,7 +70,6 @@
#include "xmpp/stanza.h"
#include "xmpp/vcard_funcs.h"
#include "xmpp/xmpp.h"
#include "xmpp/capabilities.h"
#ifdef HAVE_LIBOTR
#include "otr/otr.h"
@@ -3388,12 +3387,6 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args)
return TRUE;
}
if (g_strcmp0(args[0], "clear") == 0) {
caps_cache_clear();
cons_show("Capabilities cache cleared.");
return TRUE;
}
switch (window->type) {
case WIN_MUC:
if (args[0]) {
@@ -3412,7 +3405,6 @@ cmd_caps(ProfWin* window, const char* const command, gchar** args)
break;
case WIN_CHAT:
case WIN_CONSOLE:
case WIN_XML:
if (args[0]) {
auto_jid Jid* jid = jid_create(args[0]);
@@ -6382,8 +6374,6 @@ 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);
}
@@ -10795,11 +10785,8 @@ cmd_ai_set(ProfWin* window, const char* const command, gchar** args)
cons_bad_cmd_usage(command);
return TRUE;
}
if (ai_set_provider_setting(args[2], args[3], args[4])) {
cons_show("Setting '%s' for provider '%s' set to: %s", args[3], args[2], args[4]);
} else {
cons_show_error("Cannot set '%s' for provider '%s': unknown provider or reserved key (model, messages, stream).", args[3], args[2]);
}
ai_set_provider_setting(args[2], args[3], args[4]);
cons_show("Setting '%s' for provider '%s' set to: %s", args[3], args[2], args[4]);
cons_show("");
return TRUE;
}

View File

@@ -921,7 +921,7 @@ prof_date_time_format_iso8601(GDateTime* dt)
return ret;
}
/* build CProof version string.
/* build profanity version string.
* example: 0.13.1dev.master.69d8c1f9
*/
gchar*

View File

@@ -522,7 +522,7 @@ accounts_set_script_start(const char* const account_name, const char* const valu
void
accounts_set_client(const char* const account_name, const char* const value)
{
_accounts_set_string_option(account_name, "client.account_name", value);
_accounts_set_string_option(account_name, "client.name", value);
}
void
@@ -576,7 +576,7 @@ accounts_clear_script_start(const char* const account_name)
void
accounts_clear_client(const char* const account_name)
{
_accounts_clear_string_option(account_name, "client.account_name");
_accounts_clear_string_option(account_name, "client.name");
}
void

View File

@@ -2290,7 +2290,6 @@ _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:
@@ -2647,8 +2646,6 @@ _get_key(preference_t pref)
return "enabled";
case PREF_SPELLCHECK_LANG:
return "lang";
case PREF_AUTOPING_WARNING:
return "autoping.warning";
default:
return NULL;
}
@@ -2689,6 +2686,7 @@ _get_default_boolean(preference_t pref)
case PREF_STATUSBAR_SHOW_NUMBER:
case PREF_STATUSBAR_SHOW_READ:
case PREF_STATUSBAR_SHOW_DBBACKEND:
case PREF_REVEAL_OS:
case PREF_CORRECTION_ALLOW:
case PREF_RECEIPTS_SEND:
case PREF_CARBONS:
@@ -2701,9 +2699,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_REVEAL_OS:
case PREF_SPELLCHECK_ENABLE:
case PREF_PGP_PUBKEY_AUTOIMPORT:
case PREF_FORCE_ENCRYPTION:

View File

@@ -169,7 +169,6 @@ typedef enum {
PREF_FORCE_ENCRYPTION_MODE,
PREF_SPELLCHECK_ENABLE,
PREF_SPELLCHECK_LANG,
PREF_AUTOPING_WARNING
} preference_t;
typedef struct prof_alias_t

6
src/gitversion.h.in Normal file
View File

@@ -0,0 +1,6 @@
#ifndef PROF_GIT_BRANCH
#define PROF_GIT_BRANCH @PROF_GIT_BRANCH@
#endif
#ifndef PROF_GIT_REVISION
#define PROF_GIT_REVISION @PROF_GIT_REVISION@
#endif

View File

@@ -77,7 +77,7 @@ main(int argc, char** argv)
if (version == TRUE) {
auto_gchar gchar* prof_version = prof_get_version();
g_print("CProof, version %s\n", prof_version);
g_print("Profanity, version %s\n", prof_version);
// lets use fixed email instead of PACKAGE_BUGREPORT
g_print("Copyright (C) 2012 - 2019 James Booth <boothj5web@gmail.com>.\n");

View File

@@ -519,15 +519,13 @@ p_gpg_sign(const gchar* const str, const gchar* const fp)
}
gchar*
p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gchar* const fp, gchar** err)
p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gchar* const fp)
{
ProfPGPPubKeyId* pubkeyid = g_hash_table_lookup(pubkeys, barejid);
if (!pubkeyid) {
*err = g_strdup("No PGP key found for recipient");
return NULL;
}
if (!pubkeyid->id) {
*err = g_strdup("No key ID found for recipient");
return NULL;
}
@@ -540,7 +538,6 @@ p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gcha
gpgme_ctx_t ctx;
gpgme_error_t error = gpgme_new(&ctx);
if (error) {
*err = g_strdup_printf("Failed to create GPGME context: %s %s", gpgme_strsource(error), gpgme_strerror(error));
log_error("GPG: Failed to create gpgme context. %s %s", gpgme_strsource(error), gpgme_strerror(error));
return NULL;
}
@@ -548,7 +545,6 @@ p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gcha
gpgme_key_t receiver_key;
error = gpgme_get_key(ctx, pubkeyid->id, &receiver_key, 0);
if (error || receiver_key == NULL) {
*err = g_strdup_printf("Failed to get receiver key '%s': %s %s", pubkeyid->id, gpgme_strsource(error), gpgme_strerror(error));
log_error("GPG: Failed to get receiver_key. %s %s", gpgme_strsource(error), gpgme_strerror(error));
gpgme_release(ctx);
return NULL;
@@ -558,10 +554,8 @@ p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gcha
gpgme_key_t sender_key = NULL;
error = gpgme_get_key(ctx, fp, &sender_key, 0);
if (error || sender_key == NULL) {
*err = g_strdup_printf("Failed to get sender key '%s': %s %s", fp, gpgme_strsource(error), gpgme_strerror(error));
log_error("GPG: Failed to get sender_key. %s %s", gpgme_strsource(error), gpgme_strerror(error));
gpgme_release(ctx);
gpgme_key_unref(receiver_key);
return NULL;
}
keys[1] = sender_key;
@@ -580,8 +574,7 @@ p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gcha
gpgme_key_unref(sender_key);
if (error) {
*err = g_strdup_printf("Encryption failed: (%s) %s", gpgme_strsource(error), gpgme_strerror(error));
log_error("GPG: Failed to encrypt message. (%s) %s", gpgme_strsource(error), gpgme_strerror(error));
log_error("GPG: Failed to encrypt message. %s %s", gpgme_strsource(error), gpgme_strerror(error));
return NULL;
}

View File

@@ -42,7 +42,7 @@ gboolean p_gpg_available(const gchar* const barejid);
const gchar* p_gpg_libver(void);
gchar* p_gpg_sign(const gchar* const str, const gchar* const fp);
void p_gpg_verify(const gchar* const barejid, const gchar* const sign);
gchar* p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gchar* const fp, gchar** err);
gchar* p_gpg_encrypt(const gchar* const barejid, const gchar* const message, const gchar* const fp);
gchar* p_gpg_decrypt(const gchar* const cipher);
void p_gpg_free_decrypted(gchar* decrypted);
gchar* p_gpg_autocomplete_key(const gchar* const search_str, gboolean previous, void* context);

View File

@@ -253,7 +253,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
log_stderr_init(PROF_LEVEL_ERROR);
auto_gchar gchar* prof_version = prof_get_version();
log_info("Starting CProof (%s)…", prof_version);
log_info("Starting Profanity (%s)…", prof_version);
chatlog_init();
accounts_load();

View File

@@ -1703,11 +1703,6 @@ 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");
@@ -2008,26 +2003,21 @@ 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
@@ -2830,7 +2820,8 @@ cons_privacy_setting(void)
if (account->client) {
cons_show("Client name (/account set <account> clientid) : %s", account->client);
} else {
cons_show("Client name (/account set <account> clientid) : Pidgin");
auto_gchar gchar* prof_version = prof_get_version();
cons_show("Client name (/account set <account> clientid) : Profanity %s", prof_version);
}
if (account->max_sessions > 0) {
cons_show("Max sessions alarm (/account set <account> session_alarm) : %d", account->max_sessions);

View File

@@ -23,7 +23,6 @@
#include "plugins/plugins.h"
#include "config/files.h"
#include "config/preferences.h"
#include "xmpp/connection.h"
#include "xmpp/xmpp.h"
#include "xmpp/stanza.h"
#include "xmpp/form.h"
@@ -272,19 +271,6 @@ caps_map_jid_to_ver(const char* const jid, const char* const ver)
g_hash_table_insert(jid_to_ver, strdup(jid), strdup(ver));
}
void
caps_cache_clear(void)
{
g_hash_table_remove_all(jid_to_ver);
g_hash_table_remove_all(jid_to_caps);
if (caps_prof_keyfile.keyfile) {
g_key_file_free(caps_prof_keyfile.keyfile);
}
caps_prof_keyfile.keyfile = g_key_file_new();
cache = caps_prof_keyfile.keyfile;
save_keyfile(&caps_prof_keyfile);
}
gboolean
caps_cache_contains(const char* const ver)
{

View File

@@ -28,6 +28,5 @@ void caps_map_jid_to_ver(const char* const jid, const char* const ver);
gboolean caps_cache_contains(const char* const ver);
GList* caps_get_features(void);
char* caps_get_my_sha1(xmpp_ctx_t* const ctx);
void caps_cache_clear(void);
#endif

View File

@@ -99,7 +99,6 @@ 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);
@@ -1681,7 +1680,7 @@ _version_get_handler(xmpp_stanza_t* const stanza)
xmpp_stanza_t* name = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(name, "name");
xmpp_stanza_t* name_txt = xmpp_stanza_new(ctx);
xmpp_stanza_set_text(name_txt, is_custom_client ? client : "Pidgin");
xmpp_stanza_set_text(name_txt, is_custom_client ? client : "Profanity");
xmpp_stanza_add_child(name, name_txt);
xmpp_stanza_add_child(query, name);
bool include_os = prefs_get_boolean(PREF_REVEAL_OS) && !is_custom_client;
@@ -1690,7 +1689,7 @@ _version_get_handler(xmpp_stanza_t* const stanza)
xmpp_stanza_t* version = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(version, "version");
xmpp_stanza_t* version_txt = xmpp_stanza_new(ctx);
auto_gchar gchar* prof_version = g_strdup("");
auto_gchar gchar* prof_version = prof_get_version();
if (!is_custom_client) {
xmpp_stanza_set_text(version_txt, prof_version);
@@ -2430,12 +2429,6 @@ _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)
const char* domain = connection_get_domain();
if (from && domain && g_ascii_strcasecmp(from, domain) == 0) {
_disco_autoping_warning_message(features);
}
}
connection_features_received(from);
@@ -2443,21 +2436,6 @@ _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)
{

View File

@@ -280,13 +280,9 @@ jid_fulljid_or_barejid(Jid* jid)
gchar*
jid_random_resource(void)
{
gchar* rand = g_malloc0(29);
const gchar* digits = "0123456789";
auto_gchar gchar* rand = get_random_string(4);
for (int i = 0; i < 28; i++) {
rand[i] = digits[g_random_int_range(0, 10)];
}
rand[28] = '\0';
gchar* result = g_strdup_printf("profanity.%s", rand);
return rand;
return result;
}

View File

@@ -467,15 +467,12 @@ message_send_chat_pgp(const char* const barejid, const char* const msg, gboolean
auto_char char* jid = chat_session_get_jid(barejid);
char* id = connection_create_stanza_id();
ProfWin* current = wins_get_current();
xmpp_stanza_t* message = NULL;
#ifdef HAVE_LIBGPGME
ProfAccount* account = accounts_get_account(session_get_account_name());
if (account->pgp_keyid) {
auto_jid Jid* jidp = jid_create(jid);
auto_gchar gchar* err = NULL;
auto_gchar gchar* encrypted = p_gpg_encrypt(jidp->barejid, msg, account->pgp_keyid, &err);
auto_gchar gchar* encrypted = p_gpg_encrypt(jidp->barejid, msg, account->pgp_keyid);
if (encrypted) {
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, "This message is encrypted (XEP-0027).");
@@ -489,31 +486,18 @@ message_send_chat_pgp(const char* const barejid, const char* const msg, gboolean
xmpp_stanza_add_child(message, x);
xmpp_stanza_release(x);
} else {
if (current) {
win_println(current, THEME_ERROR, "-", "Unable to encrypt message for %s: %s.", jid, err);
}
log_error("Message not encrypted for %s: %s.", jid, err);
account_free(account);
free(id);
return NULL;
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, msg);
}
} else {
if (current) {
win_println(current, THEME_ERROR, "-", "No PGP key configured for this account.");
}
log_error("No PGP key configured, message not sent.");
account_free(account);
free(id);
return NULL;
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, msg);
}
account_free(account);
#else
if (current) {
win_println(current, THEME_ERROR, "-", "LibGPGME not available, message not sent.");
}
log_error("LibGPGME not available, message not sent.");
free(id);
return NULL;
// ?
message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
xmpp_message_set_body(message, msg);
#endif
if (state) {
@@ -562,10 +546,7 @@ message_send_chat_ox(const char* const barejid, const char* const msg, gboolean
xmpp_stanza_to_text(signcrypt, &c, &s);
char* signcrypt_e = p_ox_gpg_signcrypt(account->jid, barejid, c);
if (signcrypt_e == NULL) {
ProfWin* current = wins_get_current();
if (current) {
win_println(current, THEME_ERROR, "-", "Unable to send OX message. Check log file and profanity-ox-setup man page for details.");
}
cons_show("Unable to send OX message. Check log file and profanity-ox-setup man page for details.");
log_error("Message not signcrypted.");
return NULL;
}

View File

@@ -178,7 +178,7 @@ session_connect_with_details(const char* const jid, const char* const passwd, co
saved_details.auth_policy = NULL;
}
// use random string when no resourcepart in provided jid
// use 'profanity' when no resourcepart in provided jid
auto_jid Jid* jidp = jid_create(jid);
if (jidp->resourcepart == NULL) {
auto_gchar gchar* resource = jid_random_resource();

View File

@@ -32,7 +32,6 @@
static void _stanza_add_unique_id(xmpp_stanza_t* stanza);
static gchar* _stanza_create_sha1_hash(char* str);
static const char* _stanza_get_caps_node(const char* const client);
#if 0
xmpp_stanza_t*
@@ -931,22 +930,23 @@ stanza_create_caps_query_element(xmpp_ctx_t* ctx)
xmpp_stanza_t* identity = xmpp_stanza_new(ctx);
xmpp_stanza_set_name(identity, "identity");
xmpp_stanza_set_type(identity, "pc");
xmpp_stanza_set_attribute(identity, "category", "client");
ProfAccount* account = accounts_get_account(session_get_account_name());
gchar* client = account->client;
bool is_custom_client = client != NULL;
auto_gchar gchar* name = g_strdup(account->client ? account->client : "Pidgin");
if (account->client) {
gchar* space = strchr(name, ' ');
if (space) {
*space = '\0';
}
GString* name_str = g_string_new(is_custom_client ? client : "Profanity ");
if (!is_custom_client) {
xmpp_stanza_set_type(identity, "console");
auto_gchar gchar* prof_version = prof_get_version();
g_string_append(name_str, prof_version);
}
account_free(account);
xmpp_stanza_set_attribute(identity, "name", name);
xmpp_stanza_set_attribute(identity, "name", name_str->str);
g_string_free(name_str, TRUE);
xmpp_stanza_add_child(query, identity);
xmpp_stanza_release(identity);
@@ -1994,42 +1994,6 @@ stanza_attach_last_activity(xmpp_ctx_t* const ctx,
xmpp_stanza_release(query);
}
static const char*
_stanza_get_caps_node(const char* const client)
{
if (!client || g_strcmp0(client, "") == 0) {
return "http://pidgin.im";
}
auto_gchar gchar* lower = g_ascii_strdown(client, -1);
if (g_str_has_prefix(lower, "pidgin")) {
return "http://pidgin.im";
}
if (g_str_has_prefix(lower, "gajim")) {
return "https://gajim.org";
}
if (g_str_has_prefix(lower, "conversations")) {
return "https://conversations.im";
}
if (g_str_has_prefix(lower, "psi+")) {
return "http://psi-plus.com";
}
if (g_str_has_prefix(lower, "psi")) {
return "http://psi-im.org";
}
if (g_str_has_prefix(lower, "profanity")) {
return "http://profanity-im.github.io";
}
return "";
}
void
stanza_attach_caps(xmpp_ctx_t* const ctx, xmpp_stanza_t* const presence)
{
@@ -2038,13 +2002,9 @@ stanza_attach_caps(xmpp_ctx_t* const ctx, xmpp_stanza_t* const presence)
xmpp_stanza_set_ns(caps, STANZA_NS_CAPS);
xmpp_stanza_t* query = stanza_create_caps_query_element(ctx);
ProfAccount* account = accounts_get_account(session_get_account_name());
const char* node = _stanza_get_caps_node(account->client);
account_free(account);
char* sha1 = caps_get_my_sha1(ctx);
xmpp_stanza_set_attribute(caps, STANZA_ATTR_HASH, "sha-1");
xmpp_stanza_set_attribute(caps, STANZA_ATTR_NODE, node);
xmpp_stanza_set_attribute(caps, STANZA_ATTR_NODE, "http://profanity-im.github.io");
xmpp_stanza_set_attribute(caps, STANZA_ATTR_VER, sha1);
xmpp_stanza_add_child(presence, caps);
xmpp_stanza_release(caps);

View File

@@ -150,14 +150,6 @@ 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),

View File

@@ -601,11 +601,11 @@ prof_connect_with_roster(const char* roster)
assert_true(prof_output_regex(".+online.+ \\(priority 0\\)\\."));
expect_timeout = EXPECT_TIMEOUT_CONNECT;
// Wait for presence stanza to be sent
// Wait for presence stanza to be sent (content-based, not ID-based)
// Match the actual attribute order from stanza_attach_caps
assert_true(stbbr_received(
"<presence id=\"*\">"
"<c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"*\" ver=\"*\"/>"
"<c xmlns=\"http://jabber.org/protocol/caps\" hash=\"sha-1\" node=\"http://profanity-im.github.io\" ver=\"*\"/>"
"</presence>"));
}

View File

@@ -112,144 +112,3 @@ 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",
"<iq to='stabber@localhost/profanity' type='result' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='Stabber'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
} else {
stbbr_for_query("http://jabber.org/protocol/disco#info",
"<iq to='stabber@localhost/profanity' type='result' from='localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='server' type='im' name='Stabber'/>"
"</query>"
"</iq>"
);
}
}
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",
"<iq to='stabber@localhost/profanity' type='result' from='buddy1@localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='person' type='chat' name='Buddy1'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
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",
"<iq to='stabber@localhost/profanity' type='result' from='conf.localhost'>"
"<query xmlns='http://jabber.org/protocol/disco#info'>"
"<identity category='conference' type='text' name='Conference Service'/>"
"<feature var='urn:xmpp:ping'/>"
"</query>"
"</iq>"
);
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));
}

View File

@@ -4,9 +4,3 @@ 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);

View File

@@ -25,7 +25,7 @@ connect_jid_sends_presence_after_receiving_roster(void **state)
assert_true(stbbr_received(
"<presence id='*'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}

View File

@@ -1289,7 +1289,7 @@ _join_muc(const char* room)
snprintf(presence, sizeof(presence),
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='%s/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' "
"node='*' ver='*'/>"
"node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"

View File

@@ -26,7 +26,7 @@ sends_room_join(void **state)
assert_true(stbbr_received(
"<presence id='*' to='testroom@conference.localhost/stabber'>"
"<x xmlns='http://jabber.org/protocol/muc'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -41,7 +41,7 @@ sends_room_join_with_nick(void **state)
assert_true(stbbr_received(
"<presence id='*' to='testroom@conference.localhost/testnick'>"
"<x xmlns='http://jabber.org/protocol/muc'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -58,7 +58,7 @@ sends_room_join_with_password(void **state)
"<x xmlns='http://jabber.org/protocol/muc'>"
"<password>testpassword</password>"
"</x>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -75,7 +75,7 @@ sends_room_join_with_nick_and_password(void **state)
"<x xmlns='http://jabber.org/protocol/muc'>"
"<password>testpassword</password>"
"</x>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -87,7 +87,7 @@ shows_role_and_affiliation_on_join(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -107,7 +107,7 @@ shows_subject_on_join(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -135,7 +135,7 @@ shows_history_message(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -170,7 +170,7 @@ shows_occupant_join(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -200,7 +200,7 @@ shows_message(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -227,7 +227,7 @@ shows_me_message_from_occupant(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -254,7 +254,7 @@ shows_me_message_from_self(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -281,7 +281,7 @@ shows_all_messages_in_console_when_window_not_focussed(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -322,7 +322,7 @@ shows_first_message_in_console_when_window_not_focussed(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -369,7 +369,7 @@ shows_no_message_in_console_when_window_not_focussed(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='participant' jid='stabber@localhost/profanity' affiliation='none'/>"
"</x>"
@@ -401,7 +401,7 @@ sends_affiliation_list_request(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='owner'/>"
"</x>"
@@ -434,7 +434,7 @@ sends_kick_request(void **state)
stbbr_for_presence_to("testroom@conference.localhost/stabber",
"<presence id='*' lang='en' to='stabber@localhost/profanity' from='testroom@conference.localhost/stabber'>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='*' ver='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' node='http://profanity-im.github.io' ver='*'/>"
"<x xmlns='http://jabber.org/protocol/muc#user'>"
"<item role='moderator' jid='stabber@localhost/profanity' affiliation='admin'/>"
"</x>"

View File

@@ -19,7 +19,7 @@ presence_online(void **state)
assert_true(stbbr_received(
"<presence id='*'>"
"<status>online</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -36,7 +36,7 @@ presence_online_with_message(void **state)
assert_true(stbbr_received(
"<presence id='*'>"
"<status>Hi there</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -54,7 +54,7 @@ presence_away(void **state)
"<presence id='*'>"
"<show>away</show>"
"<status>away</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -72,7 +72,7 @@ presence_away_with_message(void **state)
"<presence id='*'>"
"<show>away</show>"
"<status>I'm not here for a bit</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -90,7 +90,7 @@ presence_xa(void **state)
"<presence id='*'>"
"<show>xa</show>"
"<status>xa</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -108,7 +108,7 @@ presence_xa_with_message(void **state)
"<presence id='*'>"
"<show>xa</show>"
"<status>Gone to the shops</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -126,7 +126,7 @@ presence_dnd(void **state)
"<presence id='*'>"
"<show>dnd</show>"
"<status>dnd</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -144,7 +144,7 @@ presence_dnd_with_message(void **state)
"<presence id='*'>"
"<show>dnd</show>"
"<status>Working</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -162,7 +162,7 @@ presence_chat(void **state)
"<presence id='*'>"
"<show>chat</show>"
"<status>chat</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -180,7 +180,7 @@ presence_chat_with_message(void **state)
"<presence id='*'>"
"<show>chat</show>"
"<status>Free to talk</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -197,7 +197,7 @@ presence_set_priority(void **state)
assert_true(stbbr_received(
"<presence id='*'>"
"<priority>25</priority>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -212,7 +212,7 @@ presence_includes_priority(void **state)
assert_true(stbbr_received(
"<presence id='*'>"
"<priority>25</priority>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
@@ -223,7 +223,7 @@ presence_includes_priority(void **state)
"<priority>25</priority>"
"<show>chat</show>"
"<status>Free to talk</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}
@@ -239,7 +239,7 @@ presence_keeps_status(void **state)
"<presence id='*'>"
"<show>chat</show>"
"<status>Free to talk</status>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
@@ -250,7 +250,7 @@ presence_keeps_status(void **state)
"<show>chat</show>"
"<status>Free to talk</status>"
"<priority>25</priority>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='*'/>"
"<c hash='sha-1' xmlns='http://jabber.org/protocol/caps' ver='*' node='http://profanity-im.github.io'/>"
"</presence>"
));
}

View File

@@ -614,24 +614,6 @@ test_ai_set_provider_setting(void** state)
assert_null(ai_get_provider_setting("p_set_settings", "nonexistent_setting"));
}
void
test_ai_set_provider_setting_reserved_key(void** state)
{
ai_add_provider("p_reserved_keys", "https://example.test/rk/");
/* Reserved payload keys are rejected (would duplicate fixed fields) */
assert_false(ai_set_provider_setting("p_reserved_keys", "model", "x"));
assert_false(ai_set_provider_setting("p_reserved_keys", "messages", "[]"));
assert_false(ai_set_provider_setting("p_reserved_keys", "stream", "true"));
assert_null(ai_get_provider_setting("p_reserved_keys", "model"));
/* Ordinary keys still work */
assert_true(ai_set_provider_setting("p_reserved_keys", "temperature", "0.7"));
auto_gchar gchar* temp = ai_get_provider_setting("p_reserved_keys", "temperature");
assert_non_null(temp);
assert_string_equal("0.7", temp);
}
void
test_ai_get_provider_setting(void** state)
{
@@ -937,22 +919,22 @@ test_ai_parse_response_openai_content(void** state)
}
void
test_ai_parse_response_legacy_text_format_unsupported(void** state)
test_ai_parse_response_perplexity_text(void** state)
{
/* Legacy Perplexity /v1/agent format: "content" is an array, not a string.
* Dropped with the chat-completions alignment, so parsing yields NULL. */
const gchar* json = "{\"output\":[{\"content\":[{\"text\":\"Search result\",\"type\":\"output_text\"}]}]}";
assert_null(ai_parse_response(json));
auto_gchar gchar* out = ai_parse_response(json);
assert_non_null(out);
assert_string_equal("Search result", out);
}
void
test_ai_parse_response_content_preferred_over_text(void** state)
test_ai_parse_response_text_preferred_over_content(void** state)
{
/* Chat-completions "content" is extracted; a stray "text" field is ignored. */
/* When both formats are present, "text" wins (Perplexity path tried first). */
const gchar* json = "{\"text\":\"from text field\",\"content\":\"from content field\"}";
auto_gchar gchar* out = ai_parse_response(json);
assert_non_null(out);
assert_string_equal("from content field", out);
assert_string_equal("from text field", out);
}
void

View File

@@ -43,7 +43,6 @@ void test_ai_providers_find_case_insensitive(void** state);
void test_ai_set_provider_default_model(void** state);
void test_ai_get_provider_default_model(void** state);
void test_ai_set_provider_setting(void** state);
void test_ai_set_provider_setting_reserved_key(void** state);
void test_ai_get_provider_setting(void** state);
/* Model caching tests */
void test_ai_models_are_fresh_initial(void** state);
@@ -65,8 +64,8 @@ int ai_client_teardown_with_prefs(void** state);
/* Chat response parser tests (ai_parse_response) */
void test_ai_parse_response_openai_content(void** state);
void test_ai_parse_response_legacy_text_format_unsupported(void** state);
void test_ai_parse_response_content_preferred_over_text(void** state);
void test_ai_parse_response_perplexity_text(void** state);
void test_ai_parse_response_text_preferred_over_content(void** state);
void test_ai_parse_response_escaped_quote(void** state);
void test_ai_parse_response_newline_escape(void** state);
void test_ai_parse_response_empty_content(void** state);

View File

@@ -752,7 +752,6 @@ main(int argc, char* argv[])
cmocka_unit_test_setup_teardown(test_ai_set_provider_default_model, ai_client_setup, ai_client_teardown),
cmocka_unit_test_setup_teardown(test_ai_get_provider_default_model, ai_client_setup, ai_client_teardown),
cmocka_unit_test_setup_teardown(test_ai_set_provider_setting, ai_client_setup, ai_client_teardown),
cmocka_unit_test_setup_teardown(test_ai_set_provider_setting_reserved_key, ai_client_setup, ai_client_teardown),
cmocka_unit_test_setup_teardown(test_ai_get_provider_setting, ai_client_setup, ai_client_teardown),
/* Model caching tests */
cmocka_unit_test_setup_teardown(test_ai_models_are_fresh_initial, ai_client_setup, ai_client_teardown),
@@ -775,8 +774,8 @@ main(int argc, char* argv[])
cmocka_unit_test(test_ai_json_escape_backslash_quote),
/* Chat response parser */
cmocka_unit_test(test_ai_parse_response_openai_content),
cmocka_unit_test(test_ai_parse_response_legacy_text_format_unsupported),
cmocka_unit_test(test_ai_parse_response_content_preferred_over_text),
cmocka_unit_test(test_ai_parse_response_perplexity_text),
cmocka_unit_test(test_ai_parse_response_text_preferred_over_content),
cmocka_unit_test(test_ai_parse_response_escaped_quote),
cmocka_unit_test(test_ai_parse_response_newline_escape),
cmocka_unit_test(test_ai_parse_response_empty_content),

View File

@@ -500,11 +500,6 @@ caps_jid_has_feature(const char* const jid, const char* const feature)
return FALSE;
}
void
caps_cache_clear(void)
{
}
gboolean
bookmark_add(const char* jid, const char* nick, const char* password, const char* autojoin_str, const char* name)
{