Compare commits

..

1 Commits

Author SHA1 Message Date
e36f7d5964 feat(api): add get_current_window call
All checks were successful
CI API Docs / Test C API Documentation Generation (pull_request) Successful in 25s
CI API Docs / Test Python API Documentation Generation (pull_request) Successful in 31s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Check spelling (pull_request) Successful in 20s
CI Code / Linux (arch) (pull_request) Successful in 13m58s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m41s
CI Code / Linux (debian) (pull_request) Successful in 16m19s
Add prof_get_current_window API to retrieve the title of the currently active window, matching the titlebar display.

The feature is especially useful to track currently used plugin window.
2025-10-01 18:00:19 +02:00
16 changed files with 66 additions and 139 deletions

View File

@@ -1,9 +1,6 @@
FROM archlinux
RUN pacman -Syu --noconfirm
RUN pacman -S --needed --noconfirm reflector && reflector --latest 20 --protocol https --sort rate --save /etc/pacman.d/mirrorlist
RUN pacman-key --init
RUN pacman -S --needed --noconfirm \
RUN pacman -Syu --noconfirm && pacman -S --needed --noconfirm \
autoconf \
autoconf-archive \
automake \
@@ -18,7 +15,7 @@ RUN pacman -S --needed --noconfirm \
gcc \
git \
gpgme \
gtk3 \
gtk2 \
libgcrypt \
libmicrohttpd \
libnotify \

View File

