Compare commits

..

9 Commits

Author SHA1 Message Date
2d3d1ced71 ref(ui): use MAX utility method for code conciseness and clarity
All checks were successful
CI Code / Check coding style (push) Successful in 1m28s
CI Code / Check spelling (push) Successful in 2m20s
CI Code / Linux (ubuntu) (push) Successful in 5m16s
CI Code / Code Coverage (push) Successful in 3m51s
CI Code / Linux (debian) (push) Successful in 5m52s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Check spelling (pull_request) Successful in 23s
CI Code / Linux (arch) (push) Successful in 8m34s
CI Code / Linux (debian) (pull_request) Successful in 4m25s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m35s
CI Code / Code Coverage (pull_request) Successful in 3m30s
CI Code / Linux (arch) (pull_request) Successful in 8m55s
2026-06-20 09:40:11 +00:00
adb078a3e2 fix(ui): reserve pad height per message to prevent multi-line clip and scroll desync
Some checks failed
CI Code / Check spelling (pull_request) Successful in 18s
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Code Coverage (pull_request) Successful in 3m24s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m54s
CI Code / Linux (debian) (pull_request) Successful in 6m58s
CI Code / Linux (arch) (pull_request) Successful in 7m7s
CI Code / Check spelling (push) Successful in 16s
CI Code / Check coding style (push) Successful in 32s
CI Code / Code Coverage (push) Failing after 3m24s
CI Code / Linux (debian) (push) Failing after 5m38s
CI Code / Linux (ubuntu) (push) Failing after 5m51s
CI Code / Linux (arch) (push) Failing after 7m38s
A tall multi-line message printed near the bottom of the ncurses pad was clipped because _win_print_internal grew the pad from the current cursor only (_win_ensure_pad_capacity(getcury)), not the height of the message about to be printed. The clipped message's captured height (e.g. 1 row for a 49-row message) was wrong while win_redraw later rendered it in full; the resulting buffer->lines / y_start_pos mismatch desynced the page-up scroll anchor, producing a ~one-page jump when scrolling past such messages from history.

Estimate the rendered height (hard newlines + soft-wrap over the usable width) and reserve that many rows before printing, so the message is never clipped and its captured height matches the redraw. All print paths go through _win_print_internal, so incoming/outgoing/history are covered.

Regression from the upstream sync (72f4f186d), which replaced the fixed-size pad with the dynamic _win_ensure_pad_capacity model.
2026-06-20 11:59:19 +03:00
5b45b35b3e refactor: rename argument to avoid C++ keyword conflict
All checks were successful
CI Code / Check spelling (pull_request) Successful in 20s
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Linux (debian) (pull_request) Successful in 4m56s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m4s
CI Code / Code Coverage (pull_request) Successful in 8m13s
CI Code / Linux (arch) (pull_request) Successful in 11m18s
CI Code / Check spelling (push) Successful in 16s
CI Code / Check coding style (push) Successful in 31s
CI Code / Linux (ubuntu) (push) Successful in 4m32s
CI Code / Linux (debian) (push) Successful in 6m44s
CI Code / Code Coverage (push) Successful in 8m17s
CI Code / Linux (arch) (push) Successful in 11m11s
The parameter `template` in format_call_external_argv() was renamed
to `template_fmt` to avoid collision with the `template` keyword in
C++. This improves compatibility when the header is included in
C++ code.
2026-06-16 11:21:11 +00:00
57d79ecf9c fix: add missing parameter types to function declarations
Some functions were declared without parameters in header files,
despite their implementations specifying arguments. This mismatch
is deprecated in all versions of C and is disallowed in C23.

This change updates the declarations to match their definitions,
ensuring compatibility with C standards, avoiding potential
undefined behavior, and improving support in tools and editors.
2026-06-16 11:21:11 +00:00
bfd7064a40 build: add missing includes to header files
This change adds necessary `#include` directives that were previously
omitted in some header files. Without these includes, compilation
could fail or produce undefined behavior when the headers are used
in isolation (i.e., before their dependencies are included elsewhere).

