aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-24 03:23:14 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2015-06-24 03:40:06 +0200
commitae2f607a19b12a30374de059ec29beaf41f82c73 (patch)
tree448afc2f95e5cb7465e30c4f8612a92d568a5449
parent81a1270a56bf1f6a13e709e653598c69c7d9334b (diff)
downloadsciteco-ae2f607a19b12a30374de059ec29beaf41f82c73.tar.gz
added "^FCLOSE" function key macro and defined SIGTERM behaviour
* ^FCLOSE is inserted when the "Close" key is pressed. It is used by the GTK+ UI to deliver window close requests and SIGTERM occurrences. (this replaces the "Break" key used before in the GTK+ UI). * The default action of ^FCLOSE is to quit SciTECO, therefore window closing is possible even in --no-profile mode for instance. * fixed a minor memleak in Cmdline::fnmacro() * added ^FCLOSE implementation to fnkeys.tes to insert EX. This currently has the disadvantage of overwriting the error message with syntax errors if there are modified buffers but it will at least not close the window if there are modified buffers. * SIGTERM will now be similar to SIGINT by default instead of terminating SciTECO right away. * the GTK+ UI handles SIGTERM by emulating the "close" key while still interrupting like SIGINT. * GTK+: SIGTERM and ^C will interrupt by sending SIGINT to the entire process group instead of simply setting `sigint_occurred`. This fixes interrupting EC and EG commands with long-running or hanging programs and is relevant to the solution of #4.
-rw-r--r--doc/sciteco.1.in14
-rw-r--r--doc/sciteco.7.template16
-rw-r--r--lib/fnkeys.tes3
-rw-r--r--src/cmdline.cpp28
-rw-r--r--src/interface-curses.cpp1
-rw-r--r--src/interface-gtk.cpp109
-rw-r--r--src/main.cpp26
-rw-r--r--src/sciteco.h3
8 files changed, 166 insertions, 34 deletions
diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in
index 5b4c1e0..7212a08 100644
--- a/doc/sciteco.1.in
+++ b/doc/sciteco.1.in
@@ -246,7 +246,7 @@ environment variables accessed by glib
.UE .
On a Curses UI, there are other important environment variables
like \fBTERM\fP, \fBLINES\fP and \fBCOLUMNS\fP that may be
-accesses when \*(ST enters interactive mode.
+accessed when \*(ST enters interactive mode.
For ncurses, see section \fBENVIRONMENT\fP in
.BR ncurses (3NCURSES)
for details.
@@ -271,9 +271,19 @@ ignored.
Some GUIs may depend on delivery of \fBSIGINT\fP when \fB^C\fP
is pressed in order to interrupt macros interactively.
Note that this signal can usually also be generated when pressing
-\fB^C\fP on the process (also if there is a graphical window).
+\fB^C\fP on the process' associated console
+(also if there is a graphical window).
This is useful for GUIs that do not yet support interruptions
directly.
+.TP
+.B SIGTERM
+Try to gracefully shut down \*(ST.
+In batch mode this only interrupts the currently running macro
+similar to \fBSIGINT\fP causing \*(ST to exit.
+If technically possible, user interfaces will additionally
+process \fBSIGTERM\fP in interactive mode as if the \fICLOSE\fP
+function key has been pressed, which will result in unconditional
+program termination or user-programmed behaviour.
.
.
.SH FILES
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index b777143..efe8538 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -215,6 +215,22 @@ Inserted when the End or shift-End key is pressed.
.TQ
.B ^FSHELP
Inserted when the Help or shift-Help key is pressed.
+.TQ
+.B ^FCLOSE
+Inserted when the Close key has been pressed.
+More importantly, this key is emulated in some GUIs
+(notably GTK+) when the user tries to close \*(ST's
+window or when the \fBSIGTERM\fP signal is received.
+This allows customizing \*(ST's behaviour when
+program termination is requested (e.g. only quit if
+there are no unsaved buffers).
+The close key is also special because
+it has a default action if function key macros are
+disabled or the \(lq^FCLOSE\(rq macro is undefined:
+It unconditionally quits \*(ST.
+The default action is \fBnot\fP performed when
+\(lq^FCLOSE\(rq has merely been masked out in the
+current parser state (see below).
.
.LP
\(lq^F\(rq corresponds to CTRL+F in the above list but
diff --git a/lib/fnkeys.tes b/lib/fnkeys.tes
index 4ad653d..6153717 100644
--- a/lib/fnkeys.tes
+++ b/lib/fnkeys.tes
@@ -91,3 +91,6 @@
}
@[DOWN]{(M[DOWN]}
1U[DOWN]
+
+@[CLOSE]{(EX)}
+1U[CLOSE]
diff --git a/src/cmdline.cpp b/src/cmdline.cpp
index 94e68ba..06cb76e 100644
--- a/src/cmdline.cpp
+++ b/src/cmdline.cpp
@@ -556,7 +556,7 @@ Cmdline::fnmacro(const gchar *name)
if (!(Flags::ed & Flags::ED_FNKEYS))
/* function key macros disabled */
- return;
+ goto default_action;
gchar macro_name[1 + strlen(name) + 1];
QRegister *reg;
@@ -569,7 +569,7 @@ Cmdline::fnmacro(const gchar *name)
reg = QRegisters::globals[macro_name];
if (!reg)
/* macro undefined */
- return;
+ goto default_action;
mask = reg->get_integer();
if (States::current == &States::start) {
@@ -584,8 +584,30 @@ Cmdline::fnmacro(const gchar *name)
}
macro = reg->get_string();
- keypress(macro);
+ try {
+ keypress(macro);
+ } catch (...) {
+ /* could be "Quit" for instance */
+ g_free(macro);
+ throw;
+ }
g_free(macro);
+
+ return;
+
+ /*
+ * Most function key macros have no default action,
+ * except "CLOSE" which quits the application
+ * (this may loose unsaved data but is better than
+ * not doing anything if the user closes the window).
+ * NOTE: Doing the check here is less efficient than
+ * doing it in the UI implementations, but defines
+ * the default actions centrally.
+ * Also, fnmacros are only handled after key presses.
+ */
+default_action:
+ if (!strcmp(name, "CLOSE"))
+ throw Quit();
}
static gchar *
diff --git a/src/interface-curses.cpp b/src/interface-curses.cpp
index c3216ce..5dc6aa8 100644
--- a/src/interface-curses.cpp
+++ b/src/interface-curses.cpp
@@ -905,6 +905,7 @@ event_loop_iter()
FN(A1); FN(A3); FN(B2); FN(C1); FN(C3);
FNS(END);
FNS(HELP);
+ FN(CLOSE);
#undef FNS
#undef FN
diff --git a/src/interface-gtk.cpp b/src/interface-gtk.cpp
index 7f16629..00f4c0c 100644
--- a/src/interface-gtk.cpp
+++ b/src/interface-gtk.cpp
@@ -21,6 +21,7 @@
#include <stdarg.h>
#include <string.h>
+#include <signal.h>
#include <glib.h>
#include <glib/gprintf.h>
@@ -43,16 +44,34 @@
#include "interface.h"
#include "interface-gtk.h"
+/*
+ * Signal handlers (e.g. for handling SIGTERM) are only
+ * available on Unix and beginning with v2.30, while
+ * we still support v2.28.
+ * Handlers using `signal()` cannot be used easily for
+ * this purpose.
+ */
+#if defined(G_OS_UNIX) && GLIB_CHECK_VERSION(2,30,0)
+#include <glib-unix.h>
+#define SCITECO_HANDLE_SIGNALS
+#endif
+
namespace SciTECO {
extern "C" {
+
static void scintilla_notify(ScintillaObject *sci, uptr_t idFrom,
SCNotification *notify, gpointer user_data);
+
static gpointer exec_thread_cb(gpointer data);
-static gboolean cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data);
-static gboolean exit_app(GtkWidget *w, GdkEventAny *e, gpointer user_data);
-}
+static gboolean cmdline_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data);
+static gboolean window_delete_cb(GtkWidget *w, GdkEventAny *e,
+ gpointer user_data);
+
+static gboolean sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
+
+} /* extern "C" */
#define UNNAMED_FILE "(Unnamed)"
@@ -127,7 +146,7 @@ InterfaceGtk::main_impl(int &argc, char **&argv)
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME);
g_signal_connect(G_OBJECT(window), "delete-event",
- G_CALLBACK(exit_app), event_queue);
+ G_CALLBACK(window_delete_cb), event_queue);
vbox = gtk_vbox_new(FALSE, 0);
@@ -156,7 +175,7 @@ InterfaceGtk::main_impl(int &argc, char **&argv)
gtk_editable_set_editable(GTK_EDITABLE(cmdline_widget), FALSE);
widget_set_font(cmdline_widget, "Courier");
g_signal_connect(G_OBJECT(cmdline_widget), "key-press-event",
- G_CALLBACK(cmdline_key_pressed), event_queue);
+ G_CALLBACK(cmdline_key_pressed_cb), event_queue);
gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(window), vbox);
@@ -181,6 +200,10 @@ InterfaceGtk::vmsg_impl(MessageType type, const gchar *fmt, va_list ap)
va_list aq;
gchar buf[255];
+ /*
+ * stdio_vmsg() leaves `ap` undefined and we are expected
+ * to do the same and behave like vprintf().
+ */
va_copy(aq, ap);
stdio_vmsg(type, fmt, ap);
g_vsnprintf(buf, sizeof(buf), fmt, aq);
@@ -383,6 +406,16 @@ InterfaceGtk::event_loop_impl(void)
gtk_widget_show_all(window);
/*
+ * SIGTERM emulates the "Close" key just like when
+ * closing the window if supported by this version of glib.
+ * Note that this replaces SciTECO's default SIGTERM handler
+ * so it will additionally raise(SIGINT).
+ */
+#ifdef SCITECO_HANDLE_SIGNALS
+ g_unix_signal_add(SIGTERM, sigterm_handler, event_queue);
+#endif
+
+ /*
* Start up SciTECO execution thread.
* Whenever it needs to send a Scintilla message
* it locks the GDK mutex.
@@ -472,14 +505,6 @@ InterfaceGtk::handle_key_press(bool is_shift, bool is_ctl, guint keyval)
gdk_threads_leave();
switch (keyval) {
- case GDK_Break:
- /*
- * FIXME: This usually means that the window's close
- * button was pressed.
- * It should be a function key macro, with quitting
- * as the default action.
- */
- throw Quit();
case GDK_Escape:
cmdline.keypress(CTL_KEY_ESC);
break;
@@ -522,6 +547,7 @@ InterfaceGtk::handle_key_press(bool is_shift, bool is_ctl, guint keyval)
FN(KP_End, C1); FN(KP_Next, C3);
FNS(End, END);
FNS(Help, HELP);
+ FN(Close, CLOSE);
#undef FNS
#undef FN
@@ -611,8 +637,8 @@ scintilla_notify(ScintillaObject *sci, uptr_t idFrom,
}
static gboolean
-cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data)
+cmdline_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data)
{
GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
@@ -630,10 +656,12 @@ cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
is_ctl && gdk_keyval_to_upper(event->keyval) == GDK_C) {
/*
* Handle asynchronous interruptions if CTRL+C is pressed.
+ * This will usually send SIGINT to the entire process
+ * group and set `sigint_occurred`.
* If the execution thread is currently blocking,
* the key is delivered like an ordinary key press.
*/
- sigint_occurred = TRUE;
+ interrupt();
} else {
/*
* Copies the key-press event, since it must be evaluated
@@ -651,26 +679,49 @@ cmdline_key_pressed(GtkWidget *widget, GdkEventKey *event,
}
static gboolean
-exit_app(GtkWidget *w, GdkEventAny *e, gpointer user_data)
+window_delete_cb(GtkWidget *w, GdkEventAny *e, gpointer user_data)
{
GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
- GdkEventKey *break_event;
+ GdkEventKey *close_event;
/*
- * We cannot yet call gtk_main_quit() as the execution
- * thread must shut down properly.
- * Therefore we emulate that the "break" key was pressed
- * which may then be handled by the execution thread.
- * It may also be used to insert a function key macro.
- * NOTE: We might also create a GDK_DELETE event.
+ * Emulate that the "close" key was pressed
+ * which may then be handled by the execution thread
+ * which invokes the appropriate "function key macro"
+ * if it exists. Its default action will ensure that
+ * the execution thread shuts down and the main loop
+ * will eventually terminate.
*/
- break_event = (GdkEventKey *)gdk_event_new(GDK_KEY_RELEASE);
- break_event->window = gtk_widget_get_parent_window(w);
- break_event->keyval = GDK_Break;
+ close_event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
+ close_event->window = gtk_widget_get_parent_window(w);
+ close_event->keyval = GDK_Close;
- g_async_queue_push(event_queue, break_event);
+ g_async_queue_push(event_queue, close_event);
return TRUE;
}
+static gboolean
+sigterm_handler(gpointer user_data)
+{
+ GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
+ GdkEventKey *close_event;
+
+ /*
+ * Since this handler replaces the default one, we
+ * also have to make sure it interrupts.
+ */
+ interrupt();
+
+ /*
+ * Similar to window deletion - emulate "close" key press.
+ */
+ close_event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
+ close_event->keyval = GDK_Close;
+
+ g_async_queue_push(event_queue, close_event);
+
+ return G_SOURCE_CONTINUE;
+}
+
} /* namespace SciTECO */
diff --git a/src/main.cpp b/src/main.cpp
index 120d73e..951e5b3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -88,6 +88,31 @@ static gpointer g_realloc_exception(gpointer mem, gsize n_bytes);
static void sigint_handler(int signal);
}
+#ifdef G_OS_UNIX
+
+void
+interrupt(void)
+{
+ /*
+ * This sends SIGINT to the entire process group,
+ * which makes sure that subprocesses are signalled,
+ * even when called from the wrong thread.
+ */
+ if (kill(0, SIGINT))
+ sigint_occurred = TRUE;
+}
+
+#else
+
+void
+interrupt(void)
+{
+ if (raise(SIGINT))
+ sigint_occurred = TRUE;
+}
+
+#endif
+
const gchar *
get_eol_seq(gint eol_mode)
{
@@ -338,6 +363,7 @@ main(int argc, char **argv)
#endif
signal(SIGINT, sigint_handler);
+ signal(SIGTERM, sigint_handler);
g_mem_set_vtable(&vtable);
diff --git a/src/sciteco.h b/src/sciteco.h
index 117791b..0e3c8dd 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -81,6 +81,9 @@ extern sig_atomic_t sigint_occurred;
#define IS_FAILURE(X) (!IS_SUCCESS(X))
/* in main.cpp */
+void interrupt(void);
+
+/* in main.cpp */
const gchar *get_eol_seq(gint eol_mode);
namespace Validate {