From bac1efa51bf889d494013925586e87ef08307529 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sat, 29 Apr 2023 15:11:34 +0300 Subject: fixed interruptions on Gtk+ (and probably on PDCurses/Win32) * test case: ECwhile true; do true; done$ * Some platforms require polling via teco_interface_is_interrupted() for detecting interruptions, so we added an idle watcher to the Glib event loop in spawn.c. * On platforms that do not require polling key presses (like Unix/ncurses), the idle watcher won't do any harm. --- src/parser.c | 2 +- src/spawn.c | 34 +++++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/parser.c b/src/parser.c index 9841c93..910fc7f 100644 --- a/src/parser.c +++ b/src/parser.c @@ -100,7 +100,7 @@ teco_machine_main_step(teco_machine_main_t *ctx, const gchar *macro, gint stop_p ctx->parent.current, ctx->mode); #endif - if (teco_interface_is_interrupted()) { + if (G_UNLIKELY(teco_interface_is_interrupted())) { teco_error_interrupted_set(error); goto error_attach; } diff --git a/src/spawn.c b/src/spawn.c index 97d0b21..416c433 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -51,6 +51,7 @@ static gboolean teco_spawn_stdin_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data); static gboolean teco_spawn_stdout_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer data); +static gboolean teco_spawn_idle_cb(gpointer user_data); /* * FIXME: Global state should be part of teco_machine_main_t @@ -58,6 +59,7 @@ static gboolean teco_spawn_stdout_watch_cb(GIOChannel *chan, static struct { GMainContext *mainctx; GMainLoop *mainloop; + GSource *idle_src; GSource *child_src; GSource *stdin_src, *stdout_src; @@ -85,6 +87,15 @@ teco_spawn_init(void) */ teco_spawn_ctx.mainctx = g_main_context_new(); teco_spawn_ctx.mainloop = g_main_loop_new(teco_spawn_ctx.mainctx, FALSE); + + /* + * This is required on platforms that require polling (both Gtk and Curses). + */ + teco_spawn_ctx.idle_src = g_idle_source_new(); + g_source_set_priority(teco_spawn_ctx.idle_src, G_PRIORITY_LOW); + g_source_set_callback(teco_spawn_ctx.idle_src, (GSourceFunc)teco_spawn_idle_cb, + NULL, NULL); + g_source_attach(teco_spawn_ctx.idle_src, teco_spawn_ctx.mainctx); } static gchar ** @@ -334,17 +345,17 @@ teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr g_source_unref(teco_spawn_ctx.child_src); g_spawn_close_pid(pid); + /* + * NOTE: This includes interruptions due to teco_interrupt() + * following CTRL+C. + * But they are reported as G_SPAWN_ERROR_FAILED and hard to filter out. + */ if (teco_spawn_ctx.error) { g_propagate_error(error, teco_spawn_ctx.error); teco_spawn_ctx.error = NULL; goto gerror; } - if (teco_interface_is_interrupted()) { - teco_error_interrupted_set(error); - return NULL; - } - if (teco_machine_main_eval_colon(ctx)) teco_expressions_push(TECO_SUCCESS); @@ -659,6 +670,8 @@ teco_spawn_stdout_watch_cb(GIOChannel *chan, GIOCondition condition, gpointer da * NOTE: Since this reads from an external process and regular memory * limiting in teco_machine_main_step() is not performed, we could insert * indefinitely (eg. cat /dev/zero). + * We could also check in teco_spawn_idle_cb(), but there is no guarantee we + * actually return to the main loop. */ if (!teco_memory_check(0, &teco_spawn_ctx.error)) goto error; @@ -671,10 +684,21 @@ error: return G_SOURCE_REMOVE; } +static gboolean +teco_spawn_idle_cb(gpointer user_data) +{ + if (G_UNLIKELY(teco_interface_is_interrupted())) + /* Just in case this didn't yet happen. Should kill the child process. */ + teco_interrupt(); + return G_SOURCE_CONTINUE; +} + #ifndef NDEBUG static void __attribute__((destructor)) teco_spawn_cleanup(void) { + g_source_unref(teco_spawn_ctx.idle_src); + g_main_loop_unref(teco_spawn_ctx.mainloop); g_main_context_unref(teco_spawn_ctx.mainctx); -- cgit v1.2.3