Ensures better modularity and reliability of header usage.
2026-06-16 11:21:11 +00:00
3f6b8f69fd fix(ui): raise pad redraw threshold above buffer cap to fix history scroll
All checks were successful
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Check coding style (pull_request) Successful in 30s
CI Code / Code Coverage (pull_request) Successful in 3m21s
CI Code / Linux (debian) (pull_request) Successful in 4m49s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m55s
CI Code / Check coding style (push) Successful in 31s
CI Code / Check spelling (push) Successful in 19s
CI Code / Linux (arch) (pull_request) Successful in 6m39s
CI Code / Linux (debian) (push) Successful in 4m16s
CI Code / Code Coverage (push) Successful in 3m29s
CI Code / Linux (ubuntu) (push) Successful in 4m32s
CI Code / Linux (arch) (push) Successful in 10m51s
PAD_THRESHOLD (3000) sat below the buffer line cap (PAD_SIZE - PAD_SIZE/10 = 9000), so _win_ensure_pad_capacity() fired a full win_redraw() from the hot print path on every message once a chat exceeded 3000 rendered lines. During history paging this turned one redraw per fetch into dozens, corrupting per-entry y_start_pos (offset jumps, dropped/skipped messages) and causing ~10s lag per page. The flat-file backend was byte-identical across the regression; the fault was purely in the UI pad path from the upstream merge.

Raise PAD_THRESHOLD to 12000 (above the cap) so the reclaim redraw only fires to drop dead pad space in long append-only sessions, never during scrolling. Also size the pad once to buffer->lines+100 in win_redraw() instead of shrinking to PAD_MIN_HEIGHT and regrowing per entry, removing the residual per-redraw wresize churn.
2026-06-08 16:03:33 +03:00
a04a8948e1 fix(chatwin): only fire plugins_post_chat_message_display on incoming
All checks were successful
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Check spelling (pull_request) Successful in 22s
CI Code / Linux (debian) (pull_request) Successful in 4m38s
CI Code / Code Coverage (pull_request) Successful in 3m12s
CI Code / Linux (ubuntu) (pull_request) Successful in 4m34s
CI Code / Linux (arch) (pull_request) Successful in 6m57s
CI Code / Check spelling (push) Successful in 17s
CI Code / Check coding style (push) Successful in 31s
CI Code / Code Coverage (push) Successful in 3m45s
CI Code / Linux (debian) (push) Successful in 4m38s
CI Code / Linux (ubuntu) (push) Successful in 4m49s
CI Code / Linux (arch) (push) Successful in 6m46s
The trigger was wired into outgoing / outgoing-carbon / history paths
too, which made plugins like sounds.py play the "new message" sound
when opening a chat (history loads), sending a message yourself, or
receiving a carbon of your own sent message from another device.

Restrict the call to chatwin_incoming_msg — matches the semantic of
"a remote party sent a chat message and it was displayed".
2026-06-01 16:31:14 +03:00
c1093db090 fix(editor): prevent UI redraw conflicts during editor suspend/resume
All checks were successful
CI Code / Check spelling (push) Successful in 16s
CI Code / Check coding style (push) Successful in 31s
CI Code / Code Coverage (push) Successful in 3m36s
CI Code / Linux (debian) (push) Successful in 4m43s
CI Code / Linux (ubuntu) (push) Successful in 4m54s
CI Code / Linux (arch) (push) Successful in 6m46s
Guard against re-entrant editor launches, abort editor on Ctrl-Z,
and centralize suspend-aware redraws through prof_doupdate() to prevent
terminal corruption when SIGTSTP is received while editor is active.

Removes SIGUSR1 editor escape in favor of standard pkill <editor> recovery.
2026-06-01 12:21:34 +00:00
72f4f186da merge: sync upstream profanity-im/profanity
All checks were successful
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 34s
CI Code / Code Coverage (push) Successful in 2m36s
CI Code / Linux (debian) (push) Successful in 4m41s
CI Code / Linux (ubuntu) (push) Successful in 4m52s
CI Code / Linux (arch) (push) Successful in 5m40s
Sync with upstream profanity-im/profanity.

Major upstream changes incorporated:

Memory management
  - Replace malloc+memset with g_new0 throughout codebase
  - Adopt auto_gchar / auto_gcharv / auto_gerror / auto_jid cleanup macros
  - Replace free() with g_free() for GAlloc'd memory

