Compare commits

...

10 Commits

Author SHA1 Message Date
88b48000f8 fix(cmd): increase /connect max args from 7 to 9
All checks were successful
CI Code / Check spelling (pull_request) Successful in 27s
CI Code / Check coding style (pull_request) Successful in 36s
CI Code / Linux (arch) (pull_request) Successful in 14m23s
CI Code / Linux (debian) (pull_request) Successful in 15m33s
CI Code / Linux (ubuntu) (pull_request) Successful in 15m46s
The /connect command can take: account + server <s> + port <p> + tls <t> + auth <a>
which totals 9 arguments, not 7. This fixes argument parsing for full command usage.
2026-01-06 17:22:53 +03:00
20af44196b fix(cmd): cmd_time: Avoid extra loop iterations for single option
All checks were successful
CI Code / Check spelling (push) Successful in 19s
CI Code / Check coding style (push) Successful in 36s
CI Code / Linux (arch) (push) Successful in 10m17s
CI Code / Linux (debian) (push) Successful in 13m28s
CI Code / Linux (ubuntu) (push) Successful in 13m47s
2025-11-27 11:38:49 +01:00
84d6253561 fix(xmpp): format debug logging statements for consistency
All checks were successful
CI Code / Check spelling (push) Successful in 16s
CI Code / Check coding style (push) Successful in 33s
CI Code / Linux (arch) (push) Successful in 9m54s
CI Code / Linux (debian) (push) Successful in 13m35s
CI Code / Linux (ubuntu) (push) Successful in 13m56s
Add debug output tracking connection lifecycle.
    - Track disconnects and reconnects
    - Record session login, logout, and reconnection attempts
    - Use [CONNDBG] tag for easy log filtering
2025-11-21 10:03:50 +01:00
74cfd32c1e Improve arch build: sort mirrors by speed in reflector
All checks were successful
CI Code / Check spelling (pull_request) Successful in 22s
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Linux (debian) (pull_request) Successful in 12m53s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m14s
CI Code / Linux (arch) (pull_request) Successful in 13m22s
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 32s
CI Code / Linux (debian) (push) Successful in 13m35s
CI Code / Linux (arch) (push) Successful in 14m24s
CI Code / Linux (ubuntu) (push) Successful in 15m59s
2025-10-29 20:45:37 +01:00
fe6912a30c Fix arch build by adding pacman-key --init and updating gtk2 to gtk3
Some checks failed
CI Code / Linux (arch) (pull_request) Failing after 1m46s
CI Code / Check coding style (pull_request) Successful in 1m57s
CI Code / Check spelling (pull_request) Successful in 43s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m52s
CI Code / Linux (debian) (pull_request) Successful in 16m11s
2025-10-29 20:27:55 +01:00
266f5aa046 feat(api): add get_current_window call
Some checks failed
CI API Docs / Test C API Documentation Generation (pull_request) Successful in 27s
CI API Docs / Test Python API Documentation Generation (pull_request) Successful in 29s
CI Code / Check coding style (pull_request) Successful in 35s
CI Code / Check spelling (pull_request) Successful in 19s
CI Code / Linux (debian) (pull_request) Successful in 13m22s
CI Code / Linux (arch) (pull_request) Successful in 13m27s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m36s
CI API Docs / Test C API Documentation Generation (push) Successful in 1m2s
CI Code / Check spelling (push) Has been cancelled
CI Code / Check coding style (push) Has been cancelled
CI Code / Linux (ubuntu) (push) Has been cancelled
CI Code / Linux (debian) (push) Has been cancelled
CI API Docs / Test Python API Documentation Generation (push) Successful in 1m8s
CI Code / Linux (arch) (push) Successful in 9m24s
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-20 19:00:48 +02:00
3738be4af1 fix: prevent autoping disable on false negative ping support check
All checks were successful
CI Code / Check spelling (pull_request) Successful in 21s
CI Code / Check coding style (pull_request) Successful in 36s
CI Code / Linux (arch) (pull_request) Successful in 12m57s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m59s
CI Code / Linux (debian) (pull_request) Successful in 16m35s
CI Code / Check spelling (push) Successful in 19s
CI Code / Check coding style (push) Successful in 38s
CI Code / Linux (arch) (push) Successful in 10m10s
CI Code / Linux (debian) (push) Successful in 13m50s
CI Code / Linux (ubuntu) (push) Successful in 16m42s
Remove autodisable logic (prefs_set_autoping(0)) and early return in autoping
to treat intermittent false negatives from connection_supports(XMPP_FEATURE_PING).
Add one-time error display with debug features print for monitoring; warn on first failing
check without aborting to maintain functionality while investigating root cause.
2025-10-14 12:42:00 +02:00
ad95edb2b9 feat: make /log level work with non-default files 2025-10-13 20:47:34 +02:00
fac1e224bc fix(scroll): handle edge case of extremely long messages
All checks were successful
CI Code / Check coding style (pull_request) Successful in 34s
CI Code / Check spelling (pull_request) Successful in 45s
CI Code / Linux (arch) (pull_request) Successful in 10m43s
CI Code / Linux (ubuntu) (pull_request) Successful in 13m11s
CI Code / Linux (debian) (pull_request) Successful in 16m46s
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 32s
CI Code / Linux (arch) (push) Successful in 12m56s
CI Code / Linux (debian) (push) Successful in 13m28s
CI Code / Linux (ubuntu) (push) Successful in 16m20s
Prior commit (6ad8a190) did not properly handle overflow by long (9000 lines) message,
to address this issue, multiple changes were made:
- Add lines recalculation on win_redraw
- Move `prof_buff_t` struct to header file so its lines could be externally changed
- Call win_redraw once overflow is detected: it allows to recalculate sizes in lines and apply changes to the buffer
2025-10-08 13:00:32 +02:00
6ad8a19053 fix(buffer): use dynamic buffer size to prevent unnecessary buffer cleanups
All checks were successful
CI Code / Check spelling (pull_request) Successful in 22s
CI Code / Check coding style (pull_request) Successful in 38s
CI Code / Linux (arch) (pull_request) Successful in 13m4s
CI Code / Linux (debian) (pull_request) Successful in 13m32s
CI Code / Linux (ubuntu) (pull_request) Successful in 16m23s
CI Code / Check spelling (push) Successful in 19s
CI Code / Check coding style (push) Successful in 37s
CI Code / Linux (arch) (push) Successful in 13m8s
CI Code / Linux (ubuntu) (push) Successful in 16m2s
CI Code / Linux (debian) (push) Successful in 16m26s
Refactor buffer trimming logic to use dynamic line counts (_lines per entry)
against PAD_SIZE (10k) instead of fixed entry count (MAX_BUFFER_SIZE=200). This
prevents premature cleanups for short messages, reduces scrolling disruptions
during history loads, and scales better with rendered content size.

