From c9a3823f13414e7c82a24a7f955aa542097a0fd0 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 13:33:51 +0200 Subject: [PATCH 1/6] fix: restore TTY access for eval_password commands Replace g_spawn_command_line_sync with g_spawn_sync with the G_SPAWN_CHILD_INHERITS_STDIN flag. This is actually needed to so that interactive commands can access the terminal. Otherwise they cannot ask the user for the passphrase. Ref: f5787fb31f9b287458c8f90e0fb8745026da7342 Fixes: https://github.com/profanity-im/profanity/issues/2143 Signed-off-by: Michael Vetter --- src/command/cmd_funcs.c | 1 + src/config/account.c | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 4d7a821bf..ca58fc159 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -385,6 +385,7 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) // use eval_password if set } else if (account->eval_password) { gboolean res = account_eval_password(account); + ui_resize(); if (res) { conn_status = cl_ev_connect_account(account); free(account->password); diff --git a/src/config/account.c b/src/config/account.c index cf9650f61..76f96bfb2 100644 --- a/src/config/account.c +++ b/src/config/account.c @@ -138,13 +138,23 @@ account_eval_password(ProfAccount* account) gchar* stdout_buf = NULL; GError* error = NULL; gint exit_status = 0; + gchar** argv = NULL; - if (!g_spawn_command_line_sync(account->eval_password, &stdout_buf, NULL, &exit_status, &error)) { + if (!g_shell_parse_argv(account->eval_password, NULL, &argv, &error)) { + log_error("Failed to parse `eval_password` command: %s", error->message); + g_error_free(error); + return FALSE; + } + + if (!g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN, NULL, NULL, &stdout_buf, NULL, &exit_status, &error)) { log_error("Failed to execute `eval_password` command: %s", error->message); + g_strfreev(argv); g_error_free(error); return FALSE; } + g_strfreev(argv); + if (WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == 0) { account->password = stdout_buf; g_strstrip(account->password); From 8675f7be0cef6f02ab61c026614704a8a1e3a56e Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 20:36:14 +0200 Subject: [PATCH 2/6] fix: Fix removal of entries in account file Commit 81f92f1fe introduced a selective update mechanism to _accounts_save() to prevent overwrites when running multiple instances by only modifying the specific account being saved. Commit 5c484c fixed a problem with that commit regarding empty accounts. It turns there was another bug introduced: The new implementation only set the keys that existed in memory. So removal of keys was not possible anymore. Fixes: 81f92f1fed8efc71933cd050510c827c0dcf9e28 Ref: 5c484c26ed775462511fb0e42f757b85416f7fb2 Signed-off-by: Michael Vetter --- src/command/cmd_funcs.c | 2 ++ src/config/accounts.c | 11 +++++++++++ src/ui/core.c | 12 ++++++++++++ src/ui/ui.h | 2 ++ tests/unittests/ui/stub_ui.c | 8 ++++++++ 5 files changed, 35 insertions(+) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index ca58fc159..64edb5159 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -384,7 +384,9 @@ cmd_connect(ProfWin* window, const char* const command, gchar** args) // use eval_password if set } else if (account->eval_password) { + ui_suspend(); gboolean res = account_eval_password(account); + ui_resume(); ui_resize(); if (res) { conn_status = cl_ev_connect_account(account); diff --git a/src/config/accounts.c b/src/config/accounts.c index 6bd77e7bb..3938f4a3f 100644 --- a/src/config/accounts.c +++ b/src/config/accounts.c @@ -75,6 +75,17 @@ _accounts_save(const char* account_name) auto_gchar gchar* sanitized = _sanitize_account_name(account_name); if (_accounts_has_group(sanitized)) { + // Remove keys from file that are no longer in memory + gsize nkeys_disk; + auto_gcharv gchar** keys_disk = g_key_file_get_keys(current.keyfile, sanitized, &nkeys_disk, NULL); + if (keys_disk) { + for (gsize j = 0; j < nkeys_disk; ++j) { + if (!g_key_file_has_key(accounts_prof_keyfile.keyfile, sanitized, keys_disk[j], NULL)) { + g_key_file_remove_key(current.keyfile, sanitized, keys_disk[j], NULL); + } + } + } + gsize nkeys; auto_gcharv gchar** keys = g_key_file_get_keys(accounts_prof_keyfile.keyfile, sanitized, &nkeys, NULL); if (keys) { diff --git a/src/ui/core.c b/src/ui/core.c index bec9f534b..ac287383e 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -171,6 +171,18 @@ ui_reset_idle_time(void) g_timer_start(ui_idle_time); } +void +ui_suspend(void) +{ + endwin(); +} + +void +ui_resume(void) +{ + refresh(); +} + void ui_resize(void) { diff --git a/src/ui/ui.h b/src/ui/ui.h index 4ab2ac539..31b0a961d 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -36,6 +36,8 @@ // core UI void ui_init(void); +void ui_suspend(void); +void ui_resume(void); void ui_load_colours(void); void ui_update(void); void ui_redraw(void); diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c index 803288251..2f46dd708 100644 --- a/tests/unittests/ui/stub_ui.c +++ b/tests/unittests/ui/stub_ui.c @@ -62,6 +62,14 @@ ui_init(void) { } void +ui_suspend(void) +{ +} +void +ui_resume(void) +{ +} +void ui_load_colours(void) { } From fc66ddc2c10f55af68fba6f662804080a2db7ccf Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 21:11:39 +0200 Subject: [PATCH 3/6] refactor: Use helper functions in vcard related code There was way too much repetition here. This is in preparation for future changes regarding the editor. Signed-off-by: Michael Vetter --- src/command/cmd_funcs.c | 278 ++++++++-------------------------------- 1 file changed, 52 insertions(+), 226 deletions(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 64edb5159..efcea89f5 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -109,6 +109,28 @@ static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gbool static gboolean _download_install_plugin(ProfWin* window, gchar* url, gchar* path); +static gboolean +_update_vcard_field(char** field, char* value) +{ + if (!value) { + gchar* editor_value; + if (get_message_from_editor(*field, &editor_value)) { + return TRUE; + } + + if (*field) { + free(*field); + } + *field = editor_value; + } else { + if (*field) { + free(*field); + } + *field = strdup(value); + } + return FALSE; +} + /** * @brief Processes a line of input and determines if profanity should continue. * @@ -10113,21 +10135,8 @@ cmd_vcard_set(ProfWin* window, const char* const command, gchar** args) switch (element->type) { case VCARD_NICKNAME: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->nickname, &editor_value)) { - return TRUE; - } - - if (element->nickname) { - free(element->nickname); - } - element->nickname = editor_value; - } else { - if (element->nickname) { - free(element->nickname); - } - element->nickname = strdup(value); + if (_update_vcard_field(&element->nickname, value)) { + return TRUE; } break; case VCARD_BIRTHDAY: @@ -10143,130 +10152,38 @@ cmd_vcard_set(ProfWin* window, const char* const command, gchar** args) element->birthday = g_date_time_new_local(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 0, 0, 0); break; case VCARD_TELEPHONE: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->telephone.number, &editor_value)) { - return TRUE; - } - - if (element->telephone.number) { - free(element->telephone.number); - } - element->telephone.number = editor_value; - } else { - if (element->telephone.number) { - free(element->telephone.number); - } - element->telephone.number = strdup(value); + if (_update_vcard_field(&element->telephone.number, value)) { + return TRUE; } - break; case VCARD_EMAIL: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->email.userid, &editor_value)) { - return TRUE; - } - - if (element->email.userid) { - free(element->email.userid); - } - element->email.userid = editor_value; - } else { - if (element->email.userid) { - free(element->email.userid); - } - element->email.userid = strdup(value); + if (_update_vcard_field(&element->email.userid, value)) { + return TRUE; } break; case VCARD_JID: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->jid, &editor_value)) { - return TRUE; - } - - if (element->jid) { - free(element->jid); - } - element->jid = editor_value; - } else { - if (element->jid) { - free(element->jid); - } - element->jid = strdup(value); + if (_update_vcard_field(&element->jid, value)) { + return TRUE; } break; case VCARD_TITLE: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->title, &editor_value)) { - return TRUE; - } - - if (element->title) { - free(element->title); - } - element->title = editor_value; - } else { - if (element->title) { - free(element->title); - } - element->title = strdup(value); + if (_update_vcard_field(&element->title, value)) { + return TRUE; } break; case VCARD_ROLE: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->role, &editor_value)) { - return TRUE; - } - - if (element->role) { - free(element->role); - } - element->role = editor_value; - } else { - if (element->role) { - free(element->role); - } - element->role = strdup(value); + if (_update_vcard_field(&element->role, value)) { + return TRUE; } break; case VCARD_NOTE: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->note, &editor_value)) { - return TRUE; - } - - if (element->note) { - free(element->note); - } - element->note = editor_value; - } else { - if (element->note) { - free(element->note); - } - element->note = strdup(value); + if (_update_vcard_field(&element->note, value)) { + return TRUE; } break; case VCARD_URL: - if (!value) { - gchar* editor_value; - if (get_message_from_editor(element->url, &editor_value)) { - return TRUE; - } - - if (element->url) { - free(element->url); - } - element->url = editor_value; - } else { - if (element->url) { - free(element->url); - } - element->url = strdup(value); + if (_update_vcard_field(&element->url, value)) { + return TRUE; } break; default: @@ -10274,123 +10191,32 @@ cmd_vcard_set(ProfWin* window, const char* const command, gchar** args) } } else if (value) { if (g_strcmp0(value, "pobox") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.pobox, &editor_value)) { - return TRUE; - } - - if (element->address.pobox) { - free(element->address.pobox); - } - element->address.pobox = editor_value; - } else { - if (element->address.pobox) { - free(element->address.pobox); - } - element->address.pobox = strdup(value2); + if (_update_vcard_field(&element->address.pobox, value2)) { + return TRUE; } } else if (g_strcmp0(value, "extaddr") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.extaddr, &editor_value)) { - return TRUE; - } - - if (element->address.extaddr) { - free(element->address.extaddr); - } - element->address.extaddr = editor_value; - } else { - if (element->address.extaddr) { - free(element->address.extaddr); - } - element->address.extaddr = strdup(value2); + if (_update_vcard_field(&element->address.extaddr, value2)) { + return TRUE; } } else if (g_strcmp0(value, "street") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.street, &editor_value)) { - return TRUE; - } - - if (element->address.street) { - free(element->address.street); - } - element->address.street = editor_value; - } else { - if (element->address.street) { - free(element->address.street); - } - element->address.street = strdup(value2); + if (_update_vcard_field(&element->address.street, value2)) { + return TRUE; } } else if (g_strcmp0(value, "locality") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.locality, &editor_value)) { - return TRUE; - } - - if (element->address.locality) { - free(element->address.locality); - } - element->address.locality = editor_value; - } else { - if (element->address.locality) { - free(element->address.locality); - } - element->address.locality = strdup(value2); + if (_update_vcard_field(&element->address.locality, value2)) { + return TRUE; } } else if (g_strcmp0(value, "region") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.region, &editor_value)) { - return TRUE; - } - - if (element->address.region) { - free(element->address.region); - } - element->address.region = editor_value; - } else { - if (element->address.region) { - free(element->address.region); - } - element->address.region = strdup(value2); + if (_update_vcard_field(&element->address.region, value2)) { + return TRUE; } } else if (g_strcmp0(value, "pocode") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.pcode, &editor_value)) { - return TRUE; - } - - if (element->address.pcode) { - free(element->address.pcode); - } - element->address.pcode = editor_value; - } else { - if (element->address.pcode) { - free(element->address.pcode); - } - element->address.pcode = strdup(value2); + if (_update_vcard_field(&element->address.pcode, value2)) { + return TRUE; } } else if (g_strcmp0(value, "country") == 0 && element->type == VCARD_ADDRESS) { - if (!value2) { - gchar* editor_value; - if (get_message_from_editor(element->address.country, &editor_value)) { - return TRUE; - } - - if (element->address.country) { - free(element->address.country); - } - element->address.country = editor_value; - } else { - if (element->address.country) { - free(element->address.country); - } - element->address.country = strdup(value2); + if (_update_vcard_field(&element->address.country, value2)) { + return TRUE; } } else if (g_strcmp0(value, "type") == 0 && element->type == VCARD_ADDRESS) { if (g_strcmp0(value2, "domestic") == 0) { From 36ec2b0ae1fa7afcdfe6f166323955b8bd121b0f Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 21:30:31 +0200 Subject: [PATCH 4/6] feat: implement asynchronous external editor support Move from blocking fork/wait logic to nonblocking fork and g_child_watch_add. This ensures that the Profanity main loop continues to run while an external editor is open. So we don't loose connection and react to pings. We change editor handling also in vcard and muc subject editing. In the new implementation we are launching the editor and passing a callback which we will use once the editor exited. We use the recently added ui_susped() and ui_resume(). To not clutter the UI we need to check whether Profanity UI is suspended and omit drawing in this case. Fixes: https://github.com/profanity-im/profanity/issues/1888 Ref: 9b112904a9bc7250dc013d901187ca8622580d98 Signed-off-by: Michael Vetter --- src/command/cmd_funcs.c | 106 ++++++++++++++++++++++-------------- src/tools/editor.c | 115 +++++++++++++++++++++++++++------------- src/tools/editor.h | 2 +- src/ui/core.c | 15 ++++++ src/ui/inputwin.c | 27 ++++++---- 5 files changed, 177 insertions(+), 88 deletions(-) diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index efcea89f5..25649a953 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -109,19 +109,26 @@ static gboolean _cmd_execute_alias(ProfWin* window, const char* const inp, gbool static gboolean _download_install_plugin(ProfWin* window, gchar* url, gchar* path); +static void +_vcard_editor_finished_cb(gchar* message, void* user_data) +{ + if (message) { + char** field = user_data; + if (*field) { + free(*field); + } + *field = g_strdup(message); + cons_show("Field updated. Remember to call /me save to apply changes."); + } +} + static gboolean _update_vcard_field(char** field, char* value) { if (!value) { - gchar* editor_value; - if (get_message_from_editor(*field, &editor_value)) { + if (launch_editor(*field, _vcard_editor_finished_cb, field)) { return TRUE; } - - if (*field) { - free(*field); - } - *field = editor_value; } else { if (*field) { free(*field); @@ -1640,6 +1647,51 @@ cmd_help(ProfWin* window, const char* const command, gchar** args) return TRUE; } +static void +_cmd_editor_finished_cb(gchar* message, void* user_data) +{ + if (message) { + rl_insert_text(message); + rl_point = rl_end; + rl_forced_update_display(); + } +} + +typedef struct +{ + ProfWin* window; + char* roomjid; +} EditorFinishedContext; + +static void +_cmd_room_editor_finished_cb(gchar* message, void* user_data) +{ + EditorFinishedContext* ctx = user_data; + if (message && ctx->roomjid) { + message_send_groupchat_subject(ctx->roomjid, message); + } + if (ctx) { + g_free(ctx->roomjid); + g_free(ctx); + } +} + +static void +_cmd_correct_editor_finished_cb(gchar* message, void* user_data) +{ + EditorFinishedContext* ctx = user_data; + if (message && ctx->window) { + if (ctx->window->type == WIN_CHAT) { + ProfChatWin* chatwin = (ProfChatWin*)ctx->window; + cl_ev_send_msg_correct(chatwin, message, FALSE, TRUE); + } else if (ctx->window->type == WIN_MUC) { + ProfMucWin* mucwin = (ProfMucWin*)ctx->window; + cl_ev_send_muc_msg_corrected(mucwin, message, FALSE, TRUE); + } + } + g_free(ctx); +} + gboolean cmd_about(ProfWin* window, const char* const command, gchar** args) { @@ -4033,18 +4085,13 @@ cmd_subject(ProfWin* window, const char* const command, gchar** args) } if (g_strcmp0(args[0], "editor") == 0) { - gchar* message = NULL; char* subject = muc_subject(mucwin->roomjid); - if (get_message_from_editor(subject, &message)) { - return TRUE; - } + EditorFinishedContext* ctx = g_new0(EditorFinishedContext, 1); + ctx->roomjid = g_strdup(mucwin->roomjid); + + launch_editor(subject, _cmd_room_editor_finished_cb, ctx); - if (message) { - message_send_groupchat_subject(mucwin->roomjid, message); - } else { - cons_bad_cmd_usage(command); - } return TRUE; } @@ -9567,16 +9614,7 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args) gboolean cmd_editor(ProfWin* window, const char* const command, gchar** args) { - auto_gchar gchar* message = NULL; - - if (get_message_from_editor(NULL, &message)) { - return TRUE; - } - - rl_insert_text(message); - ui_resize(); - rl_point = rl_end; - rl_forced_update_display(); + launch_editor(NULL, _cmd_editor_finished_cb, NULL); return TRUE; } @@ -9590,20 +9628,10 @@ cmd_correct_editor(ProfWin* window, const char* const command, gchar** args) gchar* initial_message = win_get_last_sent_message(window); - auto_gchar gchar* message = NULL; - if (get_message_from_editor(initial_message, &message)) { - return TRUE; - } - - if (window->type == WIN_CHAT) { - ProfChatWin* chatwin = (ProfChatWin*)window; + EditorFinishedContext* ctx = g_new0(EditorFinishedContext, 1); + ctx->window = window; - cl_ev_send_msg_correct(chatwin, message, FALSE, TRUE); - } else if (window->type == WIN_MUC) { - ProfMucWin* mucwin = (ProfMucWin*)window; - - cl_ev_send_muc_msg_corrected(mucwin, message, FALSE, TRUE); - } + launch_editor(initial_message, _cmd_correct_editor_finished_cb, ctx); return TRUE; } diff --git a/src/tools/editor.c b/src/tools/editor.c index 03d94ed75..bf9e9c4cc 100644 --- a/src/tools/editor.c +++ b/src/tools/editor.c @@ -20,14 +20,52 @@ #include "log.h" #include "common.h" #include "xmpp/xmpp.h" +#include "ui/ui.h" + +typedef struct EditorContext +{ + gchar* filename; + void (*callback)(gchar* content, void* user_data); + void* user_data; +} EditorContext; + +static void +_editor_exit_cb(GPid pid, gint status, gpointer data) +{ + EditorContext* ctx = data; + gchar* contents = NULL; + GError* error = NULL; + + ui_resume(); + ui_resize(); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + if (g_file_get_contents(ctx->filename, &contents, NULL, &error)) { + g_strchomp(contents); + ctx->callback(contents, ctx->user_data); + g_free(contents); + } else { + log_error("[Editor] could not read from %s: %s", ctx->filename, error->message); + 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)); + } + + if (remove(ctx->filename) != 0) { + log_error("[Editor] error during file deletion of %s", ctx->filename); + } + + g_free(ctx->filename); + g_free(ctx); + g_spawn_close_pid(pid); +} // Returns true if an error occurred gboolean -get_message_from_editor(gchar* message, gchar** returned_message) +launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data) { - /* Make sure that there's no junk in the return-pointer in error cases */ - *returned_message = NULL; - auto_gchar gchar* filename = NULL; auto_gerror GError* glib_error = NULL; const char* jid = connection_get_barejid(); @@ -37,64 +75,65 @@ get_message_from_editor(gchar* message, gchar** returned_message) log_debug("[Editor] could not get JID"); auto_gchar gchar* data_dir = files_get_data_path(DIR_EDITOR); if (!create_dir(data_dir)) { + cons_show_error("Could not create editor directory."); return TRUE; } filename = g_strdup_printf("%s/compose.md", data_dir); } if (!filename) { log_error("[Editor] something went wrong while creating compose file"); + cons_show_error("Could not create compose file."); return TRUE; } gsize messagelen = 0; - if (message != NULL) { - messagelen = strlen(message); + if (initial_content != NULL) { + messagelen = strlen(initial_content); } - if (!g_file_set_contents(filename, message, messagelen, &glib_error)) { + if (!g_file_set_contents(filename, initial_content, messagelen, &glib_error)) { log_error("[Editor] could not write to %s: %s", filename, PROF_GERROR_MESSAGE(glib_error)); + cons_show_error("Could not write to compose file: %s", PROF_GERROR_MESSAGE(glib_error)); return TRUE; } auto_gchar gchar* editor = prefs_get_string(PREF_COMPOSE_EDITOR); - auto_gchar gchar* editor_with_filename = g_strdup_printf("%s %s", editor, filename); - auto_gcharv gchar** editor_argv = g_strsplit(editor_with_filename, " ", 0); + gchar** editor_argv = NULL; + GError* error = NULL; - if (!editor_argv || !editor_argv[0]) { - log_error("[Editor] Failed to parse editor command: %s", editor); + auto_gchar gchar* full_cmd = g_strdup_printf("%s %s", editor, filename); + if (!g_shell_parse_argv(full_cmd, NULL, &editor_argv, &error)) { + log_error("[Editor] Failed to parse editor command: %s", error->message); + cons_show_error("Failed to parse editor command: %s", error->message); + g_error_free(error); return TRUE; } - // Fork / exec + EditorContext* ctx = g_new0(EditorContext, 1); + ctx->filename = g_steal_pointer(&filename); + ctx->callback = callback; + ctx->user_data = user_data; + + ui_suspend(); + pid_t pid = fork(); - if (pid == 0) { - if (editor_argv && editor_argv[0]) { - int x = execvp(editor_argv[0], editor_argv); - if (x == -1) - log_error("[Editor] Failed to exec %s", editor); - } + if (pid == -1) { + log_error("[Editor] Failed to fork: %s", strerror(errno)); + ui_resume(); + ui_resize(); + cons_show_error("Failed to start editor: %s", strerror(errno)); + g_strfreev(editor_argv); + g_free(ctx->filename); + g_free(ctx); + return TRUE; + } else if (pid == 0) { + // Child process: Inherits TTY from parent + execvp(editor_argv[0], editor_argv); _exit(EXIT_FAILURE); - } else { - if (pid == -1) { - return TRUE; - } - waitpid(pid, NULL, 0); - - gchar* contents; - gsize length; - if (!g_file_get_contents(filename, &contents, &length, &glib_error)) { - log_error("[Editor] could not read from %s: %s", filename, PROF_GERROR_MESSAGE(glib_error)); - return TRUE; - } - /* Remove all trailing new-line characters */ - g_strchomp(contents); - *returned_message = contents; - if (remove(filename) != 0) { - log_error("[Editor] error during file deletion of %s", filename); - } else { - log_debug("[Editor] deleted file: %s", filename); - } } + // Parent process: Watch the child asynchronously + g_child_watch_add((GPid)pid, _editor_exit_cb, ctx); + g_strfreev(editor_argv); return FALSE; } diff --git a/src/tools/editor.h b/src/tools/editor.h index f38d2c626..f79941254 100644 --- a/src/tools/editor.h +++ b/src/tools/editor.h @@ -13,6 +13,6 @@ #include -gboolean get_message_from_editor(gchar* message, gchar** returned_message); +gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data); #endif diff --git a/src/ui/core.c b/src/ui/core.c index ac287383e..4e8a76068 100644 --- a/src/ui/core.c +++ b/src/ui/core.c @@ -120,6 +120,11 @@ ui_sigwinch_handler(int sig) void ui_update(void) { + // UI is suspended + if (isendwin()) { + return; + } + ProfWin* current = wins_get_current(); if (current->layout->paged == 0) { win_move_to_end(current); @@ -186,6 +191,11 @@ ui_resume(void) void ui_resize(void) { + // UI is suspended + if (isendwin()) { + return; + } + struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); erase(); @@ -204,6 +214,11 @@ ui_resize(void) void ui_redraw(void) { + // UI is suspended + if (isendwin()) { + return; + } + title_bar_resize(); wins_resize_all(); status_bar_resize(); diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c index 60c588943..f53b7889b 100644 --- a/src/ui/inputwin.c +++ b/src/ui/inputwin.c @@ -171,6 +171,12 @@ _inp_slashguard_check(void) char* inp_readline(void) { + // UI is suspended + if (isendwin()) { + g_usleep(100000); // 100ms + return NULL; + } + p_rl_timeout.tv_sec = inp_timeout / 1000; p_rl_timeout.tv_usec = inp_timeout % 1000 * 1000; FD_ZERO(&fds); @@ -996,6 +1002,16 @@ _inp_rl_down_arrow_handler(int count, int key) return 0; } +static void +_editor_finished_cb(gchar* message, void* user_data) +{ + if (message) { + rl_replace_line(message, 0); + rl_point = rl_end; + rl_forced_update_display(); + } +} + static int _inp_rl_send_to_editor(int count, int key) { @@ -1003,16 +1019,7 @@ _inp_rl_send_to_editor(int count, int key) return 0; } - auto_gchar gchar* message = NULL; - - if (get_message_from_editor(rl_line_buffer, &message)) { - return 0; - } - - rl_replace_line(message, 0); - ui_resize(); - rl_point = rl_end; - rl_forced_update_display(); + launch_editor(rl_line_buffer, _editor_finished_cb, NULL); return 0; } From c3718e3ae75f267f88f98e078cb72eef0fc5046d Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 22:42:25 +0200 Subject: [PATCH 5/6] cleanup: Remove unused variable This was forgotten in a refactor commit. Ref: c43c9567dfa696112083a87c04dd17db51664bf6 Signed-off-by: Michael Vetter --- src/database.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database.c b/src/database.c index 54e413109..7f35ddec0 100644 --- a/src/database.c +++ b/src/database.c @@ -449,7 +449,6 @@ _add_to_db(ProfMessage* message, char* type, const Jid* const from_jid, const Ji return; } - char* err_msg; auto_gchar gchar* date_fmt = prof_date_time_format_iso8601(message->timestamp); const char* enc = _get_message_enc_str(message->enc); From 196d3c378312d3045b856f34dc0a08ddb791b421 Mon Sep 17 00:00:00 2001 From: Michael Vetter Date: Thu, 2 Apr 2026 23:16:51 +0200 Subject: [PATCH 6/6] cleanup: Cleanup gitignore Remove IDE specific entries, they belong in the global conf of the user. Remove autotools specific files. Ref: bc777c56b284c7f9470e7c18d635f6d224ae21d8 Signed-off-by: Michael Vetter --- .gitignore | 71 +++--------------------------------------------------- 1 file changed, 3 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 39e9b5a5e..fc6251545 100644 --- a/.gitignore +++ b/.gitignore @@ -1,72 +1,9 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ - -# IDE -.codelite/ -profanity.mk -profanity.project -profanity.workspace -compile_commands.json -.tern-port -.tern-project -.cproject -.project -.settings/ -.vscode/ -*.plist/ - -# autotools -.libs/ -/Makefile -/Makefile.in -_configs.sed -aclocal.m4 -autom4te.cache/ -build-aux/ -config.log -config.status -configure -configure*~ -libprofanity.la -libtool -m4/ -**/.deps/ -**.dirstamp -src/config.h -src/config.h.in -src/config.h.in~ -src/gitversion.h -src/stamp-h1 -src/plugins/profapi.lo - # out-of-tree build folders build*/ -# binaries -profanity -**/*.o - -# test output -tests/functionaltests/functionaltests -tests/functionaltests/functionaltests.log -tests/functionaltests/functionaltests.trs -tests/unittests/unittests -tests/unittests/unittests.log -tests/unittests/unittests.trs -test-suite.log - # valgrind output profval* -# local scripts -clean-test.sh -gen_docs.sh -gitpushall.sh - # website files main_fragment.html toc_fragment.html @@ -88,18 +25,16 @@ apidocs/c/doxygen_sqlite3.db # Temp Vim files **/*.swp -# Virtual envs -python2/ -python3/ +# GDB +.gdbinit +# Misc .DS_Store -.gdbinit *.bak *.orig *.patch *.rej breaks - *.tar.* *.zip *.log*