Editor rewrite
  - Remove pthread-based async editor; use GChildWatch callback API
  - New launch_editor(initial_content, callback, user_data) interface
  - Proper signal handling (SIGINT, SIGTSTP, SIGPIPE reset in child)
  - ui_suspend()/ui_resume() integration for TTY management

OMEMO improvements
  - Dual backend support: libsignal-protocol-c and libomemo-c
  - Proper pre-key removal after use (XEP-0384 compliance)
  - Automatic pre-key regeneration when store drops below threshold
  - New functions: omemo_is_device_active(), omemo_is_jid_trusted()
  - omemo_get_jid_untrusted_fingerprints() for better error messages
  - Fingerprint notifications on new device identity discovery
  - Deterministic pre-key ID generation tracking max_pre_key_id
  - omemo_trust_changed() UI updates on trust state changes

JID validation
  - RFC 6122-compliant validation in jid_is_valid()
  - Character-level checks (RFC 6122 forbidden chars: & ' / : < > @)
  - Length limits: 1023 per component, 3071 total
  - New jid_is_valid_user_jid() for user vs. service JID distinction

Database
  - Schema migration v3: UNIQUE constraint on archive_id for deduplication
  - Triggers for corrected message tracking (replaces_db_id / replaced_by_db_id)
  - db_history_result_t return type, _truncate_datetime_suffix()

UI / console
  - win_warn_needed() / win_warn_sent() warning deduplication hash table
  - PAD_MIN_HEIGHT dynamic pad sizing with PAD_THRESHOLD auto-cleanup
  - Spellcheck integration in input field with Unicode word detection
  - cons_spellcheck_setting() for /settings ui output
  - /[command]? shortcut for command help

Account config
  - Account name sanitization for GKeyFile special chars ([ ] = # \n \r)
  - Replace popen() with g_spawn_sync() for eval_password
  - TLS policy: add "direct" option alongside legacy

Connection
  - Port validation with g_assert (0–65535)
  - SHA-256 certificate fingerprint support (XMPP_CERT_PUBKEY_FINGERPRINT_SHA256)
  - "direct" TLS policy alias for legacy SSL

Common utilities
  - str_xml_sanitize() for XML 1.0 illegal character removal
  - string_matches_one_of() with formatted error messages
  - valid_tls_policy_option() helper
  - prof_date_time_format_iso8601() utility
  - Improved strip_arg_quotes() with backslash unescaping
  - prof_occurrences() uses g_slist_prepend + reverse for performance

PGP / OX
  - Proper GPGME resource cleanup with goto-cleanup pattern
  - g_string_free(xmppuri) leak fix in _ox_key_lookup

CSV export
  - Use GString + g_file_set_contents instead of raw write() syscalls

Tests
  - Restructured into subdirectories: command/, config/, xmpp/, ui/, omemo/, otr/, pgp/
  - New test_cmd_ac.c for autocompleter unit tests
  - Updated stubs for new UI suspend/resume functions

License headers
  - Migrate to SPDX-3.0 identifiers (GPL-3.0-or-later WITH OpenSSL-exception)

────────────────────────────────────────────────────
cproof-specific preservations:

  - XEP-0308 LMC: replace_id ?: id logic in message/stanza/omemo
  - Force encryption: cmd_force_encryption, test_forced_encryption
  - CWE-134: format string protection (cons_show("%s", ...))
  - y_start_pos-based paging in window.c
  - db_history_result_t return type, _truncate_datetime_suffix()

Merge-time fixes:

  - common.c: format-security (-Werror) — cons_show(errmsg) → cons_show("%s", errmsg)
  - database.c: null-deref guard — !msg->timestamp → msg && !msg->timestamp
  - console.c: implicit size_t → int cast — (int)(maxlen + 1)
  - tlscerts.c: %d for size_t — %zu

Build system:
  - Kept autotools (Makefile.am, configure.ac); upstream uses Meson
  - Restored deleted files: bootstrap.sh, autogen.sh, ax_valgrind_check.m4, configure-debug
  - Updated Makefile.am test paths for subdirectory structure
  - Added test_cmd_ac, test_forced_encryption to test sources

Functional tests:
  - Use cproof version; upstream requires stbbr_for_xmlns from updated stabber
  - Not yet available in devs/stabber fork

Closes #64

Merge author: jabber.developer2
Commits authors:
 Michael Vetter <jubalh@iodoru.org>
& Steffen Jaeckel <s@jaeckel.eu>
2026-05-26 17:48:14 +00:00
23 changed files with 233 additions and 39 deletions

View File

@@ -2638,7 +2638,8 @@ static const struct cmd_t command_defs[] = {
CMD_DESC(
"Spawn external editor to edit message. "
"After editing the inputline may appear empty. Press enter to send the text anyways. "
"Use /executable to set your favourite editor." )
"Use /executable to set your favourite editor. "
"Ctrl-Z in the editor aborts the session and returns to Profanity." )
},
{ CMD_PREAMBLE("/correct-editor",

View File

@@ -748,17 +748,17 @@ call_external(gchar** argv)
*
* This function constructs an argument vector (argv) based on the provided template string, replacing placeholders ("%u" and "%p") with the provided URL and filename, respectively.
*
* @param template The template string with placeholders.
* @param url The URL to replace "%u" (or NULL to skip).
* @param filename The filename to replace "%p" (or NULL to skip).
* @return The constructed argument vector (argv) as a null-terminated array of strings.
* @param template_fmt The template string with placeholders.
* @param url The URL to replace "%u" (or NULL to skip).
* @param filename The filename to replace "%p" (or NULL to skip).
* @return The constructed argument vector (argv) as a null-terminated array of strings.
*
* @note Remember to free the returned argument vector using `auto_gcharv` or `g_strfreev()`.
*/
gchar**
format_call_external_argv(const char* template, const char* url, const char* filename)
format_call_external_argv(const char* template_fmt, const char* url, const char* filename)
{
gchar** argv = g_strsplit(template, " ", 0);
gchar** argv = g_strsplit(template_fmt, " ", 0);
guint num_args = 0;
while (argv[num_args]) {
@@ -767,7 +767,7 @@ format_call_external_argv(const char* template, const char* url, const char* fil
argv[num_args] = g_strdup(url);
} else if (0 == g_strcmp0(argv[num_args], "%p") && filename != NULL) {
g_free(argv[num_args]);
argv[num_args] = strdup(filename);
argv[num_args] = g_strdup(filename);
}
num_args++;
}

View File

@@ -181,7 +181,7 @@ void get_file_paths_recursive(const char* directory, GSList** contents);
gchar* get_random_string(int length);
gboolean call_external(gchar** argv);
gchar** format_call_external_argv(const char* template, const char* url, const char* filename);
gchar** format_call_external_argv(const char* template_fmt, const char* url, const char* filename);
gchar* unique_filename_from_url(const char* url, const char* path);
gchar* get_expanded_path(const char* path);

View File

@@ -10,6 +10,8 @@
#ifndef EVENT_COMMON_H
#define EVENT_COMMON_H
#include "glib.h"
void ev_disconnect_cleanup(void);
void ev_inc_connection_counter(void);
void ev_reset_connection_counter(void);

View File

@@ -341,7 +341,7 @@ sv_ev_room_message(ProfMessage* message)
status_bar_active(num, WIN_MUC, mucwin->roomjid);
if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_BEEP))) {
beep();
ui_beep();
}
// not currently on groupchat window
@@ -349,7 +349,7 @@ sv_ev_room_message(ProfMessage* message)
status_bar_new(num, WIN_MUC, mucwin->roomjid);
if ((g_strcmp0(mynick, message->from_jid->resourcepart) != 0) && (prefs_get_boolean(PREF_FLASH))) {
flash();
ui_flash();
}
cons_show_incoming_room_message(message->from_jid->resourcepart, mucwin->roomjid, num, mention, triggers, mucwin->unread, window);

View File

@@ -10,6 +10,8 @@
#ifndef PGP_GPG_H
#define PGP_GPG_H
#include "glib.h"
typedef struct pgp_key_t
{
gchar* id;

View File

@@ -70,6 +70,38 @@ static void _connect_default(const char* const account);
pthread_mutex_t lock;
static gboolean force_quit = FALSE;
static volatile sig_atomic_t suspend_requested = FALSE;
static volatile sig_atomic_t cont_requested = FALSE;
static void
_handle_suspend_signals(void)
{
if (cont_requested) {
cont_requested = FALSE;
if (!editor_is_active()) {
clearok(stdscr, TRUE);
ui_resize();
}
}
if (editor_check_stopped()) {
log_info("[Editor] stopped; aborting back to profanity");
suspend_requested = FALSE;
editor_emergency_kill();
} else if (suspend_requested) {
suspend_requested = FALSE;
if (editor_is_active()) {
log_info("[Editor] SIGTSTP from vim group; deferring to next poll");
} else {
log_info("[Suspend] dropping to shell");
if (!isendwin()) {
endwin();
}
kill(0, SIGSTOP);
}
}
}
void
prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_file, gchar* theme_name, gchar** commands)
{
@@ -93,6 +125,7 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f
int waittime = 0;
while (cont && !force_quit) {
log_stderr_handler();
_handle_suspend_signals();
session_check_autoaway();
line = commands ? *commands : inp_readline();
@@ -182,6 +215,18 @@ sigterm_handler(int sig)
force_quit = TRUE;
}
static void
sigtstp_handler(int sig)
{
suspend_requested = TRUE;
}
static void
sigcont_handler(int sig)
{
cont_requested = TRUE;
}
static void
_init(char* log_level, char* config_file, char* log_file, char* theme_name)
{
@@ -189,7 +234,8 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name)
// ignore SIGPIPE
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTSTP, sigtstp_handler);
signal(SIGCONT, sigcont_handler);
signal(SIGWINCH, ui_sigwinch_handler);
signal(SIGTERM, sigterm_handler);
signal(SIGHUP, sigterm_handler);

View File

@@ -10,8 +10,10 @@
#ifndef BOOKMARK_IGNORE_H
#define BOOKMARK_IGNORE_H
#include "xmpp/xmpp.h"
void bookmark_ignore_on_connect(const char* const barejid);
void bookmark_ignore_on_disconnect(void);
void bookmark_ignore_on_disconnect();
gboolean bookmark_ignored(Bookmark* bookmark);
gchar** bookmark_ignore_list(gsize* len);
void bookmark_ignore_add(const char* const barejid);

View File

@@ -32,9 +32,15 @@ typedef struct EditorContext
void* user_data;
} EditorContext;
static gboolean editor_active = FALSE; // re-entrancy guard
static pid_t editor_pid = 0;
static void
_editor_exit_cb(GPid pid, gint status, gpointer data)
{
editor_active = FALSE;
editor_pid = 0;
EditorContext* ctx = data;
gchar* contents = NULL;
GError* error = NULL;
@@ -52,8 +58,10 @@ _editor_exit_cb(GPid pid, gint status, gpointer data)
cons_show_error("Could not read edited content: %s", error->message);
g_error_free(error);
}
} else {
cons_show_error("Editor exited with error status %d", WEXITSTATUS(status));
} else if (WIFEXITED(status)) {
cons_show_error("Editor exited with status %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
cons_show("Editor session cancelled.");
}
if (remove(ctx->filename) != 0) {
@@ -70,6 +78,11 @@ _editor_exit_cb(GPid pid, gint status, gpointer data)
gboolean
launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data)
{
if (editor_active) {
cons_show_error("An editor session is already active.");
return TRUE;
}
auto_gchar gchar* filename = NULL;
auto_gerror GError* glib_error = NULL;
const char* jid = connection_get_barejid();
@@ -118,6 +131,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
ctx->callback = callback;
ctx->user_data = user_data;
editor_active = TRUE;
ui_suspend();
// Force GLib to install its SIGCHLD handler before forking. Creating a
@@ -129,6 +143,7 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
pid_t pid = fork();
if (pid == -1) {
log_error("[Editor] Failed to fork: %s", strerror(errno));
editor_active = FALSE;
ui_resume();
ui_resize();
cons_show_error("Failed to start editor: %s", strerror(errno));
@@ -139,7 +154,8 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
} else if (pid == 0) {
// Child process: Inherits TTY from parent
// Reset signal handlers that profanity sets so the editor doesn't inherit them
// SIGTSTP=SIG_DFL lets vim's :stop / Ctrl-Z work; profanity catches
// the STOPPED state via editor_check_stopped() and drops to the shell.
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
@@ -151,7 +167,39 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat
}
// Parent process: Watch the child asynchronously
editor_pid = pid;
g_child_watch_add((GPid)pid, _editor_exit_cb, ctx);
g_strfreev(editor_argv);
return FALSE;
}
void
editor_emergency_kill(void)
{
if (!editor_active || editor_pid <= 0) {
return;
}
log_warning("[Editor] aborting editor pid %d", editor_pid);
kill(editor_pid, SIGCONT); // wake if STOPPED; no-op otherwise
kill(editor_pid, SIGTERM);
// _editor_exit_cb will restore the UI when the child reaps.
}
gboolean
editor_is_active(void)
{
return editor_active;
}
gboolean
editor_check_stopped(void)
{
// Poll for STOPPED without reaping (g_child_watch handles exit via its
// own WNOHANG). Lets prof_run drop the group to the shell on Ctrl-Z.
if (!editor_active || editor_pid <= 0) {
return FALSE;
}
int status;
pid_t r = waitpid(editor_pid, &status, WNOHANG | WUNTRACED);
return (r == editor_pid && WIFSTOPPED(status));
}

View File

@@ -16,4 +16,13 @@
gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data);
void editor_emergency_kill(void); // SIGCONT+SIGTERM the editor child
// TRUE if editor child is STOPPED (Ctrl-Z / :stop / gdb-attach). Does not
// reap; g_child_watch keeps handling exit.
gboolean editor_check_stopped(void);
// Editor-session flag accessor. Used by prof_run on SIGCONT resume.
gboolean editor_is_active(void);
#endif