Delete oldest/newest entry from opposite end only when total lines approach
ncurses pad limit, minimizing unnecessary deletions.
Simplify overflow warning log by removing unused MAX_BUFFER_SIZE reference.

Update buffer.c header for improved DX:
- Add detailed module overview explaining role (in-memory UI buffer vs. DB
persistence), key features (trimming, metadata), and integration (ncurses).
- Shorten copyright/license boilerplate to concise pointer (LICENSE ref).
- Add fork notice; preserve original copyrights per GPLv3.

Partially addresses #36 (stuck scroll mitigation via fewer cleanups)
Related to #36
2025-10-07 15:12:20 +02:00
22 changed files with 237 additions and 118 deletions

View File

@@ -1,6 +1,9 @@
FROM archlinux
RUN pacman -Syu --noconfirm && pacman -S --needed --noconfirm \
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 \
autoconf \
autoconf-archive \
automake \
@@ -15,7 +18,7 @@ RUN pacman -Syu --noconfirm && pacman -S --needed --noconfirm \
gcc \
git \
gpgme \
gtk2 \
gtk3 \
libgcrypt \
libmicrohttpd \
libnotify \

View File

@@ -20,13 +20,13 @@
typedef char* PROF_WIN_TAG;
/** Function pointer for command callbacks, accepting an array of arguments. */
typedef void(*CMD_CB)(char **args);
typedef void (*CMD_CB)(char** args);
/** Function pointer for timed callbacks, accepting no arguments. */
typedef void(*TIMED_CB)(void);
typedef void (*TIMED_CB)(void);
/** Function pointer for window callbacks, accepting a window tag and input line. */
typedef void(*WINDOW_CB)(PROF_WIN_TAG win, char *line);
typedef void (*WINDOW_CB)(PROF_WIN_TAG win, char* line);
/** Highlights the console window in the status bar. */
void prof_cons_alert(void);
@@ -41,7 +41,7 @@ void prof_cons_alert(void);
* prof_cons_show("This will appear in the console window");
* @endcode
*/
int prof_cons_show(const char * const message);
int prof_cons_show(const char* const message);
/**
* Shows a message in the console with a specified theme.
@@ -57,7 +57,7 @@ int prof_cons_show(const char * const message);
* prof_cons_show_themed("myplugin", "text", NULL, "Plugin themed message");
* @endcode
*/
int prof_cons_show_themed(const char *const group, const char *const item, const char *const def, const char *const message);
int prof_cons_show_themed(const char* const group, const char* const item, const char* const def, const char* const message);
/**
* Shows a message indicating a command was used incorrectly.
@@ -69,7 +69,7 @@ int prof_cons_show_themed(const char *const group, const char *const item, const
* prof_cons_bad_cmd_usage("/mycommand");
* @endcode
*/
int prof_cons_bad_cmd_usage(const char *const cmd);
int prof_cons_bad_cmd_usage(const char* const cmd);
/**
* Registers a new command with help information and callback.
@@ -91,9 +91,9 @@ int prof_cons_bad_cmd_usage(const char *const cmd);
* prof_register_command("/newcommand", 1, 2, synopsis, "Example command", args, examples, my_function);
* @endcode
*/
void prof_register_command(const char *command_name, int min_args, int max_args,
char **synopsis, const char *description, char *arguments[][2], char **examples,
CMD_CB callback);
void prof_register_command(const char* command_name, int min_args, int max_args,
char** synopsis, const char* description, char* arguments[][2], char** examples,
CMD_CB callback);
/**
* Registers a function to be called periodically by CProof.
@@ -118,7 +118,7 @@ void prof_register_timed(TIMED_CB callback, int interval_seconds);
* prof_completer_add("/mycommand", items);
* @endcode
*/
void prof_completer_add(const char *key, char **items);
void prof_completer_add(const char* key, char** items);
/**
* Removes autocomplete values for a command or argument.
@@ -131,7 +131,7 @@ void prof_completer_add(const char *key, char **items);
* prof_completer_remove("/mycommand", items);
* @endcode
*/
void prof_completer_remove(const char *key, char **items);
void prof_completer_remove(const char* key, char** items);
/**
* Clears all autocomplete values for a command or argument.
@@ -142,7 +142,7 @@ void prof_completer_remove(const char *key, char **items);
* prof_completer_clear("/mycommand");
* @endcode
*/
void prof_completer_clear(const char *key);
void prof_completer_clear(const char* key);
/**
* Adds filepath autocompletion for a command or argument.
@@ -153,7 +153,7 @@ void prof_completer_clear(const char *key);
* prof_filepath_completer_add("/filecmd");
* @endcode
*/
void prof_filepath_completer_add(const char *prefix);
void prof_filepath_completer_add(const char* prefix);
/**
* Sends a desktop notification.
@@ -166,7 +166,7 @@ void prof_filepath_completer_add(const char *prefix);
* prof_notify("Example notification", 5000, "Example plugin");
* @endcode
*/
void prof_notify(const char *message, int timeout_ms, const char *category);
void prof_notify(const char* message, int timeout_ms, const char* category);
/**
* Sends a line of input to CProof to execute.
@@ -177,7 +177,7 @@ void prof_notify(const char *message, int timeout_ms, const char *category);
* prof_send_line("/who online");
* @endcode
*/
void prof_send_line(char *line);
void prof_send_line(char* line);
/**
* Gets the Jabber ID of the current chat recipient.
@@ -185,6 +185,17 @@ void prof_send_line(char *line);
*/
char* prof_get_current_recipient(void);
/**
* Gets the title of the current window as shown in the titlebar.
*
* Special cases:
* - MUC configuration window: Includes "config" and an asterisk if modified (e.g., "room@example.com config *").
* - vCard window: Includes the bare JID and an asterisk if modified (e.g., "vCard user@example.com *").
*
* @return A null-terminated string containing the window title. The caller must free the returned string.
*/
char* prof_get_current_window(void);
/**
* Gets the Jabber ID of the current chat room.
* @return The room Jabber ID (e.g., "metalchat@conference.example.com") or NULL if not in a room.
@@ -198,7 +209,7 @@ char* prof_get_current_muc(void);
int prof_current_win_is_console(void);
/**
* Gets the users nickname in the current chat room.
* Gets the user's 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);
@@ -208,14 +219,14 @@ char* prof_get_current_nick(void);
* @param barejid The Jabber ID.
* @return The nickname (e.g., "eddie") or the barejid if not in the roster.
*/
char* prof_get_name_from_roster(const char *barejid);
char* prof_get_name_from_roster(const char* barejid);
/**
* Gets the barejid for a nickname from the roster.
* @param name The nickname.
* @return The barejid (e.g., "eddie@server.tld") or NULL if not in the roster.
*/
char* prof_get_barejid_from_roster(const char *name);
char* prof_get_barejid_from_roster(const char* name);
/**
* Gets nicknames of all occupants in the current chat room.
@@ -225,10 +236,10 @@ char** prof_get_current_occupants(void);
/**
* Gets the current nickname in a chat room.
* @param barejid The rooms Jabber ID.
* @param barejid The room's Jabber ID.
* @return The nickname.
*/
char* prof_get_room_nick(const char *barejid);
char* prof_get_room_nick(const char* barejid);
/**
* Logs a debug message to the CProof log.
@@ -239,7 +250,7 @@ char* prof_get_room_nick(const char *barejid);
* prof_log_debug("Debug message");
* @endcode
*/
void prof_log_debug(const char *message);
void prof_log_debug(const char* message);
/**
* Logs an info message to the CProof log.
@@ -250,7 +261,7 @@ void prof_log_debug(const char *message);
* prof_log_info("Info message");
* @endcode
*/
void prof_log_info(const char *message);
void prof_log_info(const char* message);
/**
* Logs a warning message to the CProof log.
@@ -261,7 +272,7 @@ void prof_log_info(const char *message);
* prof_log_warning("Warning message");
* @endcode
*/
void prof_log_warning(const char *message);
void prof_log_warning(const char* message);
/**
* Logs an error message to the CProof log.
@@ -272,7 +283,7 @@ void prof_log_warning(const char *message);
* prof_log_error("Error message");
* @endcode
*/
void prof_log_error(const char *message);
void prof_log_error(const char* message);
/**
* Creates a plugin window.
@@ -321,7 +332,7 @@ int prof_win_focus(PROF_WIN_TAG win);
* prof_win_show("My Plugin", "Message in plugin window");
* @endcode
*/
int prof_win_show(PROF_WIN_TAG win, char *message);
int prof_win_show(PROF_WIN_TAG win, char* message);
/**
* Shows a message in a plugin window with a specified theme.
@@ -338,7 +349,7 @@ int prof_win_show(PROF_WIN_TAG win, char *message);
* prof_win_show_themed("My Plugin", "myplugin", "text", NULL, "Themed message");
* @endcode
*/
int prof_win_show_themed(PROF_WIN_TAG tag, char *group, char *key, char *def, char *message);
int prof_win_show_themed(PROF_WIN_TAG tag, char* group, char* key, char* def, char* message);
/**
* Sends an XMPP stanza.
@@ -350,7 +361,7 @@ int prof_win_show_themed(PROF_WIN_TAG tag, char *group, char *key, char *def, ch
* prof_send_stanza("<iq to='juliet@capulet.lit' id='s2c1' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
* @endcode
*/
int prof_send_stanza(char *stanza);
int prof_send_stanza(char* stanza);
/**
* Gets a boolean setting.
@@ -365,7 +376,7 @@ int prof_send_stanza(char *stanza);
* prof_settings_boolean_get("myplugin", "notify", 0);
* @endcode
*/
int prof_settings_boolean_get(char *group, char *key, int def);
int prof_settings_boolean_get(char* group, char* key, int def);
/**
* Sets a boolean setting.
@@ -379,7 +390,7 @@ int prof_settings_boolean_get(char *group, char *key, int def);
* prof_settings_boolean_set("myplugin", "activate", 1);
* @endcode
*/
void prof_settings_boolean_set(char *group, char *key, int value);
void prof_settings_boolean_set(char* group, char* key, int value);
/**
* Gets a string setting.
@@ -394,7 +405,7 @@ void prof_settings_boolean_set(char *group, char *key, int value);
* prof_settings_string_get("myplugin", "prefix", "prefix-->");
* @endcode
*/
char* prof_settings_string_get(char *group, char *key, char *def);
char* prof_settings_string_get(char* group, char* key, char* def);
/**
* Sets a string setting.
@@ -408,7 +419,7 @@ char* prof_settings_string_get(char *group, char *key, char *def);
* prof_settings_string_set("myplugin", "prefix", "myplugin, ");
* @endcode
*/
void prof_settings_string_set(char *group, char *key, char *value);
void prof_settings_string_set(char* group, char* key, char* value);
/**
* Gets a string list setting, separated by semicolons.
@@ -422,7 +433,7 @@ void prof_settings_string_set(char *group, char *key, char *value);
* prof_settings_string_list_get("myplugin", "somelist");
* @endcode
*/
char** prof_settings_string_list_get(char *group, char *key);
char** prof_settings_string_list_get(char* group, char* key);
/**
* Adds an item to a string list setting.
@@ -437,7 +448,7 @@ char** prof_settings_string_list_get(char *group, char *key);
* prof_settings_string_list_add("myplugin", "somelist", "anelement");
* @endcode
*/
void prof_settings_string_list_add(char *group, char *key, char *value);
void prof_settings_string_list_add(char* group, char* key, char* value);
/**
* Removes an item from a string list setting.
@@ -452,7 +463,7 @@ void prof_settings_string_list_add(char *group, char *key, char *value);
* prof_settings_string_list_remove("myplugin", "somelist", "anelement");
* @endcode
*/
int prof_settings_string_list_remove(char *group, char *key, char *value);
int prof_settings_string_list_remove(char* group, char* key, char* value);
/**
* Clears all items from a string list setting.
@@ -466,7 +477,7 @@ int prof_settings_string_list_remove(char *group, char *key, char *value);
* prof_settings_string_list_clear("myplugin", "somelist");
* @endcode
*/
int prof_settings_string_list_clear(char *group, char *key);
int prof_settings_string_list_clear(char* group, char* key);
/**
* Gets an integer setting.
@@ -481,7 +492,7 @@ int prof_settings_string_list_clear(char *group, char *key);
* prof_settings_int_get("myplugin", "timeout", 10);
* @endcode
*/
int prof_settings_int_get(char *group, char *key, int def);
int prof_settings_int_get(char* group, char* key, int def);
/**
* Sets an integer setting.
@@ -495,7 +506,7 @@ int prof_settings_int_get(char *group, char *key, int def);
* prof_settings_int_set("myplugin", "timeout", 100);
* @endcode
*/
void prof_settings_int_set(char *group, char *key, int value);
void prof_settings_int_set(char* group, char* key, int value);
/**
* Triggers handling of an incoming message as if received by CProof.
@@ -508,10 +519,10 @@ void prof_settings_int_set(char *group, char *key, int value);
* prof_incoming_message("bob@server.org", "laptop", "Hello there");
* @endcode
*/
void prof_incoming_message(char *barejid, char *resource, char *message);
void prof_incoming_message(char* barejid, char* resource, char* message);
/**
* Adds a service discovery feature to CProofs supported features.
* Adds a service discovery feature to CProof's supported features.
* Sends a presence update if a session is connected.
* @param feature The feature to add.
*
@@ -520,7 +531,7 @@ void prof_incoming_message(char *barejid, char *resource, char *message);
* prof_disco_add_feature("urn:xmpp:omemo:0:devicelist+notify");
* @endcode
*/
void prof_disco_add_feature(char *feature);
void prof_disco_add_feature(char* feature);
/**
* Ends an encrypted session with a user.
@@ -531,7 +542,7 @@ void prof_disco_add_feature(char *feature);
* prof_encryption_reset("alice@server.org");
* @endcode
*/
void prof_encryption_reset(char *barejid);
void prof_encryption_reset(char* barejid);
/**
* Sets the titlebar encryption indicator text for a recipient.
@@ -544,10 +555,10 @@ void prof_encryption_reset(char *barejid);
* prof_chat_set_titlebar_enctext("bob@example.com", "safe");
* @endcode
*/
int prof_chat_set_titlebar_enctext(char *barejid, char *enctext);
int prof_chat_set_titlebar_enctext(char* barejid, char* enctext);
/**
* Resets the titlebar encryption indicator for a recipient to CProofs default.
* Resets the titlebar encryption indicator for a recipient to CProof's default.
* @param barejid Jabber ID of the recipient.
* @return 1 on success, 0 on failure.
*
@@ -556,7 +567,7 @@ int prof_chat_set_titlebar_enctext(char *barejid, char *enctext);
* prof_chat_unset_titlebar_enctext("bob@example.com");
* @endcode
*/
int prof_chat_unset_titlebar_enctext(char *barejid);
int prof_chat_unset_titlebar_enctext(char* barejid);
/**
* Sets the incoming message prefix character for a contact.
@@ -569,7 +580,7 @@ int prof_chat_unset_titlebar_enctext(char *barejid);
* prof_chat_set_incoming_char("kristine@example.com", "*");
* @endcode
*/
int prof_chat_set_incoming_char(char *barejid, char *ch);
int prof_chat_set_incoming_char(char* barejid, char* ch);
/**
* Resets the incoming message prefix character for a contact.
@@ -581,7 +592,7 @@ int prof_chat_set_incoming_char(char *barejid, char *ch);
* prof_chat_unset_incoming_char("kristine@example.com");
* @endcode
*/
int prof_chat_unset_incoming_char(char *barejid);
int prof_chat_unset_incoming_char(char* barejid);
/**
* Sets the outgoing message prefix character for a contact.
@@ -594,7 +605,7 @@ int prof_chat_unset_incoming_char(char *barejid);
* prof_chat_set_outgoing_char("david@example.com", "+");
* @endcode
*/
int prof_chat_set_outgoing_char(char *barejid, char *ch);
int prof_chat_set_outgoing_char(char* barejid, char* ch);
/**
* Resets the outgoing message prefix character for a contact.
@@ -606,7 +617,7 @@ int prof_chat_set_outgoing_char(char *barejid, char *ch);
* prof_chat_unset_outgoing_char("david@example.com");
* @endcode
*/
int prof_chat_unset_outgoing_char(char *barejid);
int prof_chat_unset_outgoing_char(char* barejid);
/**
* Sets the titlebar encryption indicator text for a room.
@@ -619,10 +630,10 @@ int prof_chat_unset_outgoing_char(char *barejid);
* prof_room_set_titlebar_enctext("generalchat@conference.service.com", "secret");
* @endcode
*/
int prof_room_set_titlebar_enctext(char *roomjid, char *enctext);
int prof_room_set_titlebar_enctext(char* roomjid, char* enctext);
/**
* Resets the titlebar encryption indicator for a room to CProofs default.
* Resets the titlebar encryption indicator for a room to CProof's default.
* @param roomjid Jabber ID of the room.
* @return 1 on success, 0 on failure.
*
@@ -631,7 +642,7 @@ int prof_room_set_titlebar_enctext(char *roomjid, char *enctext);
* prof_room_unset_titlebar_enctext("generalchat@conference.service.com");
* @endcode
*/
int prof_room_unset_titlebar_enctext(char *roomjid);
int prof_room_unset_titlebar_enctext(char* roomjid);
/**
* Sets the message prefix character for a room.
@@ -644,7 +655,7 @@ int prof_room_unset_titlebar_enctext(char *roomjid);
* prof_room_set_message_char("ohnoes@conference.example.com", "^");
* @endcode
*/
int prof_room_set_message_char(char *roomjid, char *ch);
int prof_room_set_message_char(char* roomjid, char* ch);
/**
* Resets the message prefix character for a room.
@@ -656,7 +667,7 @@ int prof_room_set_message_char(char *roomjid, char *ch);
* prof_room_unset_message_char("ohnoes@conference.example.com");
* @endcode
*/
int prof_room_unset_message_char(char *roomjid);
int prof_room_unset_message_char(char* roomjid);
/**
* Shows a message in a chat window.
@@ -669,7 +680,7 @@ int prof_room_unset_message_char(char *roomjid);
* prof_chat_show("bob@server.org", "From a plugin in the chat window");
* @endcode
*/
int prof_chat_show(char *barejid, char *message);
int prof_chat_show(char* barejid, char* message);
/**
* Shows a message in a chat window with a theme and prefix character.
@@ -687,7 +698,7 @@ int prof_chat_show(char *barejid, char *message);
* prof_chat_show_themed("bob@server.org", "myplugin", "text", NULL, "!", "Themed message");
* @endcode
*/
int prof_chat_show_themed(char *barejid, char *group, char *item, char *def, char *ch, char *message);
int prof_chat_show_themed(char* barejid, char* group, char* item, char* def, char* ch, char* message);
/**
* Shows a message in a chat room window.
@@ -700,7 +711,7 @@ int prof_chat_show_themed(char *barejid, char *group, char *item, char *def, cha
* prof_room_show("chat@conference.example.com", "From a plugin in the chat room");
* @endcode
*/
int prof_room_show(char *roomjid, char *message);
int prof_room_show(char* roomjid, char* message);
/**
* Shows a message in a chat room window with a theme and prefix character.
@@ -718,4 +729,4 @@ int prof_room_show(char *roomjid, char *message);
* prof_room_show_themed("chat@conference.example.com", "myplugin", "text", NULL, "!", "Themed message");
* @endcode
*/
int prof_room_show_themed(char *roomjid, char *group, char *item, char *def, char *ch, char *message);
int prof_room_show_themed(char* roomjid, char* group, char* item, char* def, char* ch, char* message);

