/* * editor.c * vim: expandtab:ts=4:sts=4:sw=4 * * Copyright (C) 2022 - 2026 Michael Vetter * Copyright (C) 2022 MarcoPolo PasTonMolo * Copyright (C) 2022 Steffen Jaeckel * * SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception */ #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" #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 a message via callback once the editor is closed. // Returns true if an error occurred. gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data) { auto_gchar gchar* filename = NULL; auto_gerror 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)) { 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 (initial_content != NULL) { messagelen = strlen(initial_content); } 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); gchar** editor_argv = NULL; GError* error = NULL; 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; } EditorContext* ctx = g_new0(EditorContext, 1); ctx->filename = g_steal_pointer(&filename); ctx->callback = callback; ctx->user_data = user_data; ui_suspend(); // Ensure GLib's SIGCHLD handler is installed before forking to prevent // zombie children if the editor is launched before the main loop starts. GMainContext* main_ctx = g_main_context_default(); g_main_context_acquire(main_ctx); g_main_context_release(main_ctx); pid_t pid = fork(); 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 // Reset signal handlers that profanity sets so the editor doesn't inherit them signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGPIPE, SIG_DFL); if (editor_argv && editor_argv[0]) { execvp(editor_argv[0], editor_argv); } _exit(EXIT_FAILURE); } // Parent process: Watch the child asynchronously g_child_watch_add((GPid)pid, _editor_exit_cb, ctx); g_strfreev(editor_argv); return FALSE; }