View File

@@ -327,7 +327,7 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr
cons_show_incoming_message(display_name, num, chatwin->unread, window);
if (prefs_get_boolean(PREF_FLASH)) {
flash();
ui_flash();
}
chatwin->unread++;
@@ -364,7 +364,7 @@ chatwin_incoming_msg(ProfChatWin* chatwin, ProfMessage* message, gboolean win_cr
wins_add_quotes_ac(window, message->plain, FALSE);
if (prefs_get_boolean(PREF_BEEP)) {
beep();
ui_beep();
}
}
@@ -399,8 +399,6 @@ chatwin_outgoing_msg(ProfChatWin* chatwin, const char* const message, const char
win_print_outgoing((ProfWin*)chatwin, enc_char, id, replace_id, display_message);
}
plugins_post_chat_message_display(myjid->barejid, myjid->resourcepart, display_message);
// Save last id and message for LMC
// Note: if the same message is to be corrected several times, the id of the original message is used in each case
// https://xmpp.org/extensions/xep-0308.html#rules
@@ -434,7 +432,6 @@ chatwin_outgoing_carbon(ProfChatWin* chatwin, ProfMessage* message)
win_print_outgoing(window, enc_char, message->id, message->replace_id, message->plain);
plugins_post_chat_message_display(message->from_jid->barejid, message->from_jid->resourcepart, message->plain);
message->plain = old_plain;
int num = wins_get_num(window);
@@ -561,7 +558,6 @@ _chatwin_history(ProfChatWin* chatwin, const char* const contact_barejid)
if (plugin_msg)
msg->plain = plugin_msg;
win_print_history((ProfWin*)chatwin, msg);
plugins_post_chat_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
msg->plain = old_plain;
curr = g_slist_next(curr);
}
@@ -601,7 +597,6 @@ chatwin_db_history(ProfChatWin* chatwin, const gchar* start_time, const gchar* e
} else {
win_print_history((ProfWin*)chatwin, msg);
}
plugins_post_chat_message_display(msg->from_jid->barejid, msg->from_jid->resourcepart, msg->plain);
msg->plain = old_plain;
curr = g_slist_next(curr);
}