View File

@@ -830,6 +830,22 @@ def get_current_recipient() -> str | None:
"""
pass
def get_current_window() -> str:
"""Retrieves the title of the current window as shown in the titlebar.
Returns the title of the currently active window, matching the titlebar display.
Special cases:
- For MUC configuration windows, includes "config" and an asterisk if modified (e.g., "room@example.com config \*").
- For vCard windows, includes the bare JID and an asterisk if modified (e.g., "vCard user@example.com \*").
:return: The window title as a string.
Example::
title = prof.get_current_window()
prof.cons_show(f"Current window: {title}")
"""
def get_current_muc() -> str | None:
"""Retrieves the Jabber ID of the current chat room.

View File

@@ -139,8 +139,9 @@ static const struct cmd_t command_defs[] = {
"Show version and license information.")
},
// Max args: account + server <s> + port <p> + tls <t> + auth <a> = 9
{ CMD_PREAMBLE("/connect",
parse_args, 0, 7, NULL)
parse_args, 0, 9, NULL)
CMD_MAINFUNC(cmd_connect)
CMD_TAGS(
CMD_TAG_CONNECTION)
@@ -1902,9 +1903,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 (10MB)." },
{ "maxsize <bytes>", "With rotate enabled, specifies the max log size, defaults to 10485760 (10MiB)." },
{ "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. Only works with default log file, not with user provided log file during startup via -f." })
{ "level INFO|DEBUG|WARN|ERROR", "Set the log level. Default is INFO." })
},
{ CMD_PREAMBLE("/carbons",

View File

@@ -5356,6 +5356,8 @@ cmd_time(ProfWin* window, const char* const command, gchar** args)
cons_bad_cmd_usage(command);
return TRUE;
}
if (!set_all)
break;
}
if (!set_all && n == ARRAY_SIZE(time_prefs)) {
cons_bad_cmd_usage(command);
@@ -6271,10 +6273,11 @@ 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, NULL);
log_init(prof_log_level, log_file);
cons_show("Log level changed to: %s.", value);
cons_show("Log level changed to: %s (log file: %s).", value, log_file ? log_file : "[default]");
return TRUE;
}
}

