Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

PATCH: support OSC52 paste sequence



We already have support for copying to the system clipboard via the
OSC52 sequence. Some terminals also allow the system clipboard or
primary selection to be retrieved (with \e]52;p;?\e\\). You may find
that that's disabled by default for security/privacy reasons but can be
enabled. Mostly, Shift-Insert is more convenient but I sometimes miss it
when using vi-style editing.

This patch adds support for this with the "* and "+ vim registers. Much
of the patch involves moving some of the existing copy code to the new
termquery.c source file.

Oliver

diff --git a/Doc/Zsh/zle.yo b/Doc/Zsh/zle.yo
index 31eb3f3ba..316d232c3 100644
--- a/Doc/Zsh/zle.yo
+++ b/Doc/Zsh/zle.yo
@@ -2483,9 +2483,8 @@ appended to the buffer instead of overwriting it. When using the tt("_)
 buffer, nothing happens. This can be useful for deleting text without
 affecting any buffers.
 
-Updating the system clipboard relies on specific support from the terminal.
-Reading it is not possible so a paste command with tt("*) or tt("+) will do
-nothing.
+Accessing and updating the system clipboard relies on specific support from
+the terminal.
 
 If no buffer is specified for a cut or change command, tt("1) is used, and
 the contents of tt("1) to tt("8) are each shifted along one buffer;
diff --git a/Src/Zle/termquery.c b/Src/Zle/termquery.c
index ccd6eaef0..1232a96bf 100644
--- a/Src/Zle/termquery.c
+++ b/Src/Zle/termquery.c
@@ -79,6 +79,7 @@ typedef const unsigned char seqstate_t;
 #define TINFO "\xc3"
 #define XTID  "\xc4"
 #define XTVER "\xc5"
+#define CLIP  "\xc6"
 
 /* Deterministic finite state automata for parsing terminal response sequences.
  * - alternatives decided by the first character (no back-tracking)
@@ -122,6 +123,12 @@ typedef const unsigned char seqstate_t;
 	    OR MATCH("u", KITTY ) \
 	    OR MATCH("c", DA) )))
 
+#define OSC52_STATES \
+    "\033]52;" EITHER("p" OR "c") ";" RECORD \
+    REPEAT( \
+	CAPTURE EITHER( "\033" MATCH("\\", CLIP) OR MATCH("\007", CLIP) ) \
+    OR WILDCARD )
+
 static char *EXTVAR  = ".term.extensions";
 static char *IDVAR   = ".term.id";
 static char *VERVAR  = ".term.version";
@@ -191,7 +198,7 @@ find_matching(seqstate_t* pos, int direction)
 static void
 probe_terminal(const char *tquery, seqstate_t *states,
 	void (*handle_seq) (int seq, int *numbers, int len,
-	    char *capture, int clen))
+	    char *capture, int clen, void *output), void *output)
 {
     size_t blen = 256, nlen = 16;
     char *buf = zhalloc(blen);
@@ -201,11 +208,12 @@ probe_terminal(const char *tquery, seqstate_t *states,
     int *num = numbers;
     int finish = 0, number = 0;
     int ch;
-    struct ttyinfo ti;
+    struct ttyinfo ti, torig;
 
     seqstate_t *curstate = states;
 
     gettyinfo(&ti);
+    memcpy(&torig, &ti, sizeof(torig));
 #ifdef HAS_TIO
     ti.tio.c_lflag &= (~ECHO & ~ICANON & ~ISIG);
     ti.tio.c_iflag &= ~ICRNL;
@@ -389,7 +397,7 @@ probe_terminal(const char *tquery, seqstate_t *states,
 	} else {
 	    if (sequence && !(finish = sequence == SEQ))
 		handle_seq(sequence & ~SEQ, numbers, num - numbers,
-			buf + record, capture - record);
+			buf + record, capture - record, output);
 
 	    if ((sequence || (action & 1)) &&
 		(current = start) && /* drop input from sequence */
@@ -409,7 +417,7 @@ probe_terminal(const char *tquery, seqstate_t *states,
     if (current > buf)
 	ungetbytes(buf, current - buf);
 
-    settyinfo(&shttyinfo);
+    settyinfo(&torig);
 }
 
 static void
@@ -440,7 +448,8 @@ static const char *queries[] =
 	{ TQ_BGCOLOR, TQ_FGCOLOR, TQ_KITTYKB, TQ_RGB, TQ_XTVERSION, TQ_DA };
 
 static void
-handle_query(int sequence, int *numbers, int len, char *capture, int clen)
+handle_query(int sequence, int *numbers, int len, char *capture, int clen,
+	UNUSED(void *output))
 {
     char **feat;
 
@@ -499,12 +508,85 @@ query_terminal(void) {
 		((cterm = getsparam("COLORTERM")) &&
 		    (!strcmp(cterm, "truecolor") ||
 			!strcmp(cterm, "24bit")))))
-	    handle_query(3, 0, 0, 0, 0);
+	    handle_query(3, NULL, 0, NULL, 0, NULL);
 	else
 	    struncpy(&tqend, (char *) queries[i], /* collate escape sequences */
 		sizeof(tquery) - (tqend - tquery));
     }
 
     if (tqend != tquery) /* unless nothing left after filtering */
-	probe_terminal(tquery, states, &handle_query);
+	probe_terminal(tquery, states, &handle_query, NULL);
+}
+
+static char*
+base64_encode(const char *src, size_t len) {
+    static const char* base64_table =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    const unsigned char *end = (unsigned char *)src + len;
+    const unsigned char *in = (unsigned char *)src;
+    char *ret = zhalloc(1 + 4 * ((len + 2) / 3)); /* 4 bytes out for 3 in */
+    char *cur = ret;
+
+    for (; end - in > 0; in += 3, cur += 4) {
+	unsigned int n = *in << 16;
+	cur[3] = end - in > 2 ? base64_table[(n |= in[2]) & 0x3f] : '=';
+	cur[2] = end - in > 1 ? base64_table[((n |= in[1]<<8) >> 6) & 0x3f] : '=';
+	cur[1] = base64_table[(n >> 12) & 0x3f];
+	cur[0] = base64_table[n >> 18];
+    }
+    *cur = '\0';
+
+    return ret;
+}
+
+static char*
+base64_decode(const char *src, size_t len)
+{
+    int i = 0;
+    unsigned int n;
+    char *buf = hcalloc((3 * len) / 4 + 1);
+    char *b = buf;
+    char c;
+
+    while (len && (c = src[i]) != '=') {
+	n = isdigit(c) ? c - '0' + 52 :
+	    islower(c) ? c - 'a' + 26 :
+	    isupper(c) ? c - 'A' :
+	    (c == '+') ? 62 :
+	    (c == '/') ? 63 : 0;
+	if (i % 4)
+	    *b++ |= n >> (2 * (3 - (i % 4)));
+	if (++i >= len)
+	    break;
+	*b = n << (2 * (i % 4));
+    }
+    return buf;
+}
+
+static void
+handle_paste(UNUSED(int sequence), UNUSED(int *numbers), UNUSED(int len),
+	char *capture, int clen, void *output)
+{
+    *(char**) output = base64_decode(capture, clen);
+}
+
+/**/
+char *
+system_clipget(char clip)
+{
+    static seqstate_t osc52[] = OSC52_STATES;
+    char seq[] = "\033]52;.;?\033\\";
+    char *contents;
+    seq[5] = clip;
+    probe_terminal(seq, osc52, &handle_paste, &contents);
+    return contents;
+}
+
+/**/
+void
+system_clipput(char clip, char *content, size_t clen)
+{
+    char *encoded = base64_encode(content, clen);
+    fprintf(shout, "\033]52;%c;%s\a", clip, encoded);
 }
