/* * Copyright (C) 2012-2016 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include /* * FIXME: Because of gdk_threads_enter(). * The only way to do it in Gtk3 style would be using * idle callbacks into the main thread and sync barriers (inefficient!) * or doing it single-threaded and ticking the Gtk main loop * (may be inefficient since gtk_events_pending() is doing * syscalls; however that may be ailed by doing it less frequently). */ #define GDK_DISABLE_DEPRECATION_WARNINGS #include #include #include #include #include #include #include "gtk-info-popup.h" #include "sciteco.h" #include "string-utils.h" #include "cmdline.h" #include "qregisters.h" #include "ring.h" #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 #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_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)" #define USER_CSS_FILE ".teco_css" /** printf() format for CSS RGB colors given as guint32 */ #define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X" /** * Convert Scintilla-style BGR color triple to * RGB. */ static inline guint32 bgr2rgb(guint32 bgr) { return ((bgr & 0x0000FF) << 16) | ((bgr & 0x00FF00) << 0) | ((bgr & 0xFF0000) >> 16); } void ViewGtk::initialize_impl(void) { gint events; gdk_threads_enter(); sci = SCINTILLA(scintilla_new()); /* * We don't want the object to be destroyed * when it is removed from the vbox. */ g_object_ref_sink(G_OBJECT(sci)); scintilla_set_id(sci, 0); gtk_widget_set_size_request(get_widget(), 500, 300); /* * This disables mouse and key events on this view. * For some strange reason, masking events on * the event box does NOT work. * NOTE: Scroll events are still allowed - scrolling * is currently not under direct control of SciTECO * (i.e. it is OK the side effects of scrolling are not * tracked). */ gtk_widget_set_can_focus(get_widget(), FALSE); events = gtk_widget_get_events(get_widget()); events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); gtk_widget_set_events(get_widget(), events); g_signal_connect(G_OBJECT(sci), SCINTILLA_NOTIFY, G_CALLBACK(scintilla_notify), NULL); /* * setup() calls Scintilla messages, so we must unlock * here already to avoid deadlocks. */ gdk_threads_leave(); setup(); } GOptionGroup * InterfaceGtk::get_options(void) { const GOptionEntry entries[] = { {"no-csd", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &use_csd, "Disable client-side decorations.", NULL}, {NULL} }; GOptionGroup *group = gtk_get_option_group(TRUE); g_option_group_add_entries(group, entries); return group; } void InterfaceGtk::main_impl(int &argc, char **&argv) { static const Cmdline empty_cmdline; GtkWidget *vbox; GtkWidget *overlay_widget, *overlay_vbox; GtkWidget *message_bar_content; /* * g_thread_init() is required prior to v2.32 * (we still support v2.28) but generates a warning * on newer versions. */ #if !GLIB_CHECK_VERSION(2,32,0) g_thread_init(NULL); #endif gdk_threads_init(); gtk_init(&argc, &argv); /* * The event queue is initialized now, so we can * pass it as user data to C-linkage callbacks. */ event_queue = g_async_queue_new(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(window_delete_cb), event_queue); vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); info_current = g_strdup(""); /* * The info bar is tried to be made the title bar of the * window which also disables the default window decorations * (client-side decorations) unless --no-csd was specified. * NOTE: Client-side decoations could fail, leaving us with a * standard title bar and the info bar with close buttons. * Other window managers have undesirable side-effects. * FIXME: At lease on Gtk 3.12 we could disable the subtitle. */ info_bar_widget = gtk_header_bar_new(); gtk_widget_set_name(info_bar_widget, "sciteco-info-bar"); info_image = gtk_image_new(); gtk_header_bar_pack_start(GTK_HEADER_BAR(info_bar_widget), info_image); if (use_csd) { /* use client-side decorations */ gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(info_bar_widget), TRUE); gtk_window_set_titlebar(GTK_WINDOW(window), info_bar_widget); } else { /* fall back to adding the info bar as an ordinary widget */ gtk_box_pack_start(GTK_BOX(vbox), info_bar_widget, FALSE, FALSE, 0); } /* * Overlay widget will allow overlaying the Scintilla view * and message widgets with the info popup. * Therefore overlay_vbox (containing the view and popup) * will be the main child of the overlay. */ overlay_widget = gtk_overlay_new(); overlay_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); /* * The event box is the parent of all Scintilla views * that should be displayed. * This is handy when adding or removing current views, * enabling and disabling GDK updates and in order to filter * mouse and keyboard events going to Scintilla. */ event_box_widget = gtk_event_box_new(); gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_widget), TRUE); gtk_box_pack_start(GTK_BOX(overlay_vbox), event_box_widget, TRUE, TRUE, 0); message_bar_widget = gtk_info_bar_new(); gtk_widget_set_name(message_bar_widget, "sciteco-message-bar"); message_bar_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(message_bar_widget)); message_widget = gtk_label_new(""); gtk_misc_set_alignment(GTK_MISC(message_widget), 0., 0.); gtk_container_add(GTK_CONTAINER(message_bar_content), message_widget); gtk_box_pack_start(GTK_BOX(overlay_vbox), message_bar_widget, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox); gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0); cmdline_widget = gtk_entry_new(); gtk_widget_set_name(cmdline_widget, "sciteco-cmdline"); gtk_entry_set_has_frame(GTK_ENTRY(cmdline_widget), FALSE); 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_cb), event_queue); gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox); /* * Popup widget will be shown in the bottom * of the overlay widget (i.e. the Scintilla views), * filling the entire width. */ popup_widget = gtk_info_popup_new(); gtk_widget_set_name(popup_widget, "sciteco-info-popup"); gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), popup_widget); g_signal_connect(overlay_widget, "get-child-position", G_CALLBACK(gtk_info_popup_get_position_in_overlay), NULL); gtk_widget_grab_focus(cmdline_widget); cmdline_update(&empty_cmdline); } void InterfaceGtk::vmsg_impl(MessageType type, const gchar *fmt, va_list ap) { /* * The message types are chosen such that there is a CSS class * for every one of them. GTK_MESSAGE_OTHER does not have * a CSS class. */ static const GtkMessageType type2gtk[] = { /* [MSG_USER] = */ GTK_MESSAGE_QUESTION, /* [MSG_INFO] = */ GTK_MESSAGE_INFO, /* [MSG_WARNING] = */ GTK_MESSAGE_WARNING, /* [MSG_ERROR] = */ GTK_MESSAGE_ERROR }; 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); va_end(aq); gdk_threads_enter(); gtk_info_bar_set_message_type(GTK_INFO_BAR(message_bar_widget), type2gtk[type]); gtk_label_set_text(GTK_LABEL(message_widget), buf); if (type == MSG_ERROR) gtk_widget_error_bell(window); gdk_threads_leave(); } void InterfaceGtk::msg_clear(void) { gdk_threads_enter(); gtk_info_bar_set_message_type(GTK_INFO_BAR(message_bar_widget), GTK_MESSAGE_QUESTION); gtk_label_set_text(GTK_LABEL(message_widget), ""); gdk_threads_leave(); } void InterfaceGtk::show_view_impl(ViewGtk *view) { current_view = view; } void InterfaceGtk::refresh_info(void) { const gchar *info_type_str; gchar *info_current_canon = String::canonicalize_ctl(info_current); GIcon *icon; gchar *title; switch (info_type) { case INFO_TYPE_QREGISTER: info_type_str = PACKAGE_NAME " - "; gtk_header_bar_set_title(GTK_HEADER_BAR(info_bar_widget), info_current_canon); gtk_header_bar_set_subtitle(GTK_HEADER_BAR(info_bar_widget), "QRegister"); gtk_image_clear(GTK_IMAGE(info_image)); break; case INFO_TYPE_BUFFER_DIRTY: String::append(info_current_canon, "*"); /* fall through */ case INFO_TYPE_BUFFER: info_type_str = PACKAGE_NAME " - "; gtk_header_bar_set_title(GTK_HEADER_BAR(info_bar_widget), info_current_canon); gtk_header_bar_set_subtitle(GTK_HEADER_BAR(info_bar_widget), "Buffer"); icon = gtk_info_popup_get_icon_for_path(info_current, "text-x-generic"); if (!icon) break; gtk_image_set_from_gicon(GTK_IMAGE(info_image), icon, GTK_ICON_SIZE_LARGE_TOOLBAR); g_object_unref(icon); break; } title = g_strconcat(info_type_str, info_current_canon, NIL); gtk_window_set_title(GTK_WINDOW(window), title); g_free(title); g_free(info_current_canon); } void InterfaceGtk::info_update_impl(const QRegister *reg) { g_free(info_current); info_type = INFO_TYPE_QREGISTER; /* NOTE: will contain control characters */ info_current = g_strdup(reg->name); } void InterfaceGtk::info_update_impl(const Buffer *buffer) { g_free(info_current); info_type = buffer->dirty ? INFO_TYPE_BUFFER_DIRTY : INFO_TYPE_BUFFER; info_current = g_strdup(buffer->filename ? : UNNAMED_FILE); } void InterfaceGtk::cmdline_insert_chr(gint &pos, gchar chr) { gchar buffer[5+1]; /* * NOTE: This mapping is similar to * View::set_representations() */ switch (chr) { case CTL_KEY_ESC: strcpy(buffer, "$"); break; case '\r': strcpy(buffer, ""); break; case '\n': strcpy(buffer, ""); break; case '\t': strcpy(buffer, ""); break; default: if (IS_CTL(chr)) { buffer[0] = '^'; buffer[1] = CTL_ECHO(chr); buffer[2] = '\0'; } else { buffer[0] = chr; buffer[1] = '\0'; } } gtk_editable_insert_text(GTK_EDITABLE(cmdline_widget), buffer, -1, &pos); } void InterfaceGtk::cmdline_update_impl(const Cmdline *cmdline) { gint pos = 1; gint cmdline_len; gdk_threads_enter(); /* * We don't know if the new command line is similar to * the old one, so we can just as well rebuild it. */ gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*"); /* format effective command line */ for (guint i = 0; i < cmdline->len; i++) cmdline_insert_chr(pos, (*cmdline)[i]); /* save end of effective command line */ cmdline_len = pos; /* format rubbed out command line */ for (guint i = cmdline->len; i < cmdline->len+cmdline->rubout_len; i++) cmdline_insert_chr(pos, (*cmdline)[i]); /* set cursor after effective command line */ gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), cmdline_len); gdk_threads_leave(); } void InterfaceGtk::popup_add_impl(PopupEntryType type, const gchar *name, bool highlight) { static const GtkInfoPopupEntryType type2gtk[] = { /* [POPUP_PLAIN] = */ GTK_INFO_POPUP_PLAIN, /* [POPUP_FILE] = */ GTK_INFO_POPUP_FILE, /* [POPUP_DIRECTORY] = */ GTK_INFO_POPUP_DIRECTORY }; gdk_threads_enter(); gtk_info_popup_add(GTK_INFO_POPUP(popup_widget), type2gtk[type], name, highlight); gdk_threads_leave(); } void InterfaceGtk::popup_show_impl(void) { gdk_threads_enter(); if (gtk_widget_get_visible(popup_widget)) gtk_info_popup_scroll_page(GTK_INFO_POPUP(popup_widget)); else gtk_widget_show(popup_widget); gdk_threads_leave(); } void InterfaceGtk::popup_clear_impl(void) { gdk_threads_enter(); if (gtk_widget_get_visible(popup_widget)) { gtk_widget_hide(popup_widget); gtk_info_popup_clear(GTK_INFO_POPUP(popup_widget)); } gdk_threads_leave(); } void InterfaceGtk::widget_set_font(GtkWidget *widget, const gchar *font_name) { PangoFontDescription *font_desc; font_desc = pango_font_description_from_string(font_name); gtk_widget_modify_font(widget, font_desc); pango_font_description_free(font_desc); } void InterfaceGtk::set_css_variables_from_view(ViewGtk *view) { gchar buffer[256]; /* * Generates a CSS that sets some predefined color variables. * This effectively "exports" Scintilla styles into the CSS * world. * Those colors are used by the fallback.css shipping with SciTECO * in order to apply the SciTECO-controlled color scheme to all the * predefined UI elements. * They can also be used in user-customizations. */ g_snprintf(buffer, sizeof(buffer), "@define-color sciteco_default_fg_color " CSS_COLOR_FORMAT ";" "@define-color sciteco_default_bg_color " CSS_COLOR_FORMAT ";" "@define-color sciteco_calltip_fg_color " CSS_COLOR_FORMAT ";" "@define-color sciteco_calltip_bg_color " CSS_COLOR_FORMAT ";", bgr2rgb(view->ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)), bgr2rgb(view->ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)), bgr2rgb(view->ssm(SCI_STYLEGETFORE, STYLE_CALLTIP)), bgr2rgb(view->ssm(SCI_STYLEGETBACK, STYLE_CALLTIP))); /* * The GError and return value has been deprecated. * A CSS parsing error would point to a programming * error anyway. */ gtk_css_provider_load_from_data(css_var_provider, buffer, -1, NULL); } void InterfaceGtk::event_loop_impl(void) { static const gchar *icon_files[] = { SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png", SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png", SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png", NULL }; GdkScreen *default_screen = gdk_screen_get_default(); GtkCssProvider *user_css_provider; gchar *config_path, *user_css_file; GList *icon_list = NULL; GThread *thread; /* * Assign an icon to the window. * If the file could not be found, we fail silently. * FIXME: On Windows, it may be better to load the icon compiled * as a resource into the binary. */ for (const gchar **file = icon_files; *file; file++) { GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(*file, NULL); /* fail silently if there's a problem with one of the icons */ if (icon_pixbuf) icon_list = g_list_append(icon_list, icon_pixbuf); } gtk_window_set_default_icon_list(icon_list); if (icon_list) g_list_free_full(icon_list, g_object_unref); refresh_info(); /* * Initialize the CSS variable provider and the CSS provider * for the included fallback.css. * NOTE: The return value of gtk_css_provider_load() is deprecated. * Instead we could register for the "parsing-error" signal. * For the time being we just silently ignore parsing errors. * They will be printed to stderr by Gtk anyway. */ css_var_provider = gtk_css_provider_new(); if (current_view) /* set CSS variables initially */ set_css_variables_from_view(current_view); gtk_style_context_add_provider_for_screen(default_screen, GTK_STYLE_PROVIDER(css_var_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); user_css_provider = gtk_css_provider_new(); /* get path of $SCITECOCONFIG/.teco_css */ config_path = QRegisters::globals["$SCITECOCONFIG"]->get_string(); user_css_file = g_build_filename(config_path, USER_CSS_FILE, NIL); if (g_file_test(user_css_file, G_FILE_TEST_IS_REGULAR)) /* open user CSS */ gtk_css_provider_load_from_path(user_css_provider, user_css_file, NULL); else /* use fallback CSS */ gtk_css_provider_load_from_path(user_css_provider, SCITECODATADIR G_DIR_SEPARATOR_S "fallback.css", NULL); g_free(user_css_file); g_free(config_path); gtk_style_context_add_provider_for_screen(default_screen, GTK_STYLE_PROVIDER(user_css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER); /* * When changing views, the new widget is not * added immediately to avoid flickering in the GUI. * It is only updated once per key press and only * if it really changed. * Therefore we must add the current view to the * window initially. * For the same reason, window title updates are * deferred to once after every key press, so we must * set the window title initially. */ if (current_view) { current_view_widget = current_view->get_widget(); gtk_container_add(GTK_CONTAINER(event_box_widget), current_view_widget); } gtk_widget_show_all(window); /* don't show popup by default */ gtk_widget_hide(popup_widget); /* * 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. */ thread = g_thread_new("sciteco-exec", exec_thread_cb, event_queue); /* * NOTE: The watchers do not modify any GTK objects * using one of the methods that lock the GDK mutex. * This is from now on reserved to the execution * thread. Therefore there can be no dead-locks. */ gdk_threads_enter(); gtk_main(); gdk_threads_leave(); /* * This usually means that the user requested program * termination and the execution thread called * gtk_main_quit(). * We still wait for the execution thread to shut down * properly. This also frees `thread`. */ g_thread_join(thread); /* * Make sure the window is hidden * now already, as there may be code that has to be * executed in batch mode. */ gtk_widget_hide(window); } static gpointer exec_thread_cb(gpointer data) { GAsyncQueue *event_queue = (GAsyncQueue *)data; for (;;) { GdkEventKey *event = (GdkEventKey *)g_async_queue_pop(event_queue); bool is_shift = event->state & GDK_SHIFT_MASK; bool is_ctl = event->state & GDK_CONTROL_MASK; try { sigint_occurred = FALSE; interface.handle_key_press(is_shift, is_ctl, event->keyval); sigint_occurred = FALSE; } catch (Quit) { /* * SciTECO should terminate, so we exit * this thread. * The main loop will terminate and * event_loop() will return. */ gdk_event_free((GdkEvent *)event); gdk_threads_enter(); gtk_main_quit(); gdk_threads_leave(); break; } gdk_event_free((GdkEvent *)event); } return NULL; } void InterfaceGtk::handle_key_press(bool is_shift, bool is_ctl, guint keyval) { GdkWindow *view_window; ViewGtk *last_view = current_view; /* * Avoid redraws of the current view by freezing updates * on the view's GDK window (we're running in parallel * to the main loop so there could be frequent redraws). * By freezing updates, the behaviour is similar to * the Curses UI. */ gdk_threads_enter(); view_window = gtk_widget_get_parent_window(event_box_widget); gdk_window_freeze_updates(view_window); gdk_threads_leave(); switch (keyval) { case GDK_KEY_Escape: cmdline.keypress(CTL_KEY_ESC); break; case GDK_KEY_BackSpace: cmdline.keypress(CTL_KEY('H')); break; case GDK_KEY_Tab: cmdline.keypress('\t'); break; case GDK_KEY_Return: cmdline.keypress('\n'); break; /* * Function key macros */ #define FN(KEY, MACRO) \ case GDK_KEY_##KEY: cmdline.fnmacro(#MACRO); break #define FNS(KEY, MACRO) \ case GDK_KEY_##KEY: cmdline.fnmacro(is_shift ? "S" #MACRO : #MACRO); break FN(Down, DOWN); FN(Up, UP); FNS(Left, LEFT); FNS(Right, RIGHT); FN(KP_Down, DOWN); FN(KP_Up, UP); FNS(KP_Left, LEFT); FNS(KP_Right, RIGHT); FNS(Home, HOME); case GDK_KEY_F1...GDK_KEY_F35: { gchar macro_name[3+1]; g_snprintf(macro_name, sizeof(macro_name), "F%d", keyval - GDK_KEY_F1 + 1); cmdline.fnmacro(macro_name); break; } FNS(Delete, DC); FNS(Insert, IC); FN(Page_Down, NPAGE); FN(Page_Up, PPAGE); FNS(Print, PRINT); FN(KP_Home, A1); FN(KP_Prior, A3); FN(KP_Begin, B2); FN(KP_End, C1); FN(KP_Next, C3); FNS(End, END); FNS(Help, HELP); FN(Close, CLOSE); #undef FNS #undef FN /* * Control keys and keys with printable representation */ default: gunichar u = gdk_keyval_to_unicode(keyval); if (u && g_unichar_to_utf8(u, NULL) == 1) { gchar key; g_unichar_to_utf8(u, &key); if (key > 0x7F) break; if (is_ctl) key = CTL_KEY(g_ascii_toupper(key)); cmdline.keypress(key); } } /* * The styles configured via Scintilla might change * with every keypress. */ set_css_variables_from_view(current_view); /* * The info area is updated very often and setting the * window title each time it is updated is VERY costly. * So we set it here once after every keypress even if the * info line did not change. * View changes are also only applied here to the GTK * window even though GDK updates have been frozen since * the size reallocations are very costly. */ gdk_threads_enter(); refresh_info(); if (current_view != last_view) { /* * The last view's object is not guaranteed to * still exist. * However its widget is, due to reference counting. */ if (current_view_widget) gtk_container_remove(GTK_CONTAINER(event_box_widget), current_view_widget); current_view_widget = current_view->get_widget(); gtk_container_add(GTK_CONTAINER(event_box_widget), current_view_widget); gtk_widget_show(current_view_widget); } gdk_window_thaw_updates(view_window); gdk_threads_leave(); } InterfaceGtk::~InterfaceGtk() { g_free(info_current); if (window) gtk_widget_destroy(window); scintilla_release_resources(); if (event_queue) { GdkEvent *e; while ((e = (GdkEvent *)g_async_queue_try_pop(event_queue))) gdk_event_free(e); g_async_queue_unref(event_queue); } if (css_var_provider) g_object_unref(css_var_provider); } /* * GTK+ callbacks */ static void scintilla_notify(ScintillaObject *sci, uptr_t idFrom, SCNotification *notify, gpointer user_data) { interface.process_notify(notify); } static gboolean cmdline_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { GAsyncQueue *event_queue = (GAsyncQueue *)user_data; bool is_ctl = event->state & GDK_CONTROL_MASK; #ifdef DEBUG g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n", event->string, *event->string, event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK); #endif g_async_queue_lock(event_queue); if (g_async_queue_length_unlocked(event_queue) >= 0 && is_ctl && gdk_keyval_to_upper(event->keyval) == GDK_KEY_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. */ interrupt(); } else { /* * Copies the key-press event, since it must be evaluated * by the exec_thread_cb. This is costly, but since we're * using the event queue as a kind of keyboard buffer, * who cares? */ g_async_queue_push_unlocked(event_queue, gdk_event_copy((GdkEvent *)event)); } g_async_queue_unlock(event_queue); return TRUE; } static gboolean window_delete_cb(GtkWidget *w, GdkEventAny *e, gpointer user_data) { GAsyncQueue *event_queue = (GAsyncQueue *)user_data; GdkEventKey *close_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. */ close_event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS); close_event->window = gtk_widget_get_parent_window(w); close_event->keyval = GDK_KEY_Close; 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_KEY_Close; g_async_queue_push(event_queue, close_event); return G_SOURCE_CONTINUE; } } /* namespace SciTECO */