aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2025-02-27 11:38:42 +0300
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2025-02-27 11:38:42 +0300
commitf421c8fcf3c47c78c666906165006969ff21429e (patch)
tree38ffdfdb6e8e7dac00767494b62014a0ddb9e9a0 /src
parent08a7f8fb56d0713db26540add4a78437c0c54f0e (diff)
downloadsciteco-f421c8fcf3c47c78c666906165006969ff21429e.tar.gz
implemented ncurses clipboard support via external processes
* As an alternative to OSC-52, which is rarely supported by terminal emulators. * Makes the new mouse support much more useful since you rely on good builtin clipboard support. You can no longer e.g. just double-click a word to copy it into the "primary" selection as terminal emulators do by default. * Set $SCITECO_CLIPBOARD_SET/GET e.g. to xclip, way-copy, pbcopy or some wrapper script. * This is currently using POSIX-specific popen() API, so it behaves a bit different to command execution via EC/EG. I am not sure if it's worth rewriting with the GSpawn-API, since it will be used only on POSIX anyway and a GSpawn-based implementation is likely to be a bit larger. * Should there be some small command-line utility for interacting (esp. pasting) via OSC-52, built-in OSC-52 support could well be removed from SciTECO. Currently, I know only of https://github.com/theimpostor/osc/ and it requires very recent Go compilers. (I still haven't tested it. Quite possibly, pasting when run as a piped command is impossible.)
Diffstat (limited to 'src')
-rw-r--r--src/interface-curses/interface.c249
1 files changed, 210 insertions, 39 deletions
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index a80a7ef..42ffdc6 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -44,6 +44,10 @@
#include <glib/gprintf.h>
#include <glib/gstdio.h>
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
#include <curses.h>
#ifdef HAVE_TIGETSTR
@@ -1263,39 +1267,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
return TRUE;
}
-#elif defined(CURSES_TTY)
-
-static void
-teco_interface_init_clipboard(void)
-{
- /*
- * At least on XTerm, there are escape sequences
- * for modifying the clipboard (OSC-52).
- * This is not standardized in terminfo, so we add special
- * XTerm support here. Unfortunately, it is pretty hard to find out
- * whether clipboard operations will actually work.
- * XTerm must be at least at v203 and the corresponding window operations
- * must be enabled.
- * There is no way to find out if they are but we must
- * not register the clipboard registers if they aren't.
- * Still, XTerm clipboards are broken with Unicode characters.
- * Also, there are other terminal emulators supporting OSC-52,
- * so the XTerm version is only checked if the terminal identifies as XTerm.
- * Also, a special clipboard ED flag must be set by the user.
- *
- * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
- * not worth using it since it won't always be set and even if set, does not
- * tell you whether the terminal will actually answer to the escape sequence or not.
- */
- if (!(teco_ed & TECO_ED_OSC52) ||
- (teco_xterm_version() >= 0 && teco_xterm_version() < 203))
- return;
-
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
-}
+#elif defined(G_OS_UNIX) && defined(CURSES_TTY)
static inline gchar
get_selection_by_name(const gchar *name)
@@ -1310,9 +1282,48 @@ get_selection_by_name(const gchar *name)
return g_ascii_tolower(*name) ? : 'c';
}
-gboolean
-teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
- GError **error)
+/*
+ * OSC-52 clipboard implementation.
+ *
+ * At least on XTerm, there are escape sequences
+ * for modifying the clipboard (OSC-52).
+ * This is not standardized in terminfo, so we add special
+ * XTerm support here. Unfortunately, it is pretty hard to find out
+ * whether clipboard operations will actually work.
+ * XTerm must be at least at v203 and the corresponding window operations
+ * must be enabled.
+ * There is no way to find out if they are but we must
+ * not register the clipboard registers if they aren't.
+ * Still, XTerm clipboards are broken with Unicode characters.
+ * Also, there are other terminal emulators supporting OSC-52,
+ * so the XTerm version is only checked if the terminal identifies as XTerm.
+ * Also, a special clipboard ED flag must be set by the user.
+ *
+ * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
+ * not worth using it since it won't always be set and even if set, does not
+ * tell you whether the terminal will actually answer to the escape sequence or not.
+ *
+ * This is a rarely used feature and could theoretically also be handled
+ * by the $SCITECO_CLIPBOARD_SET/GET feature.
+ * Unfortunately, there is no readily available command-line utility allowing both
+ * copying and pasting via OSC-52.
+ * That's really the only reason we keep built-in OSC-52 clipboard support.
+ *
+ * FIXME: This is the only thing here requiring CURSES_TTY.
+ * On the other hand, there is hardly any non-PDCurses on UNIX, which is not
+ * on a TTY, so we shouldn't be loosing much by requiring both.
+ */
+
+static inline gboolean
+teco_interface_osc52_is_enabled(void)
+{
+ return teco_ed & TECO_ED_OSC52 &&
+ (teco_xterm_version() < 0 || teco_xterm_version() >= 203);
+}
+
+static gboolean
+teco_interface_osc52_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
{
fputs("\e]52;", teco_interface.screen_tty);
fputc(get_selection_by_name(name), teco_interface.screen_tty);
@@ -1351,8 +1362,8 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
return TRUE;
}
-gboolean
-teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+static gboolean
+teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
{
gboolean ret = TRUE;
@@ -1446,7 +1457,167 @@ cleanup:
return ret;
}
-#else /* !PDCURSES && !CURSES_TTY */
+/*
+ * Implementation using external processes.
+ *
+ * NOTE: This could be done with the portable GSpawn API as well,
+ * but this implementation is much simpler.
+ * We don't really need it on Windows anyway as long as we are using
+ * only PDCurses.
+ * This might only be of interest on Windows if building for the Win32 version
+ * of ncurses.
+ * As a downside, compared to GSpawn, this cannot inherit the environment
+ * variables from the global Q-Register table.
+ */
+
+static void
+teco_interface_init_clipboard(void)
+{
+ if (!teco_interface_osc52_is_enabled() &&
+ (!teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_SET", 22) ||
+ !teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_GET", 22)))
+ return;
+
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
+}
+
+gboolean
+teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_set_clipboard(name, str, str_len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_SET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot set clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "w");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ size_t len = fwrite(str, 1, str_len, pipe);
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ if (len < str_len) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error writing to process from %s", reg_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_get_clipboard(name, str, len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_GET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot get clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "r");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ gchar buffer[1024];
+ size_t read_len;
+
+ g_auto(teco_string_t) ret = {NULL, 0};
+
+ do {
+ read_len = fread(buffer, 1, sizeof(buffer), pipe);
+ teco_string_append(&ret, buffer, read_len);
+ } while (read_len == sizeof(buffer));
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ /*
+ * You may have to add a `|| true` for instance to xclip if it
+ * could fail for empty selections.
+ */
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ *str = ret.data;
+ *len = ret.len;
+ memset(&ret, 0, sizeof(ret));
+
+ return TRUE;
+}
+
+#else /* !PDCURSES && !G_OS_UNIX && !CURSES_TTY */
static void
teco_interface_init_clipboard(void)