@@ -209,7 +209,7 @@ char* prof_get_current_muc(void);
int prof_current_win_is_console(void);
/**
* Gets the user's nickname in the current chat room.
* Gets the users nickname in the current chat room.
* @return The nickname (e.g., "eddie") or NULL if not in a room.
*/
char* prof_get_current_nick(void);
@@ -236,7 +236,7 @@ char** prof_get_current_occupants(void);
/**
* Gets the current nickname in a chat room.
* @param barejid The room's Jabber ID.
* @param barejid The rooms Jabber ID.
* @return The nickname.
*/
char* prof_get_room_nick(const char* barejid);
@@ -522,7 +522,7 @@ void prof_settings_int_set(char* group, char* key, int value);
void prof_incoming_message(char* barejid, char* resource, char* message);
/**
* Adds a service discovery feature to CProof's supported features.
* Adds a service discovery feature to CProofs supported features.
* Sends a presence update if a session is connected.
* @param feature The feature to add.
*
@@ -558,7 +558,7 @@ void prof_encryption_reset(char* barejid);
int prof_chat_set_titlebar_enctext(char* barejid, char* enctext);
/**
* Resets the titlebar encryption indicator for a recipient to CProof's default.
* Resets the titlebar encryption indicator for a recipient to CProofs default.
* @param barejid Jabber ID of the recipient.
* @return 1 on success, 0 on failure.
*
@@ -633,7 +633,7 @@ int prof_chat_unset_outgoing_char(char* barejid);
int prof_room_set_titlebar_enctext(char* roomjid, char* enctext);
/**
* Resets the titlebar encryption indicator for a room to CProof's default.
* Resets the titlebar encryption indicator for a room to CProofs default.
* @param roomjid Jabber ID of the room.
* @return 1 on success, 0 on failure.
*

View File

@@ -1902,9 +1902,9 @@ static const struct cmd_t command_defs[] = {
CMD_ARGS(
{ "where", "Show the current log file location." },
{ "rotate on|off", "Rotate log, default on. Does not take effect if you specified a filename yourself when starting Profanity." },
{ "maxsize <bytes>", "With rotate enabled, specifies the max log size, defaults to 10485760 (10MiB)." },
{ "maxsize <bytes>", "With rotate enabled, specifies the max log size, defaults to 10485760 (10MB)." },
{ "shared on|off", "Share logs between all instances, default: on. When off, the process id will be included in the log filename. Does not take effect if you specified a filename yourself when starting Profanity." },
{ "level INFO|DEBUG|WARN|ERROR", "Set the log level. Default is INFO." })
{"level INFO|DEBUG|WARN|ERROR", "Set the log level. Default is INFO. Only works with default log file, not with user provided log file during startup via -f." })
},
{ CMD_PREAMBLE("/carbons",

View File

@@ -6271,11 +6271,10 @@ cmd_log(ProfWin* window, const char* const command, gchar** args)
if (strcmp(subcmd, "level") == 0) {
log_level_t prof_log_level;
if (log_level_from_string(value, &prof_log_level) == 0) {
auto_char char* log_file = strdup(get_log_file_location());
log_close();
log_init(prof_log_level, log_file);
log_init(prof_log_level, NULL);
cons_show("Log level changed to: %s (log file: %s).", value, log_file ? log_file : "[default]");
cons_show("Log level changed to: %s.", value);
return TRUE;
}
}

View File

@@ -186,7 +186,7 @@ log_error(const char* const msg, ...)
}
void
log_init(log_level_t filter, const char* const log_file)
log_init(log_level_t filter, char* log_file)
{
level_filter = filter;

View File

@@ -50,7 +50,7 @@ typedef enum {
PROF_LEVEL_ERROR
} log_level_t;
void log_init(log_level_t filter, const char* const log_file);
void log_init(log_level_t filter, char* log_file);
log_level_t log_get_filter(void);
void log_close(void);
const gchar* get_log_file_location(void);

View File

@@ -1,36 +1,40 @@
/*
* buffer.c
*
* Message buffer implementation for CProof.
*
* This module provides an in-memory buffer for managing active chat entries in the
* console-based XMPP client. Separate from persistent SQLite storage, it holds
* messages and metadata solely for UI rendering, ensuring responsive scrolling and
* display without exceeding ncurses pad limits (10k lines). By tracking rendered
* line counts per entry, it proactively trims content to prevent overflow, enabling
* seamless integration with ncurses for position-aware rendering.
*
* Key features and operations:
* - Append/prepend entries (messages) with automatic line-based cleanup.
* - Track cumulative lines for overflow prevention and efficient buffer sizing.
* - Remove entries by ID or index; mark delivery receipts as received.
* - Retrieve entries by index or ID for rendering and history queries.
* - Store metadata including timestamps, senders, themes, and ncurses y-positions.
*
* CProof. Fork of Profanity (since 2025).
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
* Copyright (C) 2025 CProof Developers
*
* Licensed under the GNU General Public License, version 3,
* with the OpenSSL exception. See LICENSE for details.
* This file is part of Profanity.
*
* Profanity is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
*
* vim: expandtab:ts=4:sts=4:sw=4
*/
#include "config.h"
#include "ui/window_list.h"
#include <stdlib.h>
#include <string.h>
@@ -50,8 +54,15 @@
#include "ui/window.h"
#include "ui/buffer.h"
#define MAX_BUFFER_SIZE 200
#define STRDUP_OR_NULL(str) ((str) ? strdup(str) : NULL)
struct prof_buff_t
{
GSList* entries;
int lines;
};
static void _free_entry(ProfBuffEntry* entry);
static ProfBuffEntry* _create_entry(const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos);
static void _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* time, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message, DeliveryReceipt* receipt, const char* const id, int y_start_pos, int y_end_pos, gboolean append);
@@ -97,24 +108,23 @@ _buffer_add(ProfBuff buffer, const char* show_char, int pad_indent, GDateTime* t
buffer->lines += e->_lines;
// With 10% margin to ensure overflow prevention
while (buffer->lines >= PAD_SIZE - (PAD_SIZE / 10) && g_slist_length(buffer->entries) > 1) {
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;
// log_debug("(Messages left in buffer: %d) DELETING: %s", g_slist_length(buffer->entries), entry_to_delete->id);
buffer->lines -= entry_to_delete->_lines;
_free_entry(entry_to_delete);
buffer->entries = g_slist_delete_link(buffer->entries, buffer_entry_to_delete);
}
if (from_jid && y_end_pos == y_start_pos) {
// win_redraw will redraw with entries above removed,
// it will also recalculate actual size of the message that caused overflow
int old_lines = buffer->lines;
win_redraw(wins_get_current());
log_debug("Ncurses Overflow! From: %s, position: %d, ID: %s, append: %s, used message buffer size: %d, rendered lines: (old: %d/ actual: %d/ max: %d)",
from_jid, y_start_pos, id, append ? "TRUE" : "FALSE", g_slist_length(buffer->entries), old_lines, buffer->lines, PAD_SIZE);
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);

View File

@@ -68,12 +68,6 @@ typedef struct prof_buff_entry_t
char* id;
} ProfBuffEntry;
struct prof_buff_t
{
GSList* entries;
int lines;
};
typedef struct prof_buff_t* ProfBuff;
ProfBuff buffer_create();

View File

