diff --git a/src/profanity.c b/src/profanity.c index f841f158..9fccd4eb 100644 --- a/src/profanity.c +++ b/src/profanity.c @@ -20,6 +20,7 @@ #include #include +#include #include "profanity.h" #include "common.h" @@ -182,6 +183,16 @@ sigterm_handler(int sig) force_quit = TRUE; } +// Universal "I'm stuck in the editor" escape hatch. Dispatched in the main +// loop by GLib (g_unix_signal_add), so it is safe to call into editor.c +// directly. Trigger from another terminal: `pkill -USR1 profanity`. +static gboolean +_emergency_resume_handler(gpointer data) +{ + editor_emergency_kill(); + return G_SOURCE_CONTINUE; +} + static void _init(char* log_level, char* config_file, char* log_file, char* theme_name) { @@ -193,6 +204,7 @@ _init(char* log_level, char* config_file, char* log_file, char* theme_name) signal(SIGWINCH, ui_sigwinch_handler); signal(SIGTERM, sigterm_handler); signal(SIGHUP, sigterm_handler); + g_unix_signal_add(SIGUSR1, _emergency_resume_handler, NULL); if (pthread_mutex_init(&lock, NULL) != 0) { log_error("Mutex init failed"); exit(1); diff --git a/src/tools/editor.c b/src/tools/editor.c index dc3bdeb8..7c58bddf 100644 --- a/src/tools/editor.c +++ b/src/tools/editor.c @@ -40,11 +40,13 @@ typedef struct EditorContext // editor that fights the first one for the terminal, then prematurely // ui_resume when either exits. static gboolean editor_active = FALSE; +static pid_t editor_pid = 0; static void _editor_exit_cb(GPid pid, gint status, gpointer data) { editor_active = FALSE; + editor_pid = 0; EditorContext* ctx = data; gchar* contents = NULL; @@ -157,9 +159,11 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat } else if (pid == 0) { // Child process: Inherits TTY from parent - // Reset signal handlers that profanity sets so the editor doesn't inherit them + // SIGTSTP intentionally NOT reset: keeping SIG_IGN inherited via + // fork+exec avoids hanging on a STOPPED editor (g_child_watch lacks + // WUNTRACED). Cost: vim's :stop is a no-op in /editor. See + // editor_emergency_kill for other hung-editor scenarios. signal(SIGINT, SIG_DFL); - signal(SIGTSTP, SIG_DFL); signal(SIGPIPE, SIG_DFL); if (editor_argv && editor_argv[0]) { @@ -169,7 +173,20 @@ launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* dat } // Parent process: Watch the child asynchronously + editor_pid = pid; g_child_watch_add((GPid)pid, _editor_exit_cb, ctx); g_strfreev(editor_argv); return FALSE; } + +void +editor_emergency_kill(void) +{ + if (!editor_active || editor_pid <= 0) { + return; + } + log_warning("[Editor] emergency resume: sending SIGTERM to editor pid %d", editor_pid); + kill(editor_pid, SIGTERM); + // g_child_watch dispatches _editor_exit_cb when the child exits, which + // already restores the UI — no further action required here. +} diff --git a/src/tools/editor.h b/src/tools/editor.h index a6ef0cef..74d05087 100644 --- a/src/tools/editor.h +++ b/src/tools/editor.h @@ -16,4 +16,9 @@ gboolean launch_editor(gchar* initial_content, void (*callback)(gchar* content, void* data), void* user_data); +// Universal escape hatch: if an editor session is active, send SIGTERM to the +// child. The existing g_child_watch fires _editor_exit_cb on exit, which +// already restores the UI. Wired to SIGUSR1 in profanity.c. +void editor_emergency_kill(void); + #endif