diff --git a/Src/Zle/zle_misc.c b/Src/Zle/zle_misc.c
index e17a08d53..a2d07ee03 100644
--- a/Src/Zle/zle_misc.c
+++ b/Src/Zle/zle_misc.c
@@ -554,7 +554,8 @@ yank(UNUSED(char **args))
 }
 
 /* position: 0 is before, 1 after, 2 split the line */
-static void pastebuf(Cutbuffer buf, int mult, int position)
+static void
+pastebuf(Cutbuffer buf, int mult, int position)
 {
     int cc;
     if (buf->flags & CUTBUFFER_LINE) {
@@ -613,7 +614,20 @@ viputbefore(UNUSED(char **args))
 	return 1;
     if (zmod.flags & MOD_NULL)
 	return 0;
-    if (zmod.flags & MOD_VIBUF)
+    if (zmod.flags & MOD_OSSEL) {
+	struct cutbuffer kbuf;
+	int x;
+	char *pbuf = system_clipget(zmod.flags & MOD_CLIP ? 'c' : 'p');
+	if (!pbuf || !*pbuf)
+	    return 1;
+	kbuf.buf = stringaszleline(pbuf, 0, &x, NULL, NULL);
+	kbuf.len = x;
+	kbuf.flags = 0;
+	kct = -1;
+	yankcs = zlecs;
+	pastebuf(&kbuf, n, 0);
+	return 0;
+    } else if (zmod.flags & MOD_VIBUF)
 	kctbuf = &vibuf[zmod.vibuf];
     else
 	kctbuf = &cutbuf;
@@ -636,7 +650,20 @@ viputafter(UNUSED(char **args))
 	return 1;
     if (zmod.flags & MOD_NULL)
 	return 0;
-    if (zmod.flags & MOD_VIBUF)
+    if (zmod.flags & MOD_OSSEL) {
+	struct cutbuffer kbuf;
+	int x;
+	char *pbuf = system_clipget(zmod.flags & MOD_CLIP ? 'c' : 'p');
+	if (!pbuf || !*pbuf)
+	    return 1;
+	kbuf.buf = stringaszleline(pbuf, 0, &x, NULL, NULL);
+	kbuf.len = x;
+	kbuf.flags = 0;
+	kct = -1;
+	yankcs = zlecs;
+	pastebuf(&kbuf, n, 1);
+	return 0;
+    } else if (zmod.flags & MOD_VIBUF)
 	kctbuf = &vibuf[zmod.vibuf];
     else
 	kctbuf = &cutbuf;
@@ -661,15 +688,25 @@ putreplaceselection(UNUSED(char **args))
     startvichange(-1);
     if (n < 0 || zmod.flags & MOD_NULL)
 	return 1;
-    putbuf = (zmod.flags & MOD_VIBUF) ? &vibuf[zmod.vibuf] : &cutbuf;
-    if (!putbuf->buf)
-	return 1;
-    memcpy(&prevbuf, putbuf, sizeof(prevbuf));
+    if (zmod.flags & MOD_OSSEL) {
+        int x;
+        char *pbuf = system_clipget(zmod.flags & MOD_CLIP ? 'c' : 'p');
+        if (!pbuf || !*pbuf)
+            return 1;
+        prevbuf.buf = stringaszleline(pbuf, 0, &x, NULL, NULL);
+        prevbuf.len = x;
+        prevbuf.flags = 0;
+    } else {
+	putbuf = (zmod.flags & MOD_VIBUF) ? &vibuf[zmod.vibuf] : &cutbuf;
+	if (!putbuf->buf)
+	    return 1;
+	memcpy(&prevbuf, putbuf, sizeof(prevbuf));
 
-    /* if "9 was specified, prevent killregion from freeing it */
-    if (zmod.vibuf == 35) {
-	putbuf->buf = 0;
-	clear = 1;
+	/* if "9 was specified, prevent killregion from freeing it */
+	if (zmod.vibuf == 35) {
+	    putbuf->buf = 0;
+	    clear = 1;
+	}
     }
 
     zmod.flags = 0; /* flags apply to paste not kill */
diff --git a/Src/Zle/zle_utils.c b/Src/Zle/zle_utils.c
index e2b86e863..6e2456b1f 100644
--- a/Src/Zle/zle_utils.c
+++ b/Src/Zle/zle_utils.c
@@ -937,28 +937,6 @@ cut(int i, int ct, int flags)
   cuttext(zleline + i, ct, flags);
 }
 
-static char*
-base64_encode(const char *src, size_t len) {
-    static const char* base64_table =
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-    const unsigned char *end = (unsigned char *)src + len;
-    const unsigned char *in = (unsigned char *)src;
-    char *ret = zhalloc(1 + 4 * ((len + 2) / 3)); /* 4 bytes out for 3 in */
-    char *cur = ret;
-
-    for (; end - in > 0; in += 3, cur += 4) {
-        unsigned int n = *in << 16;
-        cur[3] = end - in > 2 ? base64_table[(n |= in[2]) & 0x3f] : '=';
-        cur[2] = end - in > 1 ? base64_table[((n |= in[1]<<8) >> 6) & 0x3f] : '=';
-        cur[1] = base64_table[(n >> 12) & 0x3f];
-        cur[0] = base64_table[n >> 18];
-    }
-    *cur = '\0';
-
-    return ret;
-}
-
 /*
  * As cut, but explicitly supply the text together with its length.
  */
@@ -975,10 +953,7 @@ cuttext(ZLE_STRING_T line, int ct, int flags)
 	int cutll;
 	char *mbcut = zlelineasstring(line, ct, 0, &cutll, NULL, 1);
 	unmetafy(mbcut, &cutll);
-	mbcut = base64_encode(mbcut, cutll);
-
-	fprintf(shout, "\033]52;%c;%s\a", zmod.flags & MOD_CLIP ? 'c' : 'p',
-		mbcut);
+	system_clipput(zmod.flags & MOD_CLIP ? 'c' : 'p', mbcut, cutll);
     } else if (zmod.flags & MOD_VIBUF) {
 	struct cutbuffer *b = &vibuf[zmod.vibuf];
 
diff --git a/Test/X06termquery.ztst b/Test/X06termquery.ztst
index 52f39ddf1..cce4e626f 100644
--- a/Test/X06termquery.ztst
+++ b/Test/X06termquery.ztst
@@ -84,6 +84,20 @@
   termresp ''
 0:no response - timeout
 
+# Following three vi-put tests also cover 0, 1 and 2 `=' padding
+# characters in the base64 decoding.
+  termresp $'\e[?0;cbindkey -v\necho \e"*p\e]52;p;YWZ0ZXI=\aa\n'
+0:paste after from clipboard
+>after
+
+  termresp $'\e[?0;cbindkey -v\necho X\e"*P\e]52;p;YmVmb3Jl\aa\n'
+0:paste before from clipboard
+>beforeX
+
+  termresp $'\e[?0;cbindkey -v\necho X\ev"*p\e]52;p;cmVwbGFjZQ==\aa\n'
+0:paste over from clipboard
+>replace
+
 %clean
 
   zmodload -ui zsh/zpty




Messages sorted by: Reverse Date, Date, Thread, Author