/* * editor.c * vim: expandtab:ts=4:sts=4:sw=4 * * Copyright (C) 2022 - 2025 Michael Vetter * Copyright (C) 2022 MarcoPolo PasTonMolo * Copyright (C) 2022 Steffen Jaeckel * * 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 . * * 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. * */ #include #include #include #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) { /* Make sure that there's no junk in the return-pointer in error cases */ *returned_message = NULL; 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)) { 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; } gsize messagelen = 0; if (message != NULL) { messagelen = strlen(message); } if (!g_file_set_contents(filename, 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; } 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); // Fork / exec pid_t pid = fork(); if (pid == 0) { int x = execvp(editor_argv[0], editor_argv); if (x == -1) { log_error("[Editor] Failed to exec %s", editor); } _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, glib_error ? glib_error->message : "No GLib error given"); if (glib_error) { g_error_free(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); } } return FALSE; }