@@ -268,11 +268,7 @@ _show_scrolled(ProfWin* current)
wattroff(win, bracket_attrs);
wattron(win, scrolled_attrs);
if (current->layout->unread_msg == 0) {
wprintw(win, "SCROLLED");
} else {
wprintw(win, "SCROLLED, NEW MESSAGES");
}
wprintw(win, "SCROLLED");
wattroff(win, scrolled_attrs);
wattron(win, bracket_attrs);

View File

@@ -120,7 +120,6 @@ typedef struct prof_layout_t
ProfBuff buffer;
int y_pos;
int paged;
int unread_msg;
} ProfLayout;
typedef struct prof_layout_simple_t

View File

@@ -103,7 +103,6 @@ _win_create_simple_layout(void)
layout->base.buffer = buffer_create();
layout->base.y_pos = 0;
layout->base.paged = 0;
layout->base.unread_msg = 0;
scrollok(layout->base.win, TRUE);
return &layout->base;
@@ -121,7 +120,6 @@ _win_create_split_layout(void)
layout->base.buffer = buffer_create();
layout->base.y_pos = 0;
layout->base.paged = 0;
layout->base.unread_msg = 0;
scrollok(layout->base.win, TRUE);
layout->subwin = NULL;
layout->sub_y_pos = 0;
@@ -199,7 +197,6 @@ win_create_muc(const char* const roomjid)
layout->base.buffer = buffer_create();
layout->base.y_pos = 0;
layout->base.paged = 0;
layout->base.unread_msg = 0;
scrollok(layout->base.win, TRUE);
new_win->window.layout = (ProfLayout*)layout;
@@ -701,11 +698,9 @@ void
win_page_down(ProfWin* window, int scroll_size)
{
int total_rows = getcury(window->layout->win);
int total_rows_with_unread = total_rows + window->layout->unread_msg;
int* page_start = &(window->layout->y_pos);
int page_space = getmaxy(stdscr) - 4;
int page_start_initial = *page_start;
if (scroll_size == 0)
scroll_size = page_space;
win_scroll_state_t* scroll_state = &window->scroll_state;
@@ -714,11 +709,7 @@ win_page_down(ProfWin* window, int scroll_size)
*page_start += scroll_size;
// Scrolled down after reaching the bottom of the page
gboolean past_bottom = *page_start > total_rows_with_unread - page_space;
gboolean at_page_space_and_past_unread = (*page_start == page_space && *page_start >= total_rows_with_unread);
gboolean is_chat = window->type == WIN_CHAT;
if ((past_bottom || at_page_space_and_past_unread) && is_chat) {
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 && *scroll_state != WIN_SCROLL_REACHED_BOTTOM) {
// How many lines are left until end of the screen
@@ -752,16 +743,13 @@ win_page_down(ProfWin* window, int scroll_size)
window->layout->paged = 1;
// update only if position has changed
if ((page_start_initial != *page_start) || window->layout->unread_msg) {
if (page_start_initial != *page_start) {
win_update_virtual(window);
}
/* Switch off page if no messages left to read.
* TODO: update buffer end handling to check messages just after last entry.
*/
if (*scroll_state == WIN_SCROLL_REACHED_BOTTOM) {
// switch off page if last line and space line visible
if (total_rows - *page_start == page_space) {
window->layout->paged = 0;
window->layout->unread_msg = 0;
}
}
@@ -822,7 +810,6 @@ win_clear(ProfWin* window)
int* page_start = &(window->layout->y_pos);
*page_start = y;
window->layout->paged = 1;
window->layout->unread_msg = 0;
win_update_virtual(window);
}
@@ -927,7 +914,6 @@ void
win_move_to_end(ProfWin* window)
{
window->layout->paged = 0;
window->layout->unread_msg = 0;
int rows = getmaxy(stdscr);
int y = getcury(window->layout->win);
@@ -1710,13 +1696,6 @@ win_newline(ProfWin* window)
static void
_win_printf(ProfWin* window, const char* show_char, int pad_indent, GDateTime* timestamp, int flags, theme_item_t theme_item, const char* const display_from, const char* const from_jid, const char* const message_id, const char* const message, ...)
{
/* Prevent printing and buffer update when user is viewing message history [SCROLLING]*/
if (window->layout->paged && wins_is_current(window)) {
window->layout->unread_msg++;
return;
}
if (timestamp == NULL) {
timestamp = g_date_time_new_now_local();
} else {
@@ -2024,10 +2003,6 @@ win_redraw(ProfWin* window)
_win_print_internal(window, e->show_char, e->pad_indent, e->time, e->flags, e->theme_item, e->display_from, e->message, e->receipt);
}
e->y_end_pos = getcury(window->layout->win);
// Recalculate lines (might be incorrect in case of NCurses overflow)
window->layout->buffer->lines -= e->_lines;
e->_lines = e->y_end_pos - e->y_start_pos;
window->layout->buffer->lines += e->_lines;
}
}

View File

@@ -979,7 +979,6 @@ _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status
connection_get_jid();
conn.domain = strdup(conn.jid->domainpart);
log_debug("conn.domain=%s", conn.jid->domainpart);
connection_clear_data();
conn.features_by_jid = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)g_hash_table_destroy);
@@ -1164,32 +1163,3 @@ connection_get_profanity_identifier(void)
{
return prof_identifier;
}
void
connection_debug_print_features()
{
log_debug("=== Connection Features ===");
GHashTableIter iter;
gpointer jid_ptr, features_ptr;
g_hash_table_iter_init(&iter, conn.features_by_jid);
while (g_hash_table_iter_next(&iter, &jid_ptr, &features_ptr)) {
const char* jid = (const char*)jid_ptr;
GHashTable* features = (GHashTable*)features_ptr;
if (!features || g_hash_table_size(features) == 0) {
log_debug("%s:\t(no features)", jid);
continue;
}
GList* feature_keys = g_hash_table_get_keys(features);
for (GList* l = feature_keys; l != NULL; l = l->next) {
const char* feature = (const char*)l->data;
log_debug("%s:\t%s", jid, feature);
}
g_list_free(feature_keys);
}
log_debug("=== End of Features ===");
}

