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 <jubalh@iodoru.org>
This commit is contained in:
@@ -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
|
||||
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);
|
||||
}
|
||||
_exit(EXIT_FAILURE);
|
||||
} else {
|
||||
if (pid == -1) {
|
||||
return TRUE;
|
||||
}
|
||||
waitpid(pid, NULL, 0);
|
||||
EditorContext* ctx = g_new0(EditorContext, 1);
|
||||
ctx->filename = g_steal_pointer(&filename);
|
||||
ctx->callback = callback;
|
||||
ctx->user_data = user_data;
|
||||
|
||||
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);
|
||||
}
|
||||
ui_suspend();
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user