aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-gtk')
-rw-r--r--src/interface-gtk/gtk-info-popup.c2
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c2
-rw-r--r--src/interface-gtk/gtk-label.h2
-rw-r--r--src/interface-gtk/interface.c301
5 files changed, 211 insertions, 98 deletions
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index 744900d..4e25224 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index de4b463..c3a62ec 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
index c1f4867..50cd345 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index d2e2314..bed6642 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 253600a..843ad15 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2023 Robin Haberkorn
+ * Copyright (C) 2012-2024 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
@@ -279,7 +279,8 @@ teco_interface_init(void)
"type-label");
gtk_header_bar_pack_start(GTK_HEADER_BAR(teco_interface.info_bar_widget),
teco_interface.info_type_widget);
- if (teco_interface.xembed_id || teco_interface.no_csd) {
+ if (teco_interface.xembed_id || teco_interface.no_csd ||
+ !g_strcmp0(g_getenv("GTK_CSD"), "0")) {
/* fall back to adding the info bar as an ordinary widget */
gtk_box_pack_start(GTK_BOX(vbox), teco_interface.info_bar_widget,
FALSE, FALSE, 0);
@@ -390,12 +391,6 @@ teco_interface_init(void)
GOptionGroup *
teco_interface_get_options(void)
{
- /*
- * FIXME: On platforms where you want to disable CSD, you usually
- * want to disable it always, so it should be configurable in the SciTECO
- * profile.
- * On the other hand, you could just install gtk3-nocsd.
- */
static const GOptionEntry entries[] = {
{"no-csd", 0, G_OPTION_FLAG_IN_MAIN,
G_OPTION_ARG_NONE, &teco_interface.no_csd,
@@ -656,15 +651,46 @@ teco_interface_get_selection_by_name(const gchar *name)
return gdk_atom_intern(name, FALSE);
}
+static void
+teco_interface_clipboard_provide(GtkClipboard *clipboard, GtkSelectionData *selection, guint info, gpointer userdata)
+{
+ GString *str = userdata;
+ gtk_selection_data_set_text(selection, str->str, str->len);
+}
+
+static void
+teco_interface_clipboard_clear(GtkClipboard *clipboard, gpointer userdata)
+{
+ GString *str = userdata;
+ g_string_free(str, TRUE);
+}
+
gboolean
teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error)
{
+ static const GtkTargetEntry target = {"UTF8_STRING", 0, 0};
GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name));
+ if (!str) {
+ gtk_clipboard_clear(clipboard);
+ return TRUE;
+ }
+
/*
- * NOTE: function has compatible semantics for str_len < 0.
+ * NOTE: gtk_clipboard_set_text() would ignore embedded nulls,
+ * even though it takes a length.
+ * We could theoretically avoid one allocation, but don't yet have proper types
+ * to store string data with length in one heap object.
*/
- gtk_clipboard_set_text(clipboard, str, str_len);
+ GString *gstr = g_string_new_len(str, str_len);
+ if (!gtk_clipboard_set_with_data(clipboard, &target, 1,
+ teco_interface_clipboard_provide,
+ teco_interface_clipboard_clear, gstr)) {
+ g_string_free(gstr, TRUE);
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CLIPBOARD,
+ "Cannot set clipboard");
+ return FALSE;
+ }
return TRUE;
}
@@ -674,16 +700,28 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
{
GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name));
/*
- * Could return NULL for an empty clipboard.
+ * gtk_clipboard_wait_for_text() does not return the text length,
+ * so it doesn't work with embedded nulls.
+ * gtk_clipboard_wait_for_contents() could also return NULL for empty clipboards.
*
- * FIXME: This converts to UTF8 and we loose the ability
- * to get clipboard with embedded nulls.
+ * NOTE: This also drives the main event loop,
+ * which should be safe (see teco_interface_key_pressed_cb()).
*/
- g_autofree gchar *contents = gtk_clipboard_wait_for_text(clipboard);
+ GdkAtom utf8_string = gdk_atom_intern_static_string("UTF8_STRING");
+ g_autoptr(GtkSelectionData) contents = gtk_clipboard_wait_for_contents(clipboard, utf8_string);
+ if (!contents) {
+ *len = 0;
+ if (str)
+ *str = NULL;
+ return TRUE;
+ }
- *len = contents ? strlen(contents) : 0;
- if (str)
- *str = g_steal_pointer(&contents);
+ *len = gtk_selection_data_get_length(contents);
+ if (str) {
+ /* gtk_selection_data_get_text() does not work with embedded nulls */
+ *str = memcpy(g_malloc(*len+1), gtk_selection_data_get_data(contents), *len);
+ (*str)[*len] = '\0';
+ }
return TRUE;
}
@@ -881,19 +919,50 @@ teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str, gpointer use
{
g_autoptr(GError) error = NULL;
- /*
- * FIXME: This is only for consistency as long as we
- * do not support Unicode.
- */
- for (char *p = str; *p != '\0'; p = g_utf8_next_char(p))
- if (g_utf8_get_char(p) >= 0x80)
- return;
-
if (!teco_cmdline_keypress(str, strlen(str), &error) &&
g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT))
gtk_main_quit();
}
+/**
+ * Try to find an ANSI (latin) key for a given keypress.
+ *
+ * If the given key press does not generate a key from the ANSI
+ * range, it tries to find one in another group.
+ *
+ * @param event Key event to look up. In case of success,
+ * this event structure might also be written to.
+ * @return The codepoint of the ANSI version or 0 if there is
+ * no fitting ANSI/latin key.
+ */
+static gchar
+teco_interface_get_ansi_key(GdkEventKey *event)
+{
+ gunichar cp = gdk_keyval_to_unicode(event->keyval);
+ if (cp && cp < 0x80)
+ return cp;
+
+ GdkKeymap *map = gdk_keymap_get_for_display(gdk_window_get_display(event->window));
+ g_autofree GdkKeymapKey *keys = NULL;
+ g_autofree guint *keyvals = NULL;
+ gint n_entries = 0;
+
+ gdk_keymap_get_entries_for_keycode(map, event->hardware_keycode,
+ &keys, &keyvals, &n_entries);
+ for (gint i = 0; i < n_entries; i++) {
+ g_assert(keys[i].keycode == event->hardware_keycode);
+ cp = gdk_keyval_to_unicode(keyvals[i]);
+ if (cp && cp < 0x80 &&
+ gdk_keyval_is_upper(keyvals[i]) == gdk_keyval_is_upper(event->keyval)) {
+ event->keyval = keyvals[i];
+ event->group = keys[i].group;
+ return cp;
+ }
+ }
+
+ return 0;
+}
+
static gboolean
teco_interface_handle_key_press(GdkEventKey *event, GError **error)
{
@@ -901,19 +970,19 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
switch (event->keyval) {
case GDK_KEY_Escape:
- if (!teco_cmdline_keypress_c('\e', error))
+ if (!teco_cmdline_keymacro_c('\e', error))
return FALSE;
break;
case GDK_KEY_BackSpace:
- if (!teco_cmdline_keypress_c(TECO_CTL_KEY('H'), error))
+ if (!teco_cmdline_keymacro_c(TECO_CTL_KEY('H'), error))
return FALSE;
break;
case GDK_KEY_Tab:
- if (!teco_cmdline_keypress_c('\t', error))
+ if (!teco_cmdline_keymacro_c('\t', error))
return FALSE;
break;
case GDK_KEY_Return:
- if (!teco_cmdline_keypress_c('\n', error))
+ if (!teco_cmdline_keymacro_c('\n', error))
return FALSE;
break;
@@ -922,12 +991,12 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
*/
#define FN(KEY, MACRO) \
case GDK_KEY_##KEY: \
- if (!teco_cmdline_fnmacro(#MACRO, error)) \
+ if (!teco_cmdline_keymacro(#MACRO, -1, error)) \
return FALSE; \
break
#define FNS(KEY, MACRO) \
case GDK_KEY_##KEY: \
- if (!teco_cmdline_fnmacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, error)) \
+ if (!teco_cmdline_keymacro(event->state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, -1, error)) \
return FALSE; \
break
FN(Down, DOWN); FN(Up, UP);
@@ -939,8 +1008,8 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
gchar macro_name[3+1];
g_snprintf(macro_name, sizeof(macro_name),
- "F%d", event->keyval - GDK_KEY_F1 + 1);
- if (!teco_cmdline_fnmacro(macro_name, error))
+ "F%d", event->keyval - GDK_KEY_F1 + 1);
+ if (!teco_cmdline_keymacro(macro_name, -1, error))
return FALSE;
break;
}
@@ -960,33 +1029,72 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
/*
* Control keys and keys with printable representation
*/
- default: {
- gunichar u = gdk_keyval_to_unicode(event->keyval);
+ default:
+ /*
+ * NOTE: Alt-Gr key-combinations are sometimes reported as
+ * Ctrl+Alt, so we filter those out.
+ */
+ if ((event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK) {
+ gchar c = teco_interface_get_ansi_key(event);
+ if (c) {
+ if (!teco_cmdline_keymacro_c(TECO_CTL_KEY(g_ascii_toupper(c)), error))
+ return FALSE;
+ break;
+ }
+ }
- if (u && u < 0x80 && (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) == GDK_CONTROL_MASK) {
- /*
- * NOTE: Alt-Gr key-combinations are sometimes reported as
- * Ctrl+Alt, so we filter those out.
- */
- if (!teco_cmdline_keypress_c(TECO_CTL_KEY(g_ascii_toupper(u)), error))
+ /*
+ * First look up a key macro.
+ * Only if it's undefined, we try to automatically find an ANSI key.
+ * On the downside, this means we cannot define key macros for dead keys
+ * or keys that require some sort of input method editing.
+ *
+ * FIXME: This might be a good reason to be able to disable the
+ * automatic ANSIfication, as we could look up the key macro in
+ * teco_interface_cmdline_commit_cb().
+ */
+ gunichar cp = gdk_keyval_to_unicode(event->keyval);
+ if (cp) {
+ char buf[6];
+ gsize len = g_unichar_to_utf8(cp, buf);
+ teco_keymacro_status_t rc = teco_cmdline_keymacro(buf, len, error);
+ if (rc == TECO_KEYMACRO_ERROR)
return FALSE;
- } else {
- /*
- * This is necessary to handle dead keys and in the future
- * for inputting Asian languages.
- *
- * FIXME: We do not yet support preediting.
- * It would be easier to forward the event to the Scintilla
- * widget and use its existing IM support.
- * But this breaks the event freezing and results in flickering.
- */
- gtk_im_context_filter_keypress(teco_interface.input_method, event);
+ if (rc == TECO_KEYMACRO_SUCCESS)
+ break;
+ g_assert(rc == TECO_KEYMACRO_UNDEFINED);
}
- }
+
+ /*
+ * If the current state is case-insensitive, it is a command name -
+ * which consists only of ANSI letters - we try to
+ * accept non-ANSI letters as well.
+ * This means, you don't have to change keyboard layouts
+ * so often.
+ * FIXME: This could be made to work with string-building constructs
+ * within Q-Register specs as well.
+ * Unfortunately, Q-Reg specs and string building can be nested
+ * indefinitely.
+ * This would effectively require a new keymacro_mask_cb().
+ */
+ if ((teco_cmdline.machine.parent.current->keymacro_mask |
+ teco_cmdline.machine.expectstring.machine.parent.current->keymacro_mask) &
+ TECO_KEYMACRO_MASK_CASEINSENSITIVE)
+ teco_interface_get_ansi_key(event);
+
+ /*
+ * This is necessary to handle dead keys and in the future
+ * for inputting Asian languages.
+ *
+ * FIXME: We do not yet support preediting.
+ * It would be easier to forward the event to the Scintilla
+ * widget and use its existing IM support.
+ * But this breaks the event freezing and results in flickering.
+ */
+ gtk_im_context_filter_keypress(teco_interface.input_method, event);
}
teco_interface_refresh(teco_interface_current_view != last_view);
-
return TRUE;
}
@@ -997,7 +1105,7 @@ teco_interface_event_loop(GError **error)
g_assert(scitecoconfig_reg != NULL);
g_auto(teco_string_t) scitecoconfig = {NULL, 0};
if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg,
- &scitecoconfig.data, &scitecoconfig.len, error))
+ &scitecoconfig.data, &scitecoconfig.len, NULL, error))
return FALSE;
if (teco_string_contains(&scitecoconfig, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
@@ -1006,45 +1114,6 @@ teco_interface_event_loop(GError **error)
}
g_assert(scitecoconfig.data != NULL);
-#ifdef G_OS_WIN32
- /*
- * FIXME: This is necessary so that the icon themes are found in the same
- * directory as sciteco.exe.
- * This fails of course when $SCITECOCONFIG is changed.
- * We should perhaps always use the absolute path of sciteco.exe.
- * If you want to install SciTECO differently, you can still set
- * $XDG_DATA_DIRS.
- *
- * FIXME FIXME FIXME: This is also currently broken.
- */
- //g_autofree char *theme_path = g_build_filename(scitecoconfig.data, "icons");
- //gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path);
-#else
- /*
- * Load icons for the GTK window.
- * This is not necessary on Windows since the icon included
- * as a resource will be used by default.
- */
- static const gchar *icon_files[] = {
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png",
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png",
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png"
- };
- GList *icon_list = NULL;
-
- for (gint i = 0; i < G_N_ELEMENTS(icon_files); i++) {
- GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_files[i], 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);
-
- g_list_free_full(icon_list, g_object_unref);
-#endif
-
/*
* Initialize the CSS variable provider and the CSS provider
* for the included fallback.css.
@@ -1087,6 +1156,50 @@ teco_interface_event_loop(GError **error)
/* don't show popup by default */
gtk_widget_hide(teco_interface.popup_widget);
+#ifdef G_OS_WIN32
+ /*
+ * FIXME: This is necessary so that the icon themes are found in the same
+ * directory as sciteco.exe.
+ * This fails of course when $SCITECOCONFIG is changed.
+ * We should perhaps always use the absolute path of sciteco.exe.
+ * If you want to install SciTECO differently, you can still set
+ * $XDG_DATA_DIRS.
+ *
+ * FIXME FIXME FIXME: This is also currently broken.
+ */
+ //g_autofree char *theme_path = g_build_filename(scitecoconfig.data, "icons");
+ //gtk_icon_theme_prepend_search_path(gtk_icon_theme_get_default(), theme_path);
+#else
+ /*
+ * Load icons for the GTK window.
+ * This is not necessary on Windows since the icon included
+ * as a resource will be used by default.
+ */
+ static const gchar *icon_files[] = {
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png",
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png",
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png"
+ };
+ GList *icon_list = NULL;
+
+ for (gint i = 0; i < G_N_ELEMENTS(icon_files); i++) {
+ GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(icon_files[i], 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);
+ }
+
+ /*
+ * The position of this call after gtk_widget_show() is important, so that
+ * tabbed and other Xembed hosts can pick up the icon.
+ * They also do not pick up the icon if set via gtk_window_set_default_icon_list().
+ */
+ gtk_window_set_icon_list(GTK_WINDOW(teco_interface.window), icon_list);
+
+ g_list_free_full(icon_list, g_object_unref);
+#endif
+
/*
* SIGTERM emulates the "Close" key just like when
* closing the window if supported by this version of glib.