aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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 {