Compare commits
17 Commits
1c0f39db54
...
build/auto
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ef9395882 | |||
|
a3e23c3514
|
|||
|
ad294cdd6d
|
|||
|
e1ce7ac630
|
|||
|
5ed8ec30f0
|
|||
|
|
091ff41c06
|
||
|
|
b7afea7ec3
|
||
|
7fcc14fdea
|
|||
|
d07ca9953d
|
|||
|
f636090fd9
|
|||
|
a5eab3cad5
|
|||
|
6871a4d46f
|
|||
|
f824cde45f
|
|||
|
40b7a12543
|
|||
|
6a394ae2e5
|
|||
|
43bab6ff8f
|
|||
|
d5d5dc5180
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
flavor: [arch, debian, fedora, ubuntu]
|
||||
flavor: [arch, debian, ubuntu]
|
||||
|
||||
name: Linux
|
||||
steps:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,6 +44,9 @@ src/gitversion.h.in
|
||||
src/stamp-h1
|
||||
src/plugins/profapi.lo
|
||||
|
||||
# clangd
|
||||
.cache/
|
||||
|
||||
# out-of-tree build folders
|
||||
build*/
|
||||
|
||||
|
||||
@@ -1865,7 +1865,7 @@ MAN_LINKS = NO
|
||||
# captures the structure of the code including all documentation.
|
||||
# The default value is: NO.
|
||||
|
||||
GENERATE_XML = NO
|
||||
GENERATE_XML = YES
|
||||
|
||||
# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
|
||||
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
|
||||
|
||||
@@ -1 +1 @@
|
||||
sphinx-apidoc -f -o . src && make html
|
||||
sphinx-apidoc -f -o . src && make xml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_PREREQ([2.72])
|
||||
AC_INIT([profanity],[0.15.0],[jubalh@iodoru.org],[profanity],[https://profanity-im.github.io/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
@@ -95,7 +95,7 @@ PKG_CHECK_MODULES([curl], [libcurl >= 7.62.0], [],
|
||||
PKG_CHECK_MODULES([SQLITE], [sqlite3 >= 3.22.0], [],
|
||||
[AC_MSG_ERROR([sqlite3 3.22.0 or higher is required])])
|
||||
|
||||
ACX_PTHREAD([], [AC_MSG_ERROR([pthread is required])])
|
||||
AX_PTHREAD([],[AC_MSG_ERROR([pthread is required])])
|
||||
AS_IF([test "x$PTHREAD_CC" != x], [ CC="$PTHREAD_CC" ])
|
||||
|
||||
### plugins
|
||||
|
||||
53
prof.supp
53
prof.supp
@@ -5,7 +5,7 @@
|
||||
#
|
||||
# This file is a combination of our own rules and
|
||||
# * the glib2 suppressions file that usually can be found at /usr/share/glib-2.0/valgrind/glib.supp
|
||||
# * python suppressions file from http://svn.python.org/projects/python/trunk/Misc/valgrind-python.supp
|
||||
# * python suppressions file from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
|
||||
#
|
||||
|
||||
{
|
||||
@@ -1557,6 +1557,10 @@
|
||||
...
|
||||
fun:xdg_mime_init
|
||||
}
|
||||
|
||||
# Python Valgrind .supp start
|
||||
# taken from https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
|
||||
|
||||
#
|
||||
# This is a valgrind suppression file that should be used when using valgrind.
|
||||
#
|
||||
@@ -1654,6 +1658,49 @@
|
||||
fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
|
||||
}
|
||||
|
||||
#
|
||||
# Leaks: dlopen() called without dlclose()
|
||||
#
|
||||
|
||||
{
|
||||
dlopen() called without dlclose()
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:malloc
|
||||
fun:strdup
|
||||
fun:_dl_load_cache_lookup
|
||||
}
|
||||
{
|
||||
dlopen() called without dlclose()
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:malloc
|
||||
fun:strdup
|
||||
fun:_dl_map_object
|
||||
}
|
||||
{
|
||||
dlopen() called without dlclose()
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:*
|
||||
fun:_dl_new_object
|
||||
}
|
||||
{
|
||||
dlopen() called without dlclose()
|
||||
Memcheck:Leak
|
||||
fun:calloc
|
||||
fun:*
|
||||
fun:_dl_new_object
|
||||
}
|
||||
{
|
||||
dlopen() called without dlclose()
|
||||
Memcheck:Leak
|
||||
fun:calloc
|
||||
fun:*
|
||||
fun:_dl_check_map_versions
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Non-python specific leaks
|
||||
#
|
||||
@@ -2048,6 +2095,10 @@
|
||||
fun:PyUnicode_FSConverter
|
||||
}
|
||||
|
||||
# Python Valgrind .supp end
|
||||
|
||||
|
||||
|
||||
# Actual GTK things
|
||||
{
|
||||
GtkWidgetClass action GPtrArray
|
||||
|
||||
@@ -1715,8 +1715,15 @@ cmd_help(ProfWin* window, const char* const command, gchar** args)
|
||||
gboolean
|
||||
cmd_about(ProfWin* window, const char* const command, gchar** args)
|
||||
{
|
||||
ProfWin* console = wins_get_console();
|
||||
cons_show("");
|
||||
cons_about();
|
||||
win_println(console, THEME_DEFAULT, "-", "Licensed under GNU GPL v3.");
|
||||
win_println(console, THEME_DEFAULT, "-", "CProof is an independent unaffiliated fork of Profanity.");
|
||||
win_println(console, THEME_DEFAULT, "-", "Profanity: Copyright (C) 2012–2019 James Booth, 2019–2025 Michael Vetter");
|
||||
win_println(console, THEME_DEFAULT, "-", "Copyright (C) 2025 CProof Developers");
|
||||
win_println(console, THEME_DEFAULT, "-", "");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -5233,54 +5240,43 @@ cmd_console(ProfWin* window, const char* const command, gchar** args)
|
||||
gboolean
|
||||
cmd_presence(ProfWin* window, const char* const command, gchar** args)
|
||||
{
|
||||
if (strcmp(args[0], "console") != 0 && strcmp(args[0], "chat") != 0 && strcmp(args[0], "room") != 0 && strcmp(args[0], "titlebar") != 0) {
|
||||
cons_bad_cmd_usage(command);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (strcmp(args[0], "titlebar") == 0) {
|
||||
_cmd_set_boolean_preference(args[1], "Contact presence", PREF_PRESENCE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (strcmp(args[1], "all") != 0 && strcmp(args[1], "online") != 0 && strcmp(args[1], "none") != 0) {
|
||||
const struct presence_preferences
|
||||
{
|
||||
preference_t pref;
|
||||
const char *name, *description;
|
||||
} presence_prefs[] = {
|
||||
{ .pref = PREF_STATUSES_CONSOLE, .name = "console", .description = "the console" },
|
||||
{ .pref = PREF_STATUSES_CHAT, .name = "chat", .description = "chat windows" },
|
||||
{ .pref = PREF_STATUSES_MUC, .name = "room", .description = "chat room windows" },
|
||||
};
|
||||
size_t n = 0;
|
||||
for (; n < ARRAY_SIZE(presence_prefs); ++n) {
|
||||
const struct presence_preferences* pp = &presence_prefs[n];
|
||||
if (g_strcmp0(args[0], pp->name) != 0)
|
||||
continue;
|
||||
if (strcmp(args[1], "all") == 0) {
|
||||
cons_show("All presence updates will appear in %s.", pp->description);
|
||||
} else if (strcmp(args[1], "online") == 0) {
|
||||
cons_show("Only %s presence updates will appear in %s.",
|
||||
pp->pref == PREF_STATUSES_MUC ? "join/leave" : "online/offline",
|
||||
pp->description);
|
||||
} else if (strcmp(args[1], "none") == 0) {
|
||||
cons_show("Presence updates will not appear in %s.", pp->description);
|
||||
} else {
|
||||
cons_bad_cmd_usage(command);
|
||||
return TRUE;
|
||||
}
|
||||
prefs_set_string(pp->pref, args[1]);
|
||||
break;
|
||||
}
|
||||
if (n == ARRAY_SIZE(presence_prefs)) {
|
||||
cons_bad_cmd_usage(command);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (strcmp(args[0], "console") == 0) {
|
||||
prefs_set_string(PREF_STATUSES_CONSOLE, args[1]);
|
||||
if (strcmp(args[1], "all") == 0) {
|
||||
cons_show("All presence updates will appear in the console.");
|
||||
} else if (strcmp(args[1], "online") == 0) {
|
||||
cons_show("Only online/offline presence updates will appear in the console.");
|
||||
} else {
|
||||
cons_show("Presence updates will not appear in the console.");
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(args[0], "chat") == 0) {
|
||||
prefs_set_string(PREF_STATUSES_CHAT, args[1]);
|
||||
if (strcmp(args[1], "all") == 0) {
|
||||
cons_show("All presence updates will appear in chat windows.");
|
||||
} else if (strcmp(args[1], "online") == 0) {
|
||||
cons_show("Only online/offline presence updates will appear in chat windows.");
|
||||
} else {
|
||||
cons_show("Presence updates will not appear in chat windows.");
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(args[0], "room") == 0) {
|
||||
prefs_set_string(PREF_STATUSES_MUC, args[1]);
|
||||
if (strcmp(args[1], "all") == 0) {
|
||||
cons_show("All presence updates will appear in chat room windows.");
|
||||
} else if (strcmp(args[1], "online") == 0) {
|
||||
cons_show("Only join/leave presence updates will appear in chat room windows.");
|
||||
} else {
|
||||
cons_show("Presence updates will not appear in chat room windows.");
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -5314,7 +5310,8 @@ cmd_time(ProfWin* window, const char* const command, gchar** args)
|
||||
};
|
||||
gboolean redraw = FALSE;
|
||||
gboolean set_all = g_strcmp0(args[0], "all") == 0;
|
||||
for (size_t n = 0; n < ARRAY_SIZE(time_prefs); ++n) {
|
||||
size_t n = 0;
|
||||
for (; n < ARRAY_SIZE(time_prefs); ++n) {
|
||||
if (!set_all && g_strcmp0(args[0], time_prefs[n].name) != 0)
|
||||
continue;
|
||||
const struct time_preferences* tp = &time_prefs[n];
|
||||
@@ -5355,6 +5352,9 @@ cmd_time(ProfWin* window, const char* const command, gchar** args)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
if (!set_all && n == ARRAY_SIZE(time_prefs)) {
|
||||
cons_bad_cmd_usage(command);
|
||||
}
|
||||
if (redraw)
|
||||
ui_redraw();
|
||||
return TRUE;
|
||||
@@ -9527,16 +9527,7 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args)
|
||||
gboolean
|
||||
cmd_editor(ProfWin* window, const char* const command, gchar** args)
|
||||
{
|
||||
auto_gchar gchar* message = NULL;
|
||||
|
||||
if (get_message_from_editor(NULL, &message)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
rl_insert_text(message);
|
||||
ui_resize();
|
||||
rl_point = rl_end;
|
||||
rl_forced_update_display();
|
||||
get_message_from_editor_async(NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ typedef enum {
|
||||
RESOURCE_XA
|
||||
} resource_presence_t;
|
||||
|
||||
extern gboolean background_mode;
|
||||
|
||||
gboolean string_to_verbosity(const char* cmd, int* verbosity, gchar** err_msg);
|
||||
|
||||
gboolean create_dir(const char* name);
|
||||
|
||||
@@ -86,9 +86,9 @@ _theme_close(void)
|
||||
void
|
||||
theme_init(const char* const theme_name)
|
||||
{
|
||||
if(defaults || theme_loc || theme) {
|
||||
if (defaults || theme_loc || theme) {
|
||||
log_warning("theme_init(): Detected non-NULL state (theme=%p, theme_loc=%p, defaults=%p). Cleaning up previous initialization.",
|
||||
theme, theme_loc, defaults);
|
||||
theme, theme_loc, defaults);
|
||||
_theme_close();
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ out:
|
||||
void
|
||||
log_database_close(void)
|
||||
{
|
||||
log_debug("log_database_close() called");
|
||||
if (g_chatlog_database) {
|
||||
sqlite3_close(g_chatlog_database);
|
||||
sqlite3_shutdown();
|
||||
@@ -318,19 +319,25 @@ log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_las
|
||||
// Query previous chats, constraints start_time and end_time. If end_time is
|
||||
// null the current time is used. from_start gets first few messages if true
|
||||
// otherwise the last ones. Flip flips the order of the results
|
||||
GSList*
|
||||
log_database_get_previous_chat(const gchar* const contact_barejid, const char* start_time, char* end_time, gboolean from_start, gboolean flip)
|
||||
db_history_result_t
|
||||
log_database_get_previous_chat(const gchar* const contact_barejid, const gchar* start_time, const gchar* end_time, gboolean from_start, gboolean flip, GSList** result)
|
||||
{
|
||||
if (!g_chatlog_database) {
|
||||
log_warning("log_database_get_previous_chat() called but db is not initialized");
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
const Jid* myjid = connection_get_jid();
|
||||
if (!myjid->str)
|
||||
return NULL;
|
||||
if (!myjid->str) {
|
||||
log_warning("log_database_get_previous_chat() called but no connection detected.");
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
|
||||
// Flip order when querying older pages
|
||||
gchar* sort1 = from_start ? "ASC" : "DESC";
|
||||
gchar* sort2 = !flip ? "ASC" : "DESC";
|
||||
GDateTime* now = g_date_time_new_now_local();
|
||||
auto_gchar gchar* end_date_fmt = end_time ? end_time : g_date_time_format_iso8601(now);
|
||||
auto_gchar gchar* end_date_fmt = end_time ? g_strdup(end_time) : g_date_time_format_iso8601(now);
|
||||
auto_sqlite gchar* query = sqlite3_mprintf("SELECT * FROM ("
|
||||
"SELECT COALESCE(B.`message`, A.`message`) AS message, "
|
||||
"A.`timestamp`, A.`from_jid`, A.`from_resource`, A.`to_jid`, A.`to_resource`, A.`type`, A.`encryption`, A.`stanza_id` FROM `ChatLogs` AS A "
|
||||
@@ -346,18 +353,16 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s
|
||||
g_date_time_unref(now);
|
||||
|
||||
if (!query) {
|
||||
log_error("Could not allocate memory");
|
||||
return NULL;
|
||||
log_error("Could not allocate memory.");
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_chatlog_database, query, -1, &stmt, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
log_error("SQLite error in log_database_get_previous_chat(): %s", sqlite3_errmsg(g_chatlog_database));
|
||||
return NULL;
|
||||
log_error("SQLite error in log_database_get_previous_chat(): (error code: %d) %s", rc, sqlite3_errmsg(g_chatlog_database));
|
||||
return DB_RESPONSE_ERROR;
|
||||
}
|
||||
|
||||
GSList* history = NULL;
|
||||
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
char* message = (char*)sqlite3_column_text(stmt, 0);
|
||||
char* date = (char*)sqlite3_column_text(stmt, 1);
|
||||
@@ -378,11 +383,11 @@ log_database_get_previous_chat(const gchar* const contact_barejid, const char* s
|
||||
msg->type = _get_message_type_type(type);
|
||||
msg->enc = _get_message_enc_type(encryption);
|
||||
|
||||
history = g_slist_append(history, msg);
|
||||
*result = g_slist_append(*result, msg);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return history;
|
||||
return g_slist_length(*result) != 0 ? DB_RESPONSE_SUCCESS : DB_RESPONSE_EMPTY;
|
||||
}
|
||||
|
||||
static const char*
|
||||
|
||||
@@ -40,14 +40,20 @@
|
||||
#include "config/account.h"
|
||||
#include "xmpp/xmpp.h"
|
||||
|
||||
#define MESSAGES_TO_RETRIEVE 10
|
||||
#define MESSAGES_TO_RETRIEVE 100
|
||||
|
||||
typedef enum {
|
||||
DB_RESPONSE_ERROR,
|
||||
DB_RESPONSE_EMPTY,
|
||||
DB_RESPONSE_SUCCESS
|
||||
} db_history_result_t;
|
||||
|
||||
gboolean log_database_init(ProfAccount* account);
|
||||
void log_database_add_incoming(ProfMessage* message);
|
||||
void log_database_add_outgoing_chat(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
|
||||
void log_database_add_outgoing_muc(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
|
||||
void log_database_add_outgoing_muc_pm(const char* const id, const char* const barejid, const char* const message, const char* const replace_id, prof_enc_t enc);
|
||||
GSList* log_database_get_previous_chat(const gchar* const contact_barejid, const char* start_time, char* end_time, gboolean from_start, gboolean flip);
|
||||
db_history_result_t log_database_get_previous_chat(const gchar* const contact_barejid, const gchar* start_time, const gchar* end_time, gboolean from_start, gboolean flip, GSList** result);
|
||||
ProfMessage* log_database_get_limits_info(const gchar* const contact_barejid, gboolean is_last);
|
||||
void log_database_close(void);
|
||||
|
||||
|
||||
@@ -664,7 +664,8 @@ p_gpg_decrypt(const char* const cipher)
|
||||
}
|
||||
gpgme_key_unref(key);
|
||||
} else if (error) {
|
||||
log_warning("GPGME error: %s", gpgme_strerror(error));
|
||||
// Emails are used only to fetch PGP recipients for debug logging. If something went wrong here, it's not worthy of warning.
|
||||
log_debug("GPGME error on gpgme_get_key(): %s", gpgme_strerror(error));
|
||||
}
|
||||
|
||||
if (recipient->next) {
|
||||
|
||||
@@ -900,6 +900,8 @@ _python_type_error(ProfPlugin* plugin, char* hook, char* type)
|
||||
g_string_free(err_msg, TRUE);
|
||||
}
|
||||
|
||||
// Converts a Python string or None result to a C string and decreases the Python object's reference count.
|
||||
// If the result is NULL, or not a string/unicode/bytes/None, logs an error and returns NULL.
|
||||
static char*
|
||||
_handle_string_or_none_result(ProfPlugin* plugin, PyObject* result, char* hook)
|
||||
{
|
||||
@@ -910,18 +912,16 @@ _handle_string_or_none_result(ProfPlugin* plugin, PyObject* result, char* hook)
|
||||
}
|
||||
#ifdef PY_IS_PYTHON3
|
||||
if (result != Py_None && !PyUnicode_Check(result) && !PyBytes_Check(result)) {
|
||||
allow_python_threads();
|
||||
_python_type_error(plugin, hook, "string, unicode or None");
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (result != Py_None && !PyUnicode_Check(result) && !PyString_Check(result)) {
|
||||
#endif
|
||||
Py_XDECREF(result);
|
||||
allow_python_threads();
|
||||
_python_type_error(plugin, hook, "string, unicode or None");
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
char* result_str = python_str_or_unicode_to_string(result);
|
||||
Py_XDECREF(result);
|
||||
allow_python_threads();
|
||||
return result_str;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include "config/tlscerts.h"
|
||||
#include "config/scripts.h"
|
||||
#include "command/cmd_defs.h"
|
||||
#include "tools/editor.h"
|
||||
#include "plugins/plugins.h"
|
||||
#include "event/client_events.h"
|
||||
#include "ui/ui.h"
|
||||
@@ -93,6 +94,9 @@ static void _connect_default(const char* const account);
|
||||
pthread_mutex_t lock;
|
||||
static gboolean force_quit = FALSE;
|
||||
|
||||
// main.c (prof_run section)
|
||||
gboolean background_mode = FALSE;
|
||||
|
||||
void
|
||||
prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_file, gchar* theme_name, gchar** commands)
|
||||
{
|
||||
@@ -118,42 +122,44 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
|
||||
log_stderr_handler();
|
||||
session_check_autoaway();
|
||||
|
||||
line = commands ? *commands : inp_readline();
|
||||
if (commands && line && memcmp(line, "/sleep", 6) == 0) {
|
||||
if (!g_timer_is_active(waittimer)) {
|
||||
gchar* err_msg;
|
||||
if (strtoi_range(line + 7, &waittime, 0, 300, &err_msg)) {
|
||||
g_timer_start(waittimer);
|
||||
/* Increase the minimal runtime by the waiting time
|
||||
* so we can be sure there's runtime left after executing
|
||||
* the last command.
|
||||
*/
|
||||
min_runtime += waittime;
|
||||
} else {
|
||||
log_error(err_msg);
|
||||
g_free(err_msg);
|
||||
commands = NULL;
|
||||
if (!background_mode) {
|
||||
line = commands ? *commands : inp_readline();
|
||||
if (commands && line && memcmp(line, "/sleep", 6) == 0) {
|
||||
if (!g_timer_is_active(waittimer)) {
|
||||
gchar* err_msg;
|
||||
if (strtoi_range(line + 7, &waittime, 0, 300, &err_msg)) {
|
||||
g_timer_start(waittimer);
|
||||
/* Increase the minimal runtime by the waiting time
|
||||
* so we can be sure there's runtime left after executing
|
||||
* the last command.
|
||||
*/
|
||||
min_runtime += waittime;
|
||||
} else {
|
||||
log_error(err_msg);
|
||||
g_free(err_msg);
|
||||
commands = NULL;
|
||||
}
|
||||
} else if (g_timer_elapsed(waittimer, NULL) >= waittime) {
|
||||
g_timer_stop(waittimer);
|
||||
commands++;
|
||||
if (!(*commands))
|
||||
commands = NULL;
|
||||
}
|
||||
cont = TRUE;
|
||||
} else if (line) {
|
||||
ProfWin* window = wins_get_current();
|
||||
cont = cmd_process_input(window, line);
|
||||
if (commands) {
|
||||
commands++;
|
||||
if (!(*commands))
|
||||
commands = NULL;
|
||||
} else {
|
||||
free(line);
|
||||
line = NULL;
|
||||
}
|
||||
} else if (g_timer_elapsed(waittimer, NULL) >= waittime) {
|
||||
g_timer_stop(waittimer);
|
||||
commands++;
|
||||
if (!(*commands))
|
||||
commands = NULL;
|
||||
}
|
||||
cont = TRUE;
|
||||
} else if (line) {
|
||||
ProfWin* window = wins_get_current();
|
||||
cont = cmd_process_input(window, line);
|
||||
if (commands) {
|
||||
commands++;
|
||||
if (!(*commands))
|
||||
commands = NULL;
|
||||
} else {
|
||||
free(line);
|
||||
line = NULL;
|
||||
cont = TRUE;
|
||||
}
|
||||
} else {
|
||||
cont = TRUE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBOTR
|
||||
@@ -163,7 +169,12 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
|
||||
notify_remind();
|
||||
session_process_events();
|
||||
iq_autoping_check();
|
||||
ui_update();
|
||||
|
||||
editor_process(wins_get_current());
|
||||
|
||||
if (!background_mode) {
|
||||
ui_update();
|
||||
}
|
||||
#ifdef HAVE_GTK
|
||||
tray_update();
|
||||
#endif
|
||||
|
||||
@@ -140,6 +140,9 @@ aesgcm_file_get(void* userdata)
|
||||
"Downloading '%s' failed: Failed to decrypt "
|
||||
"file (%s).",
|
||||
https_url, gcry_strerror(crypt_res));
|
||||
} else {
|
||||
http_print_transfer_update(aesgcm_dl->window, aesgcm_dl->id,
|
||||
"Decrypted file saved to '%s'", aesgcm_dl->filename);
|
||||
}
|
||||
|
||||
if (aesgcm_dl->cmd_template != NULL) {
|
||||
|
||||
@@ -38,15 +38,200 @@
|
||||
#include <fcntl.h>
|
||||
#include <glib.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h> // necessary for readline
|
||||
#include <readline/readline.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "config/files.h"
|
||||
#include "config/preferences.h"
|
||||
#include "editor.h"
|
||||
#include "log.h"
|
||||
#include "common.h"
|
||||
#include "ncurses.h"
|
||||
#include "ui/ui.h"
|
||||
#include "xmpp/xmpp.h"
|
||||
|
||||
EditorTask editor_task = { 0, FALSE, NULL, NULL, PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL };
|
||||
|
||||
void*
|
||||
editor_thread(void* arg)
|
||||
{
|
||||
EditorTask* task = (EditorTask*)arg;
|
||||
auto_gcharv gchar** argv = g_strsplit(task->editor_cmd, " ", 0);
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
execvp(argv[0], argv);
|
||||
log_error("[Editor] Failed to exec %s", argv[0]);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&task->mutex);
|
||||
if (pid > 0) {
|
||||
task->pid = pid;
|
||||
int status;
|
||||
// Unlock mutex to avoid deadlock on the main loop
|
||||
pthread_mutex_unlock(&task->mutex);
|
||||
waitpid(pid, &status, 0);
|
||||
pthread_mutex_lock(&task->mutex);
|
||||
|
||||
gsize length;
|
||||
if (!g_file_get_contents(task->filename, &task->result, &length, &task->error)) {
|
||||
log_error("[Editor] could not read from %s: %s", task->filename,
|
||||
task->error ? task->error->message : "No GLib error given");
|
||||
task->result = g_strdup(""); // Ensure valid result
|
||||
} else {
|
||||
g_strchomp(task->result);
|
||||
if (remove(task->filename) != 0) {
|
||||
log_error("[Editor] error during file deletion of %s", task->filename);
|
||||
} else {
|
||||
log_debug("[Editor] deleted file: %s", task->filename);
|
||||
}
|
||||
}
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
task->error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Editor process failed with status %d", WEXITSTATUS(status));
|
||||
}
|
||||
} else {
|
||||
task->error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED, "Fork failed: %s", strerror(errno));
|
||||
task->result = g_strdup(""); // Ensure valid result
|
||||
}
|
||||
|
||||
task->active = FALSE;
|
||||
pthread_mutex_unlock(&task->mutex);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns true if an error occurred
|
||||
gboolean
|
||||
get_message_from_editor_async(gchar* message)
|
||||
{
|
||||
// Check if editor is already running
|
||||
pthread_mutex_lock(&editor_task.mutex);
|
||||
if (editor_task.active) {
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
log_error("[Editor] Editor already running");
|
||||
return TRUE;
|
||||
}
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
|
||||
// Create temporary file
|
||||
auto_gchar gchar* filename = NULL;
|
||||
GError* glib_error = NULL;
|
||||
const char* jid = connection_get_barejid();
|
||||
if (jid) {
|
||||
filename = files_file_in_account_data_path(DIR_EDITOR, jid, "compose.md");
|
||||
} else {
|
||||
log_debug("[Editor] could not get JID");
|
||||
auto_gchar gchar* data_dir = files_get_data_path(DIR_EDITOR);
|
||||
if (!create_dir(data_dir)) {
|
||||
log_error("[Editor] could not create directory %s", data_dir);
|
||||
return TRUE;
|
||||
}
|
||||
filename = g_strdup_printf("%s/compose.md", data_dir);
|
||||
}
|
||||
if (!filename) {
|
||||
log_error("[Editor] something went wrong while creating compose file");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Write message to file
|
||||
gsize messagelen = message ? strlen(message) : 0;
|
||||
if (!g_file_set_contents(filename, message ? message : "", messagelen, &glib_error)) {
|
||||
log_error("[Editor] could not write to %s: %s", filename,
|
||||
glib_error ? glib_error->message : "No GLib error given");
|
||||
if (glib_error) {
|
||||
g_error_free(glib_error);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Prepare editor command
|
||||
auto_gchar gchar* editor = prefs_get_string(PREF_COMPOSE_EDITOR);
|
||||
auto_gchar gchar* editor_with_filename = g_strdup_printf("%s %s", editor, filename);
|
||||
|
||||
// Suspend NCurses
|
||||
def_prog_mode();
|
||||
endwin();
|
||||
|
||||
// Initialize thread task
|
||||
pthread_mutex_lock(&editor_task.mutex);
|
||||
editor_task.active = TRUE;
|
||||
editor_task.result = NULL;
|
||||
editor_task.error = NULL;
|
||||
g_free(editor_task.filename);
|
||||
g_free(editor_task.editor_cmd);
|
||||
editor_task.filename = g_strdup(filename);
|
||||
editor_task.editor_cmd = g_strdup(editor_with_filename);
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
|
||||
// Set background mode
|
||||
background_mode = TRUE;
|
||||
log_debug("[Editor] Entering background mode");
|
||||
|
||||
// Start editor thread
|
||||
if (pthread_create(&editor_task.thread, NULL, editor_thread, &editor_task) != 0) {
|
||||
pthread_mutex_lock(&editor_task.mutex);
|
||||
editor_task.active = FALSE;
|
||||
editor_task.error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
||||
"Failed to create editor thread: %s", strerror(errno));
|
||||
g_free(editor_task.filename);
|
||||
g_free(editor_task.editor_cmd);
|
||||
editor_task.filename = NULL;
|
||||
editor_task.editor_cmd = NULL;
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
background_mode = FALSE;
|
||||
log_debug("[Editor] Exiting background mode");
|
||||
reset_prog_mode();
|
||||
doupdate();
|
||||
log_error("[Editor] Failed to create editor thread");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
editor_process(ProfWin* window)
|
||||
{
|
||||
pthread_mutex_lock(&editor_task.mutex);
|
||||
if (editor_task.active) {
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
return;
|
||||
}
|
||||
if (!editor_task.result) {
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
g_strchomp(editor_task.result);
|
||||
rl_replace_line(editor_task.result, 0);
|
||||
rl_point = rl_end;
|
||||
rl_forced_update_display();
|
||||
reset_prog_mode();
|
||||
doupdate();
|
||||
ui_resize();
|
||||
g_free(editor_task.result);
|
||||
g_free(editor_task.filename);
|
||||
g_free(editor_task.editor_cmd);
|
||||
editor_task.result = NULL;
|
||||
editor_task.filename = NULL;
|
||||
editor_task.editor_cmd = NULL;
|
||||
if (editor_task.error) {
|
||||
log_error("[Editor] %s", editor_task.error->message);
|
||||
g_error_free(editor_task.error);
|
||||
editor_task.error = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&editor_task.mutex);
|
||||
background_mode = FALSE;
|
||||
log_debug("[Editor] Exiting background mode");
|
||||
}
|
||||
|
||||
// Deprecated synchronous editor call. Returns a message as returned_message.
|
||||
// Please avoid using it, since it blocks execution of the message handling and other important functionality until the editor is closed.
|
||||
// Returns true if an error occurred
|
||||
gboolean
|
||||
get_message_from_editor(gchar* message, gchar** returned_message)
|
||||
|
||||
@@ -37,8 +37,24 @@
|
||||
#ifndef TOOLS_EDITOR_H
|
||||
#define TOOLS_EDITOR_H
|
||||
|
||||
#include "ui/win_types.h"
|
||||
#include <glib.h>
|
||||
typedef struct
|
||||
{
|
||||
pid_t pid; // Editor process PID
|
||||
gboolean active; // Thread running
|
||||
gchar* result; // Edited message
|
||||
GError* error; // Error if any
|
||||
pthread_mutex_t mutex; // Protect access
|
||||
pthread_t thread; // Editor thread
|
||||
gchar* filename; // Temporary file path
|
||||
gchar* editor_cmd; // Editor command with filename
|
||||
} EditorTask;
|
||||
|
||||
extern EditorTask editor_task;
|
||||
|
||||
gboolean get_message_from_editor(gchar* message, gchar** returned_message);
|
||||
gboolean get_message_from_editor_async(gchar* message);
|
||||
void editor_process(ProfWin* window);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -109,6 +109,7 @@ _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* t
|
||||
buffer->lines += e->_lines;
|
||||
|
||||
while (g_slist_length(buffer->entries) >= MAX_BUFFER_SIZE) {
|
||||
// Delete message from the opposite size to free buffer
|
||||
GSList* buffer_entry_to_delete = append ? buffer->entries : g_slist_last(buffer->entries);
|
||||
ProfBuffEntry* entry_to_delete = (ProfBuffEntry*)buffer_entry_to_delete->data;
|
||||
buffer->lines -= entry_to_delete->_lines;
|
||||
@@ -117,7 +118,13 @@ _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* t
|
||||
}
|
||||
|
||||
if (from_jid && y_end_pos == y_start_pos) {
|
||||
log_warning("Ncurses Overflow! From: %s, pos: %d, ID: %s, message: %s", from_jid, y_end_pos, id, message);
|
||||
log_warning("Ncurses Overflow! From: %s, position: %d, ID: %s, append: %s, used message buffer size: %d, message buffer size: %d, rendered lines buffer size: %d",
|
||||
from_jid, y_start_pos, id, append ? "TRUE" : "FALSE", g_slist_length(buffer->entries), MAX_BUFFER_SIZE, PAD_SIZE);
|
||||
// At this point we have a message that caused overflow of the render buffer.
|
||||
// Ideally, we want to clean other messages to rerender everything properly. To do so,
|
||||
// first we need to determine the size of the message that we are trying to display,
|
||||
// then we need to print the message.
|
||||
// However, _buffer_add is too late in the code to do this, therefore it has to be done in the win_print_old_history.
|
||||
}
|
||||
|
||||
buffer->entries = append ? g_slist_append(buffer->entries, e) : g_slist_prepend(buffer->entries, e);
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "glib.h"
|
||||
#include "xmpp/chat_session.h"
|
||||
#include "window_list.h"
|
||||
#include "xmpp/roster_list.h"
|
||||
@@ -561,7 +562,8 @@ static void
|
||||
_chatwin_history(ProfChatWin* chatwin, const char* const contact_barejid)
|
||||
{
|
||||
if (!chatwin->history_shown) {
|
||||
GSList* history = log_database_get_previous_chat(contact_barejid, NULL, NULL, FALSE, FALSE);
|
||||
GSList* history = NULL;
|
||||
log_database_get_previous_chat(contact_barejid, NULL, NULL, FALSE, FALSE, &history);
|
||||
GSList* curr = history;
|
||||
|
||||
while (curr) {
|
||||
@@ -583,15 +585,17 @@ _chatwin_history(ProfChatWin* chatwin, const char* const contact_barejid)
|
||||
// Print history starting from start_time to end_time if end_time is null the
|
||||
// first entry's timestamp in the buffer is used. Flip true to prepend to buffer.
|
||||
// Timestamps should be in iso8601
|
||||
gboolean
|
||||
chatwin_db_history(ProfChatWin* chatwin, const char* start_time, char* end_time, gboolean flip)
|
||||
db_history_result_t
|
||||
chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* end_time, gboolean flip)
|
||||
{
|
||||
if (!end_time) {
|
||||
end_time = buffer_size(((ProfWin*)chatwin)->layout->buffer) == 0 ? NULL : g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0)->time);
|
||||
auto_gchar gchar* _end_time = NULL;
|
||||
if (!end_time && buffer_size(((ProfWin*)chatwin)->layout->buffer) > 0) {
|
||||
_end_time = g_date_time_format_iso8601(buffer_get_entry(((ProfWin*)chatwin)->layout->buffer, 0)->time);
|
||||
end_time = _end_time;
|
||||
}
|
||||
|
||||
GSList* history = log_database_get_previous_chat(chatwin->barejid, start_time, end_time, !flip, flip);
|
||||
gboolean has_items = g_slist_length(history) != 0;
|
||||
GSList* history = NULL;
|
||||
db_history_result_t result = log_database_get_previous_chat(chatwin->barejid, start_time, end_time, !flip, flip, &history);
|
||||
GSList* curr = history;
|
||||
|
||||
while (curr) {
|
||||
@@ -610,9 +614,12 @@ chatwin_db_history(ProfChatWin* chatwin, const char* start_time, char* end_time,
|
||||
}
|
||||
|
||||
g_slist_free_full(history, (GDestroyNotify)message_free);
|
||||
// TODO: Potential optimization
|
||||
// if (flip)
|
||||
// since adding messages to history without overflowing buffer does not require a win_redraw
|
||||
win_redraw((ProfWin*)chatwin);
|
||||
|
||||
return has_items;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -387,16 +387,9 @@ cons_about(void)
|
||||
_cons_splash_logo();
|
||||
} else {
|
||||
auto_gchar gchar* prof_version = prof_get_version();
|
||||
win_println(console, THEME_DEFAULT, "-", "Welcome to Profanity, version %s", prof_version);
|
||||
win_println(console, THEME_DEFAULT, "-", "Welcome to CProof, version %s", prof_version);
|
||||
}
|
||||
|
||||
win_println(console, THEME_DEFAULT, "-", "Copyright (C) 2012 - 2019 James Booth <boothj5web@gmail.com>.");
|
||||
win_println(console, THEME_DEFAULT, "-", "Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>.");
|
||||
win_println(console, THEME_DEFAULT, "-", "License GPLv3+: GNU GPL version 3 or later <https://www.gnu.org/licenses/gpl.html>");
|
||||
win_println(console, THEME_DEFAULT, "-", "");
|
||||
win_println(console, THEME_DEFAULT, "-", "This is free software; you are free to change and redistribute it.");
|
||||
win_println(console, THEME_DEFAULT, "-", "There is NO WARRANTY, to the extent permitted by law.");
|
||||
win_println(console, THEME_DEFAULT, "-", "");
|
||||
win_println(console, THEME_DEFAULT, "-", "Type '/help' to show complete help.");
|
||||
win_println(console, THEME_DEFAULT, "-", "");
|
||||
|
||||
@@ -484,7 +477,7 @@ cons_show_wins(gboolean unread)
|
||||
}
|
||||
curr = g_slist_next(curr);
|
||||
}
|
||||
g_slist_free_full(window_strings, free);
|
||||
g_slist_free_full(window_strings, g_free);
|
||||
|
||||
cons_alert(NULL);
|
||||
}
|
||||
@@ -505,7 +498,7 @@ cons_show_wins_attention()
|
||||
}
|
||||
curr = g_slist_next(curr);
|
||||
}
|
||||
g_slist_free_full(window_strings, free);
|
||||
g_slist_free_full(window_strings, g_free);
|
||||
|
||||
cons_alert(NULL);
|
||||
}
|
||||
@@ -2704,13 +2697,12 @@ _cons_splash_logo(void)
|
||||
ProfWin* console = wins_get_console();
|
||||
win_println(console, THEME_DEFAULT, "-", "Welcome to");
|
||||
|
||||
win_println(console, THEME_SPLASH, "-", " ___ _ ");
|
||||
win_println(console, THEME_SPLASH, "-", " / __) (_)_ ");
|
||||
win_println(console, THEME_SPLASH, "-", " ____ ___ ___ | |__ ____ ____ _| |_ _ _ ");
|
||||
win_println(console, THEME_SPLASH, "-", "| _ \\ / __) _ \\| __) _ | _ \\| | _) | | |");
|
||||
win_println(console, THEME_SPLASH, "-", "| | ) | | | (_) | | | ( | | | | | | |_| |_| |");
|
||||
win_println(console, THEME_SPLASH, "-", "| ||_/|_| \\___/|_| \\_||_|_| |_|_|\\___)__ |");
|
||||
win_println(console, THEME_SPLASH, "-", "|_| (____/ ");
|
||||
win_println(console, THEME_SPLASH, "-", " _____ _____ __ ");
|
||||
win_println(console, THEME_SPLASH, "-", " / ____| __ \\ / _|");
|
||||
win_println(console, THEME_SPLASH, "-", " | | | |__) | __ ___ ___ | |_ ");
|
||||
win_println(console, THEME_SPLASH, "-", " | | | ___/ '__/ _ \\ / _ \\| _|");
|
||||
win_println(console, THEME_SPLASH, "-", " | |____| | | | | (_) | (_) | | ");
|
||||
win_println(console, THEME_SPLASH, "-", " \\_____|_| |_| \\___/ \\___/|_| ");
|
||||
win_println(console, THEME_SPLASH, "-", "");
|
||||
|
||||
auto_gchar gchar* prof_version = prof_get_version();
|
||||
|
||||
@@ -974,14 +974,7 @@ _inp_rl_send_to_editor(int count, int key)
|
||||
|
||||
auto_gchar gchar* message = NULL;
|
||||
|
||||
if (get_message_from_editor(rl_line_buffer, &message)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rl_replace_line(message, 0);
|
||||
ui_resize();
|
||||
rl_point = rl_end;
|
||||
rl_forced_update_display();
|
||||
get_message_from_editor_async(rl_line_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "config/account.h"
|
||||
#include "config/preferences.h"
|
||||
#include "command/cmd_funcs.h"
|
||||
#include "database.h"
|
||||
#include "ui/win_types.h"
|
||||
#include "xmpp/message.h"
|
||||
#include "xmpp/muc.h"
|
||||
@@ -145,7 +146,7 @@ void chatwin_set_incoming_char(ProfChatWin* chatwin, const char* const ch);
|
||||
void chatwin_unset_incoming_char(ProfChatWin* chatwin);
|
||||
void chatwin_set_outgoing_char(ProfChatWin* chatwin, const char* const ch);
|
||||
void chatwin_unset_outgoing_char(ProfChatWin* chatwin);
|
||||
gboolean chatwin_db_history(ProfChatWin* chatwin, const char* start_time, char* end_time, gboolean flip);
|
||||
db_history_result_t chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* end_time, gboolean flip);
|
||||
|
||||
// MUC window
|
||||
ProfMucWin* mucwin_new(const char* const barejid);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "database.h"
|
||||
#include "ui/window_list.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -62,9 +63,8 @@
|
||||
#include "xmpp/xmpp.h"
|
||||
#include "xmpp/roster_list.h"
|
||||
|
||||
static const int PAD_SIZE = 100;
|
||||
static const char* LOADING_MESSAGE = "Loading older messages…";
|
||||
static const char* CONS_WIN_TITLE = "Profanity. Type /help for help information.";
|
||||
static const char* CONS_WIN_TITLE = "CProof. Type /help for help information.";
|
||||
static const char* XML_WIN_TITLE = "XML Console";
|
||||
|
||||
#define CEILING(X) (X - (int)(X) > 0 ? (int)(X + 1) : (int)(X))
|
||||
@@ -629,54 +629,68 @@ win_free(ProfWin* window)
|
||||
void
|
||||
win_page_up(ProfWin* window, int scroll_size)
|
||||
{
|
||||
// Page offset from the start of the NCurses PAD (invisible window)
|
||||
int* page_start = &(window->layout->y_pos);
|
||||
int page_start_initial = *page_start;
|
||||
int total_rows = getcury(window->layout->win);
|
||||
// Size of the visible chat page
|
||||
int page_space = getmaxy(stdscr) - 4;
|
||||
if (scroll_size == 0)
|
||||
scroll_size = page_space;
|
||||
win_scroll_state_t* scroll_state = &window->scroll_state;
|
||||
*scroll_state = (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) ? WIN_SCROLL_INNER : *scroll_state;
|
||||
*page_start -= scroll_size;
|
||||
gboolean has_scroll_past_buffer = *page_start < 0;
|
||||
|
||||
if (*page_start == -scroll_size && window->type == WIN_CHAT) {
|
||||
if (has_scroll_past_buffer && window->type == WIN_CHAT) {
|
||||
ProfChatWin* chatwin = (ProfChatWin*)window;
|
||||
ProfBuffEntry* first_entry = buffer_size(window->layout->buffer) != 0 ? buffer_get_entry(window->layout->buffer, 0) : NULL;
|
||||
|
||||
// Don't do anything if still fetching mam messages
|
||||
if (first_entry && !(first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, LOADING_MESSAGE) == 0)) {
|
||||
gboolean is_still_fetching_mam = first_entry && (first_entry->theme_item == THEME_ROOMINFO && g_strcmp0(first_entry->message, LOADING_MESSAGE) == 0);
|
||||
if (!is_still_fetching_mam) {
|
||||
// WIN_SCROLL_REACHED_TOP means that we reached top of DB and there is no need to refetch data from it
|
||||
gboolean is_db_offset_jump_needed = false;
|
||||
if (*scroll_state != WIN_SCROLL_REACHED_TOP) {
|
||||
*scroll_state = !chatwin_db_history(chatwin, NULL, NULL, TRUE) ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
|
||||
db_history_result_t db_response = chatwin_db_history(chatwin, NULL, NULL, TRUE);
|
||||
is_db_offset_jump_needed = db_response == DB_RESPONSE_SUCCESS;
|
||||
*scroll_state = db_response == DB_RESPONSE_EMPTY ? WIN_SCROLL_REACHED_TOP : WIN_SCROLL_INNER;
|
||||
log_debug("Scroll state after DB history fetch: %s", *scroll_state ? "WIN_SCROLL_REACHED_TOP" : "WIN_SCROLL_INNER");
|
||||
}
|
||||
|
||||
if (*scroll_state == WIN_SCROLL_REACHED_TOP && prefs_get_boolean(PREF_MAM)) {
|
||||
win_print_loading_history(window);
|
||||
iq_mam_request_older(chatwin);
|
||||
if (*scroll_state == WIN_SCROLL_REACHED_TOP) {
|
||||
*page_start = 0;
|
||||
if (prefs_get_boolean(PREF_MAM)) {
|
||||
win_print_loading_history(window);
|
||||
iq_mam_request_older(chatwin);
|
||||
}
|
||||
}
|
||||
|
||||
int buff_size = buffer_size(window->layout->buffer);
|
||||
int offset_entry_id = buff_size > 10 ? 10 : buff_size - 1;
|
||||
int offset = buffer_get_entry(window->layout->buffer, offset_entry_id)->y_end_pos;
|
||||
*page_start = offset - page_space;
|
||||
// Recalculate scroll offset after loading older messages from the DB.
|
||||
// *page_start may become negative due to paging up beyond the current buffer.
|
||||
// To maintain smooth scroll positioning, we adjust it relative to the visual offset
|
||||
// (y_start_pos) of the first buffer entry *before* DB fetch.
|
||||
// This ensures a seamless transition as older messages are prepended, regardless
|
||||
// of how many visual lines they occupy (since messages can vary in height).
|
||||
// For example, if *page_start == -5 and first_entry->y_start_pos == 40,
|
||||
// we shift the scroll to 35, preserving the visual context for the user.
|
||||
if (is_db_offset_jump_needed) {
|
||||
*page_start = first_entry ? first_entry->y_start_pos + *page_start : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// went past beginning, show first page
|
||||
if (*page_start < 0) {
|
||||
log_debug("win_page_up(): *page_start adjusted to 0 due to negative value (expected if insufficient history messages). Previous value: %d", *page_start);
|
||||
*page_start = 0;
|
||||
}
|
||||
|
||||
window->layout->paged = 1;
|
||||
|
||||
// update only if position has changed
|
||||
// Update window location only if position has changed
|
||||
if (page_start_initial != *page_start) {
|
||||
win_update_virtual(window);
|
||||
}
|
||||
|
||||
// switch off page if last line and space line visible
|
||||
if ((total_rows) - *page_start == page_space) {
|
||||
window->layout->paged = 0;
|
||||
}
|
||||
// Update scrolling state
|
||||
int total_rows = getcury(window->layout->win);
|
||||
window->layout->paged = (total_rows) - *page_start > page_space;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -696,20 +710,22 @@ win_page_down(ProfWin* window, int scroll_size)
|
||||
// Scrolled down after reaching the bottom of the page
|
||||
if ((*page_start > total_rows - page_space || (*page_start == page_space && *page_start >= total_rows)) && window->type == WIN_CHAT) {
|
||||
int bf_size = buffer_size(window->layout->buffer);
|
||||
if (bf_size > 0) {
|
||||
if (bf_size > 0 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
|
||||
// How many lines are left until end of the screen
|
||||
int current_offset = total_rows - *page_start;
|
||||
GDateTime* now = g_date_time_new_now_local();
|
||||
ProfBuffEntry* last_entry = buffer_get_entry(window->layout->buffer, bf_size - 1);
|
||||
auto_gchar gchar* start = g_date_time_format_iso8601(last_entry->time);
|
||||
GDateTime* now = g_date_time_new_now_local();
|
||||
gchar* end_date = g_date_time_format_iso8601(now);
|
||||
g_date_time_unref(now);
|
||||
if (*scroll_state != WIN_SCROLL_REACHED_BOTTOM && !chatwin_db_history((ProfChatWin*)window, start, end_date, FALSE)) {
|
||||
auto_gchar gchar* end_date = g_date_time_format_iso8601(now);
|
||||
db_history_result_t db_response = chatwin_db_history((ProfChatWin*)window, start, end_date, FALSE);
|
||||
if (db_response == DB_RESPONSE_EMPTY)
|
||||
*scroll_state = WIN_SCROLL_REACHED_BOTTOM;
|
||||
} else if (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) {
|
||||
g_free(end_date);
|
||||
}
|
||||
|
||||
int offset = last_entry->y_end_pos - 1;
|
||||
*page_start = offset - page_space + scroll_size;
|
||||
// similar to page_up (see explanation there)
|
||||
// Get offset of the message that was the latest prior to loading messages from the DB
|
||||
int original_pos = last_entry->y_end_pos;
|
||||
*page_start = original_pos - current_offset;
|
||||
g_date_time_unref(now);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
#include "xmpp/contact.h"
|
||||
#include "xmpp/muc.h"
|
||||
|
||||
// Max lines per window rendered by NCurses
|
||||
static const int PAD_SIZE = 10000;
|
||||
|
||||
void win_move_to_end(ProfWin* window);
|
||||
void win_show_status_string(ProfWin* window, const char* const from,
|
||||
const char* const show, const char* const status,
|
||||
|
||||
@@ -258,22 +258,20 @@ GList*
|
||||
wins_get_private_chats(const char* const roomjid)
|
||||
{
|
||||
GList* result = NULL;
|
||||
GString* prefix = g_string_new(roomjid);
|
||||
g_string_append(prefix, "/");
|
||||
auto_gchar gchar* prefix = g_strdup_printf("%s/", roomjid);
|
||||
GList* curr = values;
|
||||
|
||||
while (curr) {
|
||||
ProfWin* window = curr->data;
|
||||
if (window->type == WIN_PRIVATE) {
|
||||
ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
|
||||
if (roomjid == NULL || g_str_has_prefix(privatewin->fulljid, prefix->str)) {
|
||||
if (roomjid == NULL || g_str_has_prefix(privatewin->fulljid, prefix)) {
|
||||
result = g_list_append(result, privatewin);
|
||||
}
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
g_string_free(prefix, TRUE);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1067,19 +1065,13 @@ wins_create_summary(gboolean unread)
|
||||
while (curr) {
|
||||
ProfWin* window = g_hash_table_lookup(windows, curr->data);
|
||||
if (!unread || (unread && win_unread(window) > 0)) {
|
||||
GString* line = g_string_new("");
|
||||
|
||||
int ui_index = GPOINTER_TO_INT(curr->data);
|
||||
auto_gchar gchar* winstring = win_to_string(window);
|
||||
if (!winstring) {
|
||||
g_string_free(line, TRUE);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_string_append_printf(line, "%d: %s", ui_index, winstring);
|
||||
|
||||
result = g_slist_append(result, strdup(line->str));
|
||||
g_string_free(line, TRUE);
|
||||
gchar* line = g_strdup_printf("%d: %s", ui_index, winstring);
|
||||
result = g_slist_append(result, line);
|
||||
}
|
||||
|
||||
curr = g_list_next(curr);
|
||||
@@ -1108,19 +1100,13 @@ wins_create_summary_attention()
|
||||
has_attention = mucwin->has_attention;
|
||||
}
|
||||
if (has_attention) {
|
||||
GString* line = g_string_new("");
|
||||
|
||||
int ui_index = GPOINTER_TO_INT(curr->data);
|
||||
auto_gchar gchar* winstring = win_to_string(window);
|
||||
if (!winstring) {
|
||||
g_string_free(line, TRUE);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_string_append_printf(line, "%d: %s", ui_index, winstring);
|
||||
|
||||
result = g_slist_append(result, strdup(line->str));
|
||||
g_string_free(line, TRUE);
|
||||
gchar* line = g_strdup_printf("%d: %s", ui_index, winstring);
|
||||
result = g_slist_append(result, line);
|
||||
}
|
||||
curr = g_list_next(curr);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,14 @@ caps_init(void)
|
||||
g_hash_table_add(prof_features, strdup(STANZA_NS_LAST_MESSAGE_CORRECTION));
|
||||
}
|
||||
|
||||
if (prefs_get_boolean(PREF_MOOD)) {
|
||||
g_hash_table_add(prof_features, strdup(STANZA_NS_MOOD_NOTIFY));
|
||||
}
|
||||
|
||||
#ifdef HAVE_OMEMO
|
||||
g_hash_table_add(prof_features, strdup(XMPP_FEATURE_OMEMO_DEVICELIST_NOTIFY));
|
||||
#endif
|
||||
|
||||
my_sha1 = NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -2752,6 +2752,22 @@ iq_mam_request(ProfChatWin* win, GDateTime* enddate)
|
||||
return;
|
||||
}
|
||||
|
||||
// Trim the last 3 characters from the datetime string to remove unwanted suffix (timezone offset)
|
||||
static void
|
||||
_truncate_datetime_suffix(gchar* str)
|
||||
{
|
||||
if (!str) {
|
||||
log_warning("_truncate_datetime_suffix(): unexpected null datetime string");
|
||||
return;
|
||||
}
|
||||
size_t len = strlen(str);
|
||||
if (len >= 3) {
|
||||
str[len - 3] = '\0';
|
||||
} else {
|
||||
log_warning("_truncate_datetime_suffix(): unexpected short datetime string: %s", str);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
_mam_rsm_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
|
||||
{
|
||||
@@ -2773,17 +2789,17 @@ _mam_rsm_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
|
||||
|
||||
buffer_remove_entry(window->layout->buffer, 0);
|
||||
|
||||
auto_char char* start_str = NULL;
|
||||
auto_gchar gchar* start_str = NULL;
|
||||
if (data->start_datestr) {
|
||||
start_str = strdup(data->start_datestr);
|
||||
start_str = g_strdup(data->start_datestr);
|
||||
// Convert to iso8601
|
||||
start_str[strlen(start_str) - 3] = '\0';
|
||||
_truncate_datetime_suffix(start_str);
|
||||
}
|
||||
char* end_str = NULL;
|
||||
auto_gchar gchar* end_str = NULL;
|
||||
if (data->end_datestr) {
|
||||
end_str = strdup(data->end_datestr);
|
||||
end_str = g_strdup(data->end_datestr);
|
||||
// Convert to iso8601
|
||||
end_str[strlen(end_str) - 3] = '\0';
|
||||
_truncate_datetime_suffix(end_str);
|
||||
}
|
||||
|
||||
if (is_complete || !data->fetch_next) {
|
||||
|
||||
@@ -190,7 +190,7 @@ get_nick_from_full_jid(const char* const full_room_jid)
|
||||
/*
|
||||
* get the fulljid, fall back to the barejid
|
||||
*/
|
||||
char*
|
||||
const char*
|
||||
jid_fulljid_or_barejid(Jid* jid)
|
||||
{
|
||||
if (jid->fulljid) {
|
||||
|
||||
@@ -63,7 +63,7 @@ gboolean jid_is_valid_room_form(Jid* jid);
|
||||
char* create_fulljid(const char* const barejid, const char* const resource);
|
||||
char* get_nick_from_full_jid(const char* const full_room_jid);
|
||||
|
||||
char* jid_fulljid_or_barejid(Jid* jid);
|
||||
const char* jid_fulljid_or_barejid(Jid* jid);
|
||||
gchar* jid_random_resource(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -621,7 +621,7 @@ _available_handler(xmpp_stanza_t* const stanza)
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
char* jid = jid_fulljid_or_barejid(xmpp_presence->jid);
|
||||
const char* jid = jid_fulljid_or_barejid(xmpp_presence->jid);
|
||||
log_debug("Presence available handler fired for: %s", jid);
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ _available_handler(xmpp_stanza_t* const stanza)
|
||||
XMPPCaps* caps = stanza_parse_caps(stanza);
|
||||
if ((g_strcmp0(my_jid->fulljid, xmpp_presence->jid->fulljid) != 0) && caps) {
|
||||
log_debug("Presence contains capabilities.");
|
||||
char* jid = jid_fulljid_or_barejid(xmpp_presence->jid);
|
||||
const char* jid = jid_fulljid_or_barejid(xmpp_presence->jid);
|
||||
_handle_caps(jid, caps);
|
||||
}
|
||||
stanza_free_caps(caps);
|
||||
@@ -644,7 +644,7 @@ _available_handler(xmpp_stanza_t* const stanza)
|
||||
const char* account_name = session_get_account_name();
|
||||
int max_sessions = accounts_get_max_sessions(account_name);
|
||||
if (max_sessions > 0) {
|
||||
const gchar* cur_resource = accounts_get_resource(account_name);
|
||||
auto_gchar gchar* cur_resource = accounts_get_resource(account_name);
|
||||
int res_count = connection_count_available_resources();
|
||||
if (res_count > max_sessions && g_strcmp0(cur_resource, resource->name)) {
|
||||
ProfWin* console = wins_get_console();
|
||||
|
||||
@@ -275,11 +275,8 @@ roster_remove(const char* const name, const char* const barejid)
|
||||
if (contact) {
|
||||
GList* resources = p_contact_get_available_resources(contact);
|
||||
while (resources) {
|
||||
GString* fulljid = g_string_new(barejid);
|
||||
g_string_append(fulljid, "/");
|
||||
g_string_append(fulljid, resources->data);
|
||||
autocomplete_remove(roster->fulljid_ac, fulljid->str);
|
||||
g_string_free(fulljid, TRUE);
|
||||
auto_gchar gchar* fulljid = g_strdup_printf("%s/%s", barejid, (char*)resources->data);
|
||||
autocomplete_remove(roster->fulljid_ac, fulljid);
|
||||
resources = g_list_next(resources);
|
||||
}
|
||||
g_list_free(resources);
|
||||
|
||||
@@ -281,7 +281,7 @@ shows_all_messages_in_console_when_window_not_focussed(void **state)
|
||||
assert_true(prof_output_exact("-> You have joined the room as stabber, role: participant, affiliation: none"));
|
||||
|
||||
prof_input("/win 1");
|
||||
assert_true(prof_output_exact("Profanity. Type /help for help information."));
|
||||
assert_true(prof_output_exact("CProof. Type /help for help information."));
|
||||
|
||||
stbbr_send(
|
||||
"<message type='groupchat' to='stabber@localhost/profanity' from='testroom@conference.localhost/testoccupant'>"
|
||||
@@ -322,7 +322,7 @@ shows_first_message_in_console_when_window_not_focussed(void **state)
|
||||
assert_true(prof_output_exact("-> You have joined the room as stabber, role: participant, affiliation: none"));
|
||||
|
||||
prof_input("/win 1");
|
||||
assert_true(prof_output_exact("Profanity. Type /help for help information."));
|
||||
assert_true(prof_output_exact("CProof. Type /help for help information."));
|
||||
|
||||
stbbr_send(
|
||||
"<message type='groupchat' to='stabber@localhost/profanity' from='testroom@conference.localhost/testoccupant'>"
|
||||
@@ -368,7 +368,7 @@ shows_no_message_in_console_when_window_not_focussed(void **state)
|
||||
assert_true(prof_output_exact("-> You have joined the room as stabber, role: participant, affiliation: none"));
|
||||
|
||||
prof_input("/win 1");
|
||||
assert_true(prof_output_exact("Profanity. Type /help for help information."));
|
||||
assert_true(prof_output_exact("CProof. Type /help for help information."));
|
||||
|
||||
stbbr_send(
|
||||
"<message type='groupchat' to='stabber@localhost/profanity' from='testroom@conference.localhost/testoccupant'>"
|
||||
|
||||
@@ -219,7 +219,7 @@ returns_fulljid_when_exists(void** state)
|
||||
{
|
||||
Jid* jid = jid_create("localpart@domainpart/resourcepart");
|
||||
|
||||
char* result = jid_fulljid_or_barejid(jid);
|
||||
const char* result = jid_fulljid_or_barejid(jid);
|
||||
|
||||
assert_string_equal("localpart@domainpart/resourcepart", result);
|
||||
|
||||
@@ -231,7 +231,7 @@ returns_barejid_when_fulljid_not_exists(void** state)
|
||||
{
|
||||
Jid* jid = jid_create("localpart@domainpart");
|
||||
|
||||
char* result = jid_fulljid_or_barejid(jid);
|
||||
const char* result = jid_fulljid_or_barejid(jid);
|
||||
|
||||
assert_string_equal("localpart@domainpart", result);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user