View File

@@ -54,6 +54,7 @@
static int inp_size;
static gboolean perform_resize = FALSE;
static gboolean ui_suspended = FALSE;
static GTimer* ui_idle_time;
static WINDOW* main_scr;
@@ -63,6 +64,14 @@ static Display* display;
static void _ui_draw_term_title(void);
// Fail-safe gate: authoritative flag (doesn't depend on isendwin staying
// TRUE), plus the regular endwin check for shutdown.
static gboolean
_ui_redraw_suspended(void)
{
return ui_suspended || isendwin();
}
static void
_ui_close(void)
{
@@ -120,8 +129,7 @@ ui_sigwinch_handler(int sig)
void
ui_update(void)
{
// UI is suspended
if (isendwin()) {
if (_ui_redraw_suspended()) {
return;
}
@@ -138,7 +146,7 @@ ui_update(void)
title_bar_update_virtual();
status_bar_draw();
inp_put_back();
doupdate();
prof_doupdate();
if (perform_resize) {
perform_resize = FALSE;
@@ -180,21 +188,49 @@ void
ui_suspend(void)
{
inp_suspend();
// Flush before flipping the flag (prof_doupdate skips while suspended).
prof_doupdate();
ui_suspended = TRUE;
endwin();
}
void
ui_resume(void)
{
ui_suspended = FALSE;
clearok(stdscr, TRUE); // ncurses can't see vim's screen clobber
refresh();
inp_resume();
}
void
ui_beep(void)
{
if (!isendwin()) {
beep();
}
}
void
ui_flash(void)
{
if (!isendwin()) {
flash();
}
}
void
prof_doupdate(void)
{
if (!_ui_redraw_suspended()) {
doupdate();
}
}
void
ui_resize(void)
{
// UI is suspended
if (isendwin()) {
if (_ui_redraw_suspended()) {
return;
}
@@ -216,8 +252,7 @@ ui_resize(void)
void
ui_redraw(void)
{
// UI is suspended
if (isendwin()) {
if (_ui_redraw_suspended()) {
return;
}

View File

@@ -182,7 +182,11 @@ inp_readline(void)
{
// UI is suspended
if (is_suspended || isendwin()) {
// Mirror the normal-path unlock so workers aren't starved
// for the whole editor session.
pthread_mutex_unlock(&lock);
g_usleep(100000); // 100ms
pthread_mutex_lock(&lock);
return NULL;
}
@@ -311,11 +315,13 @@ inp_get_line(void)
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
doupdate();
prof_doupdate();
char* line = NULL;
while (!line) {
line = inp_readline();
ui_update();
while (g_main_context_iteration(NULL, FALSE)) // non-blocking; bounded by ready sources
;
}
status_bar_clear_prompt();
return line;
@@ -338,12 +344,14 @@ inp_get_password(void)
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
doupdate();
prof_doupdate();
char* password = NULL;
get_password = TRUE;
while (!password) {
password = inp_readline();
ui_update();
while (g_main_context_iteration(NULL, FALSE)) // non-blocking; bounded by ready sources
;
}
get_password = FALSE;
status_bar_clear_prompt();
@@ -459,7 +467,7 @@ _inp_write(char* line, int offset)
_inp_win_handle_scroll();
_inp_win_update_virtual();
doupdate();
prof_doupdate();
}
static int

View File

@@ -57,7 +57,7 @@ privwin_incoming_msg(ProfPrivateWin* privatewin, ProfMessage* message)
privatewin->unread++;
if (prefs_get_boolean(PREF_FLASH)) {
flash();
ui_flash();
}
}
@@ -65,7 +65,7 @@ privwin_incoming_msg(ProfPrivateWin* privatewin, ProfMessage* message)
wins_add_quotes_ac(window, message->plain, TRUE);
if (prefs_get_boolean(PREF_BEEP)) {
beep();
ui_beep();
}
if (notify) {

View File

@@ -10,6 +10,8 @@
#ifndef UI_TITLEBAR_H
#define UI_TITLEBAR_H
#include "glib.h"
void create_title_bar(void);
void free_title_bar(void);
void title_bar_update_virtual(void);

View File

@@ -43,6 +43,9 @@ void ui_load_colours(void);
void ui_update(void);
void ui_redraw(void);
void ui_resize(void);
// doupdate() wrapper that skips when the UI is suspended. Use this instead
// of raw doupdate() to avoid the per-site whitelist class of bug.
void prof_doupdate(void);
void ui_focus_win(ProfWin* window);
void ui_sigwinch_handler(int sig);
void ui_handle_otr_error(const char* const barejid, const char* const message);
@@ -84,6 +87,8 @@ void ui_handle_recipient_error(const char* const recipient, const char* const er
void ui_handle_error(const char* const err_msg);
void ui_clear_win_title(void);
void ui_goodbye_title(void);
void ui_beep(void);
void ui_flash(void);
void ui_handle_room_configuration_form_error(const char* const roomjid, const char* const message);
void ui_handle_room_config_submit_result(const char* const roomjid);
void ui_handle_room_config_submit_result_error(const char* const roomjid, const char* const message);

View File

@@ -43,7 +43,7 @@
#include "ai/ai_client.h"
static const int PAD_MIN_HEIGHT = 100;
static const int PAD_THRESHOLD = 3000;
static const int PAD_THRESHOLD = 12000; // above buffer cap (~9000): reclaims dead pad space, never fires while scrolling
static gboolean _in_redraw = FALSE;
static void
@@ -67,6 +67,20 @@ _win_ensure_pad_capacity(ProfWin* window, WINDOW* win, int lines_needed)
}
}
}
// upper-bound estimate of rows a message body occupies (hard newlines + soft-wrap), to reserve pad space before printing so a tall message isn't clipped (a clipped height desyncs the scroll offset)
static int
_win_estimated_lines(WINDOW* win, const char* const message, int indent, int pad_indent)
{
int usable = MAX(1, getmaxx(win) - (indent + pad_indent));
int newlines = 0;
for (const char* p = message; *p; p++) {
if (*p == '\n') {
newlines++;
}
}
return newlines + utf8_display_len(message) / usable + 2;
}
static const char* LOADING_MESSAGE = "Loading older messages…";
static const char* CONS_WIN_TITLE = "CProof. Type /help for help information.";
static const char* XML_WIN_TITLE = "XML Console";
@@ -2001,7 +2015,7 @@ _win_print_internal(ProfWin* window, const char* show_char, int pad_indent, GDat
}
}
_win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win));
_win_ensure_pad_capacity(window, window->layout->win, getcury(window->layout->win) + _win_estimated_lines(window->layout->win, message + offset, indent, pad_indent));
if (prefs_get_boolean(PREF_WRAP)) {
_win_print_wrapped(window->layout->win, message + offset, indent, pad_indent);
@@ -2172,9 +2186,10 @@ win_redraw(ProfWin* window)
unsigned int size = buffer_size(window->layout->buffer);
_in_redraw = TRUE;
// shrink pad back to minimum size and erase it
// size the pad to fit the whole buffer (plus headroom) and erase it
int cols = getmaxx(window->layout->win);
wresize(window->layout->win, PAD_MIN_HEIGHT, cols);
int needed = window->layout->buffer->lines + 100;
wresize(window->layout->win, MAX(needed, PAD_MIN_HEIGHT), cols);
werase(window->layout->win);
for (unsigned int i = 0; i < size; i++) {

View File

@@ -10,6 +10,8 @@
#ifndef XMPP_BLOCKING_H
#define XMPP_BLOCKING_H
#include <strophe.h>
void blocking_request(void);
int blocked_set_handler(xmpp_stanza_t* stanza);
int reporting_set_handler(xmpp_stanza_t* stanza);

View File

@@ -10,6 +10,8 @@
#ifndef XMPP_IQ_H
#define XMPP_IQ_H
#include <strophe.h>
typedef int (*ProfIqCallback)(xmpp_stanza_t* const stanza, void* const userdata);
typedef void (*ProfIqFreeCallback)(void* userdata);

View File

@@ -7,6 +7,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
*/
#include "glib.h"
/*!
* \page OX OX Implementation
*

View File

@@ -10,6 +10,9 @@
#ifndef XMPP_ROSTER_H
#define XMPP_ROSTER_H
#include "glib.h"
#include <strophe.h>
void roster_request(void);
void roster_set_handler(xmpp_stanza_t* const stanza);
void roster_result_handler(xmpp_stanza_t* const stanza);

View File

@@ -12,6 +12,7 @@
#include "ui/win_types.h"
#include "xmpp/vcard.h"
#include <strophe.h>
vCard* vcard_new();
void vcard_free(vCard* vcard);

View File

@@ -85,6 +85,10 @@ void
ui_resize(void)
{
}
void
prof_doupdate(void)
{
}
void
ui_focus_win(ProfWin* win)
@@ -1475,3 +1479,13 @@ ui_is_suspended(void)
{
return FALSE;
}
void
ui_beep(void)
{
}
void
ui_flash(void)
{
}