Skip to content

Commit 79b1b2c

Browse files
committed
Support osc 52 Manipulate Selection Data.
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
1 parent e4ed7c7 commit 79b1b2c

File tree

6 files changed

+141
-2
lines changed

6 files changed

+141
-2
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,26 @@ The variable `vterm-use-vterm-prompt-detection-method` determines whether to use
331331
the vterm prompt tracking, if false it use the regexp in
332332
`vterm-copy-prompt-regexp` to search for the prompt.
333333
334+
## `vterm-enable-manipulate-selection-data-by-osc52`
335+
336+
Vterm support copy text to emacs kill ring and system clipboard by using OSC 52.
337+
See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html for more info about OSC 52.
338+
For example: send 'blabla' to kill ring: printf "\033]52;c;$(printf "%s" "blabla" | base64)\a"
339+
340+
tmux can share its copy buffer to terminals bysupporting osc52(like iterm2 xterm),
341+
you can enable this feature for tmux by :
342+
set -g set-clipboard on #osc 52 copy paste share with iterm
343+
set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007'
344+
set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007'
345+
346+
The clipboard querying/clearing functionality offered by OSC 52 is not implemented here,
347+
And for security reason, this feature is disabled by default."
348+
349+
This feature need the new way of handling strings with a struct `VTermStringFragment`
350+
in libvterm. You'd better compile emacs-libvterm with `cmake -DUSE_SYSTEM_LIBVTERM=no ..`.
351+
If you don't do that, when the content you want to copied is too long, it would be truncated
352+
by bug of libvterm.
353+
334354
## `vterm-buffer-name-string`
335355
336356
When `vterm-buffer-name-string` is not nil, vterm renames automatically its own

elisp.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ emacs_value Fvterm_invalidate;
5454
emacs_value Feq;
5555
emacs_value Fvterm_get_color;
5656
emacs_value Fvterm_eval;
57+
emacs_value Fvterm_selection;
5758

5859
/* Set the function cell of the symbol named NAME to SFUN using
5960
the 'fset' function. */
@@ -194,3 +195,9 @@ void vterm_invalidate(emacs_env *env) {
194195
emacs_value vterm_eval(emacs_env *env, emacs_value string) {
195196
return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string});
196197
}
198+
199+
emacs_value vterm_selection(emacs_env *env, emacs_value selection_target,
200+
emacs_value selection_data) {
201+
return env->funcall(env, Fvterm_selection, 2,
202+
(emacs_value[]){selection_target, selection_data});
203+
}

elisp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ extern emacs_value Fvterm_invalidate;
5757
extern emacs_value Feq;
5858
extern emacs_value Fvterm_get_color;
5959
extern emacs_value Fvterm_eval;
60+
extern emacs_value Fvterm_selection;
6061

6162
// Utils
6263
void bind_function(emacs_env *env, const char *name, emacs_value Sfun);
@@ -90,5 +91,7 @@ void set_directory(emacs_env *env, emacs_value string);
9091
void vterm_invalidate(emacs_env *env);
9192
emacs_value vterm_get_color(emacs_env *env, int index);
9293
emacs_value vterm_eval(emacs_env *env, emacs_value string);
94+
emacs_value vterm_selection(emacs_env *env, emacs_value selection_target,
95+
emacs_value selection_data);
9396

9497
#endif /* ELISP_H */

vterm-module.c

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,15 @@ static void term_redraw(Term *term, emacs_env *env) {
578578
free(term->elisp_code);
579579
term->elisp_code = NULL;
580580
}
581+
if (term->selection_data) {
582+
emacs_value selection_target = env->make_string(
583+
env, &term->selection_target[0], strlen(&term->selection_target[0]));
584+
emacs_value selection_data = env->make_string(env, term->selection_data,
585+
strlen(term->selection_data));
586+
vterm_selection(env, selection_target, selection_data);
587+
free(term->selection_data);
588+
term->selection_data = NULL;
589+
}
581590

