diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dd40b9a5..487e5282 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - flavor: [arch, debian, fedora, ubuntu] + flavor: [arch, debian, ubuntu] name: Linux steps: diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c index 5710de2e..d444f3c8 100644 --- a/src/command/cmd_funcs.c +++ b/src/command/cmd_funcs.c @@ -9527,16 +9527,7 @@ cmd_change_password(ProfWin* window, const char* const command, gchar** args) gboolean cmd_editor(ProfWin* window, const char* const command, gchar** args) { - auto_gchar gchar* message = NULL; - - if (get_message_from_editor(NULL, &message)) { - return TRUE; - } - - rl_insert_text(message); - ui_resize(); - rl_point = rl_end; - rl_forced_update_display(); + get_message_from_editor_async(NULL); return TRUE; } diff --git a/src/common.h b/src/common.h index 4e7ed3d2..0532e0d7 100644 --- a/src/common.h +++ b/src/common.h @@ -171,6 +171,8 @@ typedef enum { RESOURCE_XA } resource_presence_t; +extern gboolean background_mode; + gboolean string_to_verbosity(const char* cmd, int* verbosity, gchar** err_msg); gboolean create_dir(const char* name); diff --git a/src/profanity.c b/src/profanity.c index fd869b06..750f7891 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -59,6 +59,7 @@ #include "config/tlscerts.h" #include "config/scripts.h" #include "command/cmd_defs.h" +#include "tools/editor.h" #include "plugins/plugins.h" #include "event/client_events.h" #include "ui/ui.h" @@ -93,6 +94,9 @@ static void _connect_default(const char* const account); pthread_mutex_t lock; static gboolean force_quit = FALSE; +// main.c (prof_run section) +gboolean background_mode = FALSE; + void prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_file, gchar* theme_name, gchar** commands) { @@ -118,42 +122,44 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f log_stderr_handler(); session_check_autoaway(); - line = commands ? *commands : inp_readline(); - if (commands && line && memcmp(line, "/sleep", 6) == 0) { - if (!g_timer_is_active(waittimer)) { - gchar* err_msg; - if (strtoi_range(line + 7, &waittime, 0, 300, &err_msg)) { - g_timer_start(waittimer); - /* Increase the minimal runtime by the waiting time - * so we can be sure there's runtime left after executing - * the last command. - */ - min_runtime += waittime; - } else { - log_error(err_msg); - g_free(err_msg); - commands = NULL; + if (!background_mode) { + line = commands ? *commands : inp_readline(); + if (commands && line && memcmp(line, "/sleep", 6) == 0) { + if (!g_timer_is_active(waittimer)) { + gchar* err_msg; + if (strtoi_range(line + 7, &waittime, 0, 300, &err_msg)) { + g_timer_start(waittimer); + /* Increase the minimal runtime by the waiting time + * so we can be sure there's runtime left after executing + * the last command. + */ + min_runtime += waittime; + } else { + log_error(err_msg); + g_free(err_msg); + commands = NULL; + } + } else if (g_timer_elapsed(waittimer, NULL) >= waittime) { + g_timer_stop(waittimer); + commands++; + if (!(*commands)) + commands = NULL; + } + cont = TRUE; + } else if (line) { + ProfWin* window = wins_get_current(); + cont = cmd_process_input(window, line); + if (commands) { + commands++; + if (!(*commands)) + commands = NULL; + } else { + free(line); + line = NULL; } - } else if (g_timer_elapsed(waittimer, NULL) >= waittime) { - g_timer_stop(waittimer); - commands++; - if (!(*commands)) - commands = NULL; - } - cont = TRUE; - } else if (line) { - ProfWin* window = wins_get_current(); - cont = cmd_process_input(window, line); - if (commands) { - commands++; - if (!(*commands)) - commands = NULL; } else { - free(line); - line = NULL; + cont = TRUE; } - } else { - cont = TRUE; } #ifdef HAVE_LIBOTR @@ -163,7 +169,12 @@ prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_f notify_remind(); session_process_events(); iq_autoping_check(); - ui_update(); + + editor_process(wins_get_current()); + + if (!background_mode) { + ui_update(); + } #ifdef HAVE_GTK tray_update(); #endif diff --git a/src/tools/editor.c b/src/tools/editor.c index 7bd74afe..99adcac5 100644 --- a/src/tools/editor.c +++ b/src/tools/editor.c @@ -38,15 +38,200 @@ #include #include #include +#include // necessary for readline +#include #include #include +#include #include "config/files.h" #include "config/preferences.h" +#include "editor.h" #include "log.h" #include "common.h" +#include "ncurses.h" +#include "ui/ui.h" #include "xmpp/xmpp.h" +EditorTask editor_task = { 0, FALSE, NULL, NULL, PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL }; + +void* +editor_thread(void* arg) +{ + EditorTask* task = (EditorTask*)arg; + auto_gcharv gchar** argv = g_strsplit(task->editor_cmd, " ", 0); + + pid_t pid = fork(); + if (pid == 0) { + execvp(argv[0], argv); + log_error("[Editor] Failed to exec %s", argv[0]); + _exit(EXIT_FAILURE); + } + + pthread_mutex_lock(&task->mutex); + if (pid > 0) { + task->pid = pid; + int status; + // Unlock mutex to avoid deadlock on the main loop + pthread_mutex_unlock(&task->mutex); + waitpid(pid, &status, 0); + pthread_mutex_lock(&task->mutex); + + gsize length; + if (!g_file_get_contents(task->filename, &task->result, &length, &task->error)) { + log_error("[Editor] could not read from %s: %s", task->filename, + task->error ? task->error->message : "No GLib error given"); + task->result = g_strdup(""); // Ensure valid result + } else { + g_strchomp(task->result); + if (remove(task->filename) != 0) { + log_error("[Editor] error during file deletion of %s", task->filename); + } else { + log_debug("[Editor] deleted file: %s", task->filename); + } + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + task->error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Editor process failed with status %d", WEXITSTATUS(status)); + } + } else { + task->error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED, "Fork failed: %s", strerror(errno)); + task->result = g_strdup(""); // Ensure valid result + } + + task->active = FALSE; + pthread_mutex_unlock(&task->mutex); + + return NULL; +} + +// Returns true if an error occurred +gboolean +get_message_from_editor_async(gchar* message) +{ + // Check if editor is already running + pthread_mutex_lock(&editor_task.mutex); + if (editor_task.active) { + pthread_mutex_unlock(&editor_task.mutex); + log_error("[Editor] Editor already running"); + return TRUE; + } + pthread_mutex_unlock(&editor_task.mutex); + + // Create temporary file + auto_gchar gchar* filename = NULL; + GError* glib_error = NULL; + const char* jid = connection_get_barejid(); + if (jid) { + filename = files_file_in_account_data_path(DIR_EDITOR, jid, "compose.md"); + } else { + log_debug("[Editor] could not get JID"); + auto_gchar gchar* data_dir = files_get_data_path(DIR_EDITOR); + if (!create_dir(data_dir)) { + log_error("[Editor] could not create directory %s", data_dir); + return TRUE; + } + filename = g_strdup_printf("%s/compose.md", data_dir); + } + if (!filename) { + log_error("[Editor] something went wrong while creating compose file"); + return TRUE; + } + + // Write message to file + gsize messagelen = message ? strlen(message) : 0; + if (!g_file_set_contents(filename, message ? message : "", messagelen, &glib_error)) { + log_error("[Editor] could not write to %s: %s", filename, + glib_error ? glib_error->message : "No GLib error given"); + if (glib_error) { + g_error_free(glib_error); + } + return TRUE; + } + + // Prepare editor command + auto_gchar gchar* editor = prefs_get_string(PREF_COMPOSE_EDITOR); + auto_gchar gchar* editor_with_filename = g_strdup_printf("%s %s", editor, filename); + + // Suspend NCurses + def_prog_mode(); + endwin(); + + // Initialize thread task + pthread_mutex_lock(&editor_task.mutex); + editor_task.active = TRUE; + editor_task.result = NULL; + editor_task.error = NULL; + g_free(editor_task.filename); + g_free(editor_task.editor_cmd); + editor_task.filename = g_strdup(filename); + editor_task.editor_cmd = g_strdup(editor_with_filename); + pthread_mutex_unlock(&editor_task.mutex); + + // Set background mode + background_mode = TRUE; + log_debug("[Editor] Entering background mode"); + + // Start editor thread + if (pthread_create(&editor_task.thread, NULL, editor_thread, &editor_task) != 0) { + pthread_mutex_lock(&editor_task.mutex); + editor_task.active = FALSE; + editor_task.error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Failed to create editor thread: %s", strerror(errno)); + g_free(editor_task.filename); + g_free(editor_task.editor_cmd); + editor_task.filename = NULL; + editor_task.editor_cmd = NULL; + pthread_mutex_unlock(&editor_task.mutex); + background_mode = FALSE; + log_debug("[Editor] Exiting background mode"); + reset_prog_mode(); + doupdate(); + log_error("[Editor] Failed to create editor thread"); + return TRUE; + } + + return FALSE; +} + +void +editor_process(ProfWin* window) +{ + pthread_mutex_lock(&editor_task.mutex); + if (editor_task.active) { + pthread_mutex_unlock(&editor_task.mutex); + return; + } + if (!editor_task.result) { + pthread_mutex_unlock(&editor_task.mutex); + return; + } + + g_strchomp(editor_task.result); + rl_replace_line(editor_task.result, 0); + rl_point = rl_end; + rl_forced_update_display(); + reset_prog_mode(); + doupdate(); + ui_resize(); + g_free(editor_task.result); + g_free(editor_task.filename); + g_free(editor_task.editor_cmd); + editor_task.result = NULL; + editor_task.filename = NULL; + editor_task.editor_cmd = NULL; + if (editor_task.error) { + log_error("[Editor] %s", editor_task.error->message); + g_error_free(editor_task.error); + editor_task.error = NULL; + } + pthread_mutex_unlock(&editor_task.mutex); + background_mode = FALSE; + log_debug("[Editor] Exiting background mode"); +} + +// Deprecated synchronous editor call. Returns a message as returned_message. +// Please avoid using it, since it blocks execution of the message handling and other important functionality until the editor is closed. // Returns true if an error occurred gboolean get_message_from_editor(gchar* message, gchar** returned_message) diff --git a/src/tools/editor.h b/src/tools/editor.h index 011cba0d..f7bc29ad 100644 --- a/src/tools/editor.h +++ b/src/tools/editor.h @@ -37,8 +37,24 @@ #ifndef TOOLS_EDITOR_H #define TOOLS_EDITOR_H +#include "ui/win_types.h" #include +typedef struct +{ + pid_t pid; // Editor process PID + gboolean active; // Thread running + gchar* result; // Edited message + GError* error; // Error if any + pthread_mutex_t mutex; // Protect access + pthread_t thread; // Editor thread + gchar* filename; // Temporary file path + gchar* editor_cmd; // Editor command with filename +} EditorTask; + +extern EditorTask editor_task; gboolean get_message_from_editor(gchar* message, gchar** returned_message); +gboolean get_message_from_editor_async(gchar* message); +void editor_process(ProfWin* window); #endif diff --git a/src/ui/inputwin.c b/src/ui/inputwin.c index e1cb10cf..6eddc177 100644 --- a/src/ui/inputwin.c +++ b/src/ui/inputwin.c @@ -974,14 +974,7 @@ _inp_rl_send_to_editor(int count, int key) auto_gchar gchar* message = NULL; - if (get_message_from_editor(rl_line_buffer, &message)) { - return 0; - } - - rl_replace_line(message, 0); - ui_resize(); - rl_point = rl_end; - rl_forced_update_display(); + get_message_from_editor_async(rl_line_buffer); return 0; }