View File

@@ -1417,8 +1417,6 @@ _manual_pong_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
return 0;
}
static gboolean autoping_error_shown = false;
static int
_autoping_timed_send(xmpp_conn_t* const conn, void* const userdata)
{
@@ -1427,13 +1425,10 @@ _autoping_timed_send(xmpp_conn_t* const conn, void* const userdata)
}
if (connection_supports(XMPP_FEATURE_PING) == FALSE) {
// TODO: do we need to check it on each autoping call?
log_warning("Server doesn't advertise %s feature.", XMPP_FEATURE_PING);
if (!autoping_error_shown) {
connection_debug_print_features();
cons_show_error("Server ping not supported (%s). Check log for details.", XMPP_FEATURE_PING);
}
autoping_error_shown = 1;
log_warning("Server doesn't advertise %s feature, disabling autoping.", XMPP_FEATURE_PING);
prefs_set_autoping(0);
cons_show_error("Server ping not supported (%s), autoping disabled.", XMPP_FEATURE_PING);
return 0;
}
if (autoping_wait) {
@@ -1504,7 +1499,7 @@ _auto_pong_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
if (g_strcmp0(errtype, STANZA_TYPE_CANCEL) == 0) {
log_warning("Server ping (id=%s) error type 'cancel', disabling autoping.", id);
prefs_set_autoping(0);
cons_show_error("Autoping is not supported by the server (stanza cancelled). The feature has been disabled automatically.");
cons_show_error("Server ping not supported, autoping disabled.");
xmpp_timed_handler_delete(connection_get_conn(), _autoping_timed_send);
}
@@ -2520,7 +2515,6 @@ _disco_items_result_handler(xmpp_stanza_t* const stanza)
GSList* items = NULL;
if ((g_strcmp0(id, "discoitemsreq") != 0) && (g_strcmp0(id, "discoitemsreq_onconnect") != 0)) {
log_warning("_disco_items_result_handler: Received unexpected disco id: %s", id);
return;
}

View File

@@ -208,8 +208,6 @@ const char* connection_jid_for_feature(const char* const feature);
const char* connection_get_profanity_identifier(void);
void connection_debug_print_features();
char* message_send_chat(const char* const barejid, const char* const msg, const char* const oob_url, gboolean request_receipt, const char* const replace_id);
char* message_send_chat_otr(const char* const barejid, const char* const msg, gboolean request_receipt, const char* const replace_id);
char* message_send_chat_pgp(const char* const barejid, const char* const msg, gboolean request_receipt, const char* const replace_id);

View File

@@ -26,7 +26,7 @@
#include "log.h"
void
log_init(log_level_t filter, const char* const log_file)
log_init(log_level_t filter, char* log_file)
{
}
log_level_t

View File

@@ -156,11 +156,6 @@ connection_get_profanity_identifier(void)
return "profident";
}
void
connection_debug_print_features()
{
}
jabber_conn_status_t
connection_register(const char* const altdomain, int port, const char* const tls_policy,
const char* const username, const char* const password)