mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-07-18 23:16:22 +00:00
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:
@@ -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;
|
||||
}
|
||||
EditorFinishedContext* ctx = g_new0(EditorFinishedContext, 1);
|
||||
ctx->window = window;
|
||||
|
||||
if (window->type == WIN_CHAT) {
|
||||
ProfChatWin* chatwin = (ProfChatWin*)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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user