diff options
-rw-r--r-- | doc/sciteco.1.in | 14 | ||||
-rw-r--r-- | doc/sciteco.7.template | 16 | ||||
-rw-r--r-- | lib/fnkeys.tes | 3 | ||||
-rw-r--r-- | src/cmdline.cpp | 28 | ||||
-rw-r--r-- | src/interface-curses.cpp | 1 | ||||
-rw-r--r-- | src/interface-gtk.cpp | 109 | ||||
-rw-r--r-- | src/main.cpp | 26 | ||||
-rw-r--r-- | src/sciteco.h | 3 |
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 { |