582591
term->is_invalidated = false;
583592
}
@@ -937,6 +946,10 @@ void term_finalize(void *object) {
937946
free(term->cmd_buffer);
938947
term->cmd_buffer = NULL;
939948
}
949+
if (term->selection_data) {
950+
free(term->selection_data);
951+
term->selection_data = NULL;
952+
}
940953

941954
for (int i = 0; i < term->lines_len; i++) {
942955
if (term->lines[i] != NULL) {
@@ -994,14 +1007,48 @@ static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) {
9941007
}
9951008
return 0;
9961009
}
1010+
static int handle_osc_cmd_52(Term *term, char *buffer) {
1011+
/* OSC 52 ; Pc ; Pd BEL */
1012+
/* Manipulate Selection Data */
1013+
/* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */
1014+
/* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */
1015+
1016+
for (int i = 0; i < SELECTION_TARGET_MAX; i++) { /* reset Pc */
1017+
term->selection_target[i] = 0;
1018+
}
1019+
int selection_target_idx = 0;
1020+
size_t cmdlen = strlen(buffer);
1021+
1022+
for (int i = 0; i < cmdlen; i++) {
1023+
/* OSC 52 ; Pc ; Pd BEL */
1024+
if (buffer[i] == ';') { /* find the second ";" */
1025+
term->selection_data = malloc(cmdlen - i);
1026+
strcpy(term->selection_data, &buffer[i + 1]);
1027+
break;
1028+
}
1029+
if (selection_target_idx < SELECTION_TARGET_MAX) {
1030+
/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
1031+
/* for clipboard, primary, secondary, select, or cut buffers 0 through 7
1032+
* respectively */
1033+
term->selection_target[selection_target_idx] = buffer[i];
1034+
selection_target_idx++;
1035+
} else { /* len of Pc should not >12 just ignore this cmd,am I wrong? */
1036+
return 0;
1037+
}
1038+
}
1039+
return 1;
1040+
}
9971041
static int handle_osc_cmd(Term *term, int cmd, char *buffer) {
9981042
if (cmd == 51) {
9991043
char subCmd = '0';
10001044
if (strlen(buffer) == 0) {
10011045
return 0;
10021046
}
10031047
subCmd = buffer[0];
1048+
/* ++ skip the subcmd char */
10041049
return handle_osc_cmd_51(term, subCmd, ++buffer);
1050+
} else if (cmd == 52) {
1051+
return handle_osc_cmd_52(term, buffer);
10051052
}
10061053
return 0;
10071054
}
@@ -1018,10 +1065,13 @@ static int osc_callback(const char *command, size_t cmdlen, void *user) {
10181065
} else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' &&
10191066
buffer[2] == ';' && buffer[3] == 'E') {
10201067
return handle_osc_cmd_51(term, 'E', &buffer[4]);
1068+
} else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '2' &&
1069+
buffer[2] == ';') {
1070+
/* OSC 52 ; Pc ; Pd BEL */
1071+
return handle_osc_cmd_52(term, &buffer[3]);
10211072
}
10221073
return 0;
10231074
}
1024-
10251075
static VTermParserCallbacks parser_callbacks = {
10261076
.text = NULL,
10271077
.control = NULL,
@@ -1041,10 +1091,16 @@ static int osc_callback(int cmd, VTermStringFragment frag, void *user) {
10411091
/* "51;E" executes elisp code */
10421092
/* The elisp code is executed in term_redraw */
10431093

1094+
/* "52;[cpqs01234567];data" Manipulate Selection Data */
1095+
/* I think libvterm has bug ,sometimes when the data is long enough ,the final
1096+
* fragment is missed */
1097+
/* printf "\033]52;c;$(printf "%s" $(ruby -e 'print "x"*999999')|base64)\a"
1098+
*/
1099+
10441100
Term *term = (Term *)user;
10451101

10461102
if (frag.initial) {
1047-
/* drop old fragment,because this is a initial fragment */
1103+
/* drop old fragment,because this is a initial fragment */
10481104
if (term->cmd_buffer) {
10491105
free(term->cmd_buffer);
10501106
term->cmd_buffer = NULL;
@@ -1127,6 +1183,7 @@ emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[],
11271183
term->directory_changed = false;
11281184
term->elisp_code = NULL;
11291185
term->elisp_code_changed = false;
1186+
term->selection_data = NULL;
11301187

11311188
term->cmd_buffer = NULL;
11321189

@@ -1336,6 +1393,8 @@ int emacs_module_init(struct emacs_runtime *ert) {
13361393
Fvterm_get_color =
13371394
env->make_global_ref(env, env->intern(env, "vterm--get-color"));
13381395
Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval"));
1396+
Fvterm_selection =
1397+
env->make_global_ref(env, env->intern(env, "vterm--selection"));
13391398

13401399
// Exported functions
13411400
emacs_value fun;

vterm-module.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ enum {
3838
VTERM_PROP_CURSOR_NOT_VISIBLE = 5,
3939
};
4040

41+
/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
42+
/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
43+
#define SELECTION_TARGET_MAX 12
44+
4145
typedef struct Cursor {
4246
int row, col;
4347
int cursor_type;
@@ -75,6 +79,11 @@ typedef struct Term {
7579
char *elisp_code;
7680
bool elisp_code_changed;
7781

82+
/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */
83+
/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */
84+
char selection_target[SELECTION_TARGET_MAX];
85+
char *selection_data;
86+
7887
/* the size of dirs almost = window height, value = directory of that line */
7988
LineInfo **lines;
8089
int lines_len;

vterm.el

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,22 @@ information on the how to configure the shell."
269269
:type 'string
270270
:group 'vterm)
271271

272+
(defcustom vterm-enable-manipulate-selection-data-by-osc52 nil
273+
"Support OSC 52 MANIPULATE SELECTION DATA.
274+
275+
Support copy text to emacs kill ring and system clipboard by using OSC 52.
276+
For example: send base64 encoded 'foo' to kill ring: echo -en '\e]52;c;Zm9v\a',
277+
tmux can share its copy buffer to terminals by supporting osc52(like iterm2 xterm),
278+
you can enable this feature for tmux by :
279+
set -g set-clipboard on #osc 52 copy paste share with iterm
280+
set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007'
281+
set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007'
282+
283+
The clipboard querying/clearing functionality offered by OSC 52 is not implemented here,
284+
And for security reason, this feature is disabled by default."
285+
:type 'boolean
286+
:group 'vterm)
287+
272288
;; TODO: Improve doc string, it should not point to the readme but it should
273289
;; be self-contained.
274290
(defcustom vterm-eval-cmds '(("find-file" find-file)
@@ -922,6 +938,31 @@ Argument BUFFER the terminal buffer."
922938
(when (cl-member (selected-window) windows :test #'eq)
923939
(set-window-hscroll (selected-window) 0))))))))
924940

941+
(defun vterm--selection (targets data)
942+
"OSC 52 Manipulate Selection Data.
943+
Search Manipulate Selection Data in
944+
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ."
945+
(when vterm-enable-manipulate-selection-data-by-osc52
946+
(unless (or (string-equal data "?")
947+
(string-empty-p data))
948+
(let ((decoded-data (decode-coding-string
949+
(base64-decode-string data) locale-coding-system))
950+
(select-enable-clipboard select-enable-clipboard)
951+
(select-enable-primary select-enable-primary))
952+
;; https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
953+
;; c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7
954+
;; clipboard, primary, secondary, select, or cut buffers 0 through 7
955+
(unless (string-empty-p targets)
956+
(setq select-enable-clipboard nil)
957+
(setq select-enable-primary nil))
958+
(when (cl-find ?c targets)
959+
(setq select-enable-clipboard t))
960+
(when (cl-find ?p targets)
961+
(setq select-enable-primary t))
962+
963+
(kill-new decoded-data)
964+
(message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)")))))
965+
925966
;;; Entry Points
926967

927968
;;;###autoload

0 commit comments

Comments
 (0)