View File

@@ -186,7 +186,7 @@ log_error(const char* const msg, ...)
}
void
log_init(log_level_t filter, char* log_file)
log_init(log_level_t filter, const char* const 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, char* log_file);
void log_init(log_level_t filter, const char* const log_file);
log_level_t log_get_filter(void);
void log_close(void);
const gchar* get_log_file_location(void);

View File

@@ -54,6 +54,7 @@
#include "plugins/settings.h"
#include "plugins/disco.h"
#include "ui/ui.h"
#include "ui/win_types.h"
#include "ui/window_list.h"
#include "xmpp/roster_list.h"
@@ -205,6 +206,13 @@ api_get_current_recipient(void)
}
}
gchar*
api_get_current_window(void)
{
ProfWin* current = wins_get_current();
return win_get_title(current);
}
char*
api_get_current_muc(void)
{

View File

@@ -46,6 +46,7 @@ void api_notify(const char* message, const char* category, int timeout_ms);
void api_send_line(char* line);
char* api_get_current_recipient(void);
gchar* api_get_current_window(void);
char* api_get_current_muc(void);
gboolean api_current_win_is_console(void);
char* api_get_current_nick(void);

View File

@@ -165,6 +165,12 @@ c_api_get_current_recipient(void)
return api_get_current_recipient();
}
static char*
c_api_get_current_window(void)
{
return api_get_current_window();
}
static char*
c_api_get_current_muc(void)
{
@@ -477,6 +483,7 @@ c_api_init(void)
prof_notify = c_api_notify;
prof_send_line = c_api_send_line;
prof_get_current_recipient = c_api_get_current_recipient;
prof_get_current_window = c_api_get_current_window;
prof_get_current_muc = c_api_get_current_muc;
prof_current_win_is_console = c_api_current_win_is_console;
prof_get_current_nick = c_api_get_current_nick;

View File

@@ -62,6 +62,7 @@ void (*prof_notify)(const char* message, int timeout_ms, const char* category) =
void (*prof_send_line)(char* line) = NULL;
char* (*prof_get_current_recipient)(void) = NULL;
char* (*prof_get_current_window)(void) = NULL;
char* (*prof_get_current_muc)(void) = NULL;
int (*prof_current_win_is_console)(void) = NULL;
char* (*prof_get_current_nick)(void) = NULL;

View File

@@ -71,6 +71,7 @@ void (*prof_notify)(const char* message, int timeout_ms, const char* category);
void (*prof_send_line)(char* line);
char* (*prof_get_current_recipient)(void);
char* (*prof_get_current_window)(void);
char* (*prof_get_current_muc)(void);
int (*prof_current_win_is_console)(void);
char* (*prof_get_current_nick)(void);

View File

@@ -415,6 +415,15 @@ python_api_get_current_recipient(PyObject* self, PyObject* args)
}
}
static PyObject*
python_api_get_current_window(PyObject* self, PyObject* args)
{
allow_python_threads();
auto_gchar gchar* recipient = api_get_current_window();
disable_python_threads();
return Py_BuildValue("s", recipient);
}
static PyObject*
python_api_get_current_muc(PyObject* self, PyObject* args)
{
@@ -1530,6 +1539,7 @@ static PyMethodDef apiMethods[] = {
{ "send_line", python_api_send_line, METH_VARARGS, "Send a line of input." },
{ "notify", python_api_notify, METH_VARARGS, "Send desktop notification." },
{ "get_current_recipient", python_api_get_current_recipient, METH_VARARGS, "Return the jid of the recipient of the current window." },
{ "get_current_window", python_api_get_current_window, METH_VARARGS, "Return title of the current window." },
{ "get_current_muc", python_api_get_current_muc, METH_VARARGS, "Return the jid of the room of the current window." },
{ "get_current_nick", python_api_get_current_nick, METH_VARARGS, "Return nickname in current room." },
{ "get_name_from_roster", python_api_get_name_from_roster, METH_VARARGS, "Return nickname in roster of barejid." },

View File

@@ -1,40 +1,36 @@
/*
* buffer.c
* vim: expandtab:ts=4:sts=4:sw=4
*
* 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).
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
* Copyright (C) 2019 - 2025 Michael Vetter <jubalh@iodoru.org>
* Copyright (C) 2025 CProof Developers
*
* 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.
* Licensed under the GNU General Public License, version 3,
* with the OpenSSL exception. See LICENSE for details.
*
* vim: expandtab:ts=4:sts=4:sw=4
*/
#include "config.h"
#include "ui/window_list.h"
#include <stdlib.h>
#include <string.h>
@@ -54,15 +50,8 @@
#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);
@@ -108,23 +97,24 @@ _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) {
// With 10% margin to ensure overflow prevention
while (buffer->lines >= PAD_SIZE - (PAD_SIZE / 10) && g_slist_length(buffer->entries) > 1) {
// 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) {
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.
// 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);
}
buffer->entries = append ? g_slist_append(buffer->entries, e) : g_slist_prepend(buffer->entries, e);

View File

@@ -68,6 +68,12 @@ 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

@@ -2003,6 +2003,10 @@ 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

@@ -312,6 +312,7 @@ iq_reg2_cb(xmpp_conn_t* xmpp_conn, xmpp_stanza_t* stanza, void* userdata)
goto quit;
quit:
log_debug("[CONNDBG] iq_reg2_cb: disconnecting after registration completion");
xmpp_disconnect(xmpp_conn);
return 0;
@@ -550,6 +551,7 @@ connection_disconnect(void)
// don't disconnect already disconnected connection,
// or we get infinite loop otherwise
if (conn.conn_last_event == XMPP_CONN_CONNECT) {
log_debug("[CONNDBG] connection_disconnect: user-initiated disconnect (status=%d)", (int)conn.conn_status);
conn.conn_status = JABBER_DISCONNECTING;
xmpp_disconnect(conn.xmpp_conn);
@@ -557,6 +559,7 @@ connection_disconnect(void)
session_process_events();
}
} else {
log_debug("[CONNDBG] connection_disconnect: already disconnected (last_event=%d)", (int)conn.conn_last_event);
conn.conn_status = JABBER_DISCONNECTED;
}
@@ -979,6 +982,7 @@ _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);
@@ -1018,6 +1022,9 @@ _connection_handler(xmpp_conn_t* const xmpp_conn, const xmpp_conn_event_t status
// disconnected
case XMPP_CONN_DISCONNECT:
log_debug("Connection handler: XMPP_CONN_DISCONNECT");
log_debug("[CONNDBG] disconnect: previous_status=%d error=%d has_stream_error=%s",
(int)conn.conn_status, error,
(stream_error && stream_error->stanza) ? "yes" : "no");
// lost connection for unknown reason
if (conn.conn_status == JABBER_CONNECTED || conn.conn_status == JABBER_DISCONNECTING) {
@@ -1163,3 +1170,32 @@ 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,6 +1417,8 @@ _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)
{
@@ -1425,10 +1427,13 @@ _autoping_timed_send(xmpp_conn_t* const conn, void* const userdata)
}
if (connection_supports(XMPP_FEATURE_PING) == FALSE) {
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;
// 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;
}
if (autoping_wait) {
@@ -1499,7 +1504,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("Server ping not supported, autoping disabled.");
cons_show_error("Autoping is not supported by the server (stanza cancelled). The feature has been disabled automatically.");
xmpp_timed_handler_delete(connection_get_conn(), _autoping_timed_send);
}
@@ -2515,6 +2520,7 @@ _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

@@ -218,6 +218,7 @@ session_connect_with_details(const char* const jid, const char* const passwd, co
void
session_autoping_fail(void)
{
log_debug("[CONNDBG] session_autoping_fail: autoping timeout, triggering lost connection");
session_lost_connection();
}
@@ -264,11 +265,14 @@ session_process_events(void)
if ((reconnect_sec != 0) && reconnect_timer) {
int elapsed_sec = g_timer_elapsed(reconnect_timer, NULL);
if (elapsed_sec > reconnect_sec) {
log_debug("[CONNDBG] session_process_events: auto-reconnect triggered after %d seconds (threshold=%d)",
elapsed_sec, reconnect_sec);
session_reconnect_now();
}
}
break;
case JABBER_RECONNECT:
log_debug("[CONNDBG] session_process_events: JABBER_RECONNECT state, calling session_reconnect_now");
session_reconnect_now();
break;
default:
@@ -327,12 +331,12 @@ session_login_success(gboolean secured)
// logged in with account
if (saved_account.name) {
log_debug("Connection handler: logged in with account name: %s", saved_account.name);
log_debug("[CONNDBG] Connection handler: logged in with account name: %s", saved_account.name);
sv_ev_login_account_success(saved_account.name, secured);
// logged in without account, use details to create new account
} else {
log_debug("Connection handler: logged in with jid: %s", saved_details.name);
log_debug("[CONNDBG] Connection handler: logged in with jid: %s", saved_details.name);
accounts_add(saved_details.name, saved_details.altdomain, saved_details.port, saved_details.tls_policy, saved_details.auth_policy);
accounts_set_jid(saved_details.name, saved_details.jid);
@@ -371,11 +375,11 @@ void
session_login_failed(void)
{
if (reconnect_timer == NULL) {
log_debug("Connection handler: No reconnect timer");
log_debug("[CONNDBG] Connection handler: No reconnect timer");
sv_ev_failed_login();
_session_free_internals();
} else {
log_debug("Connection handler: Restarting reconnect timer");
log_debug("[CONNDBG] Connection handler: Restarting reconnect timer");
if (prefs_get_reconnect() != 0) {
g_timer_start(reconnect_timer);
}
@@ -389,12 +393,16 @@ session_login_failed(void)
void
session_lost_connection(void)
{
log_debug("[CONNDBG] session_lost_connection: connection lost, reconnect_interval=%d",
prefs_get_reconnect());
/* this callback also clears all cached data */
sv_ev_lost_connection();
if (prefs_get_reconnect() != 0) {
assert(reconnect_timer == NULL);
reconnect_timer = g_timer_new();
log_debug("[CONNDBG] session_lost_connection: reconnect timer started");
} else {
log_debug("[CONNDBG] session_lost_connection: auto-reconnect disabled, cleaning up");
_session_free_internals();
}
}

View File

@@ -208,6 +208,8 @@ 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, char* log_file)
log_init(log_level_t filter, const char* const log_file)
{
}
log_level_t

View File

@@ -156,6 +156,11 @@ 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)