diff --git a/README.md b/README.md index e6777b94..52d5e6f7 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,26 @@ The variable `vterm-use-vterm-prompt-detection-method` determines whether to use the vterm prompt tracking, if false it use the regexp in `vterm-copy-prompt-regexp` to search for the prompt. +## `vterm-enable-manipulate-selection-data-by-osc52` + +Vterm support copy text to emacs kill ring and system clipboard by using OSC 52. +See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html for more info about OSC 52. +For example: send 'blabla' to kill ring: printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" + +tmux can share its copy buffer to terminals bysupporting osc52(like iterm2 xterm), +you can enable this feature for tmux by : +set -g set-clipboard on #osc 52 copy paste share with iterm +set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007' +set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007' + +The clipboard querying/clearing functionality offered by OSC 52 is not implemented here, +And for security reason, this feature is disabled by default." + +This feature need the new way of handling strings with a struct `VTermStringFragment` +in libvterm. You'd better compile emacs-libvterm with `cmake -DUSE_SYSTEM_LIBVTERM=no ..`. +If you don't do that, when the content you want to copied is too long, it would be truncated +by bug of libvterm. + ## `vterm-buffer-name-string` When `vterm-buffer-name-string` is not nil, vterm renames automatically its own diff --git a/elisp.c b/elisp.c index 43f0ef94..8b4dfedc 100644 --- a/elisp.c +++ b/elisp.c @@ -54,6 +54,7 @@ emacs_value Fvterm_invalidate; emacs_value Feq; emacs_value Fvterm_get_color; emacs_value Fvterm_eval; +emacs_value Fvterm_selection; /* Set the function cell of the symbol named NAME to SFUN using the 'fset' function. */ @@ -194,3 +195,9 @@ void vterm_invalidate(emacs_env *env) { emacs_value vterm_eval(emacs_env *env, emacs_value string) { return env->funcall(env, Fvterm_eval, 1, (emacs_value[]){string}); } + +emacs_value vterm_selection(emacs_env *env, emacs_value selection_target, + emacs_value selection_data) { + return env->funcall(env, Fvterm_selection, 2, + (emacs_value[]){selection_target, selection_data}); +} diff --git a/elisp.h b/elisp.h index d59f182a..2fd64f41 100644 --- a/elisp.h +++ b/elisp.h @@ -57,6 +57,7 @@ extern emacs_value Fvterm_invalidate; extern emacs_value Feq; extern emacs_value Fvterm_get_color; extern emacs_value Fvterm_eval; +extern emacs_value Fvterm_selection; // Utils 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); void vterm_invalidate(emacs_env *env); emacs_value vterm_get_color(emacs_env *env, int index); emacs_value vterm_eval(emacs_env *env, emacs_value string); +emacs_value vterm_selection(emacs_env *env, emacs_value selection_target, + emacs_value selection_data); #endif /* ELISP_H */ diff --git a/vterm-module.c b/vterm-module.c index c99ed02d..c976e950 100644 --- a/vterm-module.c +++ b/vterm-module.c @@ -578,6 +578,15 @@ static void term_redraw(Term *term, emacs_env *env) { free(term->elisp_code); term->elisp_code = NULL; } + if (term->selection_data) { + emacs_value selection_target = env->make_string( + env, &term->selection_target[0], strlen(&term->selection_target[0])); + emacs_value selection_data = env->make_string(env, term->selection_data, + strlen(term->selection_data)); + vterm_selection(env, selection_target, selection_data); + free(term->selection_data); + term->selection_data = NULL; + } term->is_invalidated = false; } @@ -938,6 +947,10 @@ void term_finalize(void *object) { free(term->cmd_buffer); term->cmd_buffer = NULL; } + if (term->selection_data) { + free(term->selection_data); + term->selection_data = NULL; + } for (int i = 0; i < term->lines_len; i++) { if (term->lines[i] != NULL) { @@ -995,6 +1008,37 @@ static int handle_osc_cmd_51(Term *term, char subCmd, char *buffer) { } return 0; } +static int handle_osc_cmd_52(Term *term, char *buffer) { + /* OSC 52 ; Pc ; Pd BEL */ + /* Manipulate Selection Data */ + /* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html */ + /* test by printf "\033]52;c;$(printf "%s" "blabla" | base64)\a" */ + + for (int i = 0; i < SELECTION_TARGET_MAX; i++) { /* reset Pc */ + term->selection_target[i] = 0; + } + int selection_target_idx = 0; + size_t cmdlen = strlen(buffer); + + for (int i = 0; i < cmdlen; i++) { + /* OSC 52 ; Pc ; Pd BEL */ + if (buffer[i] == ';') { /* find the second ";" */ + term->selection_data = malloc(cmdlen - i); + strcpy(term->selection_data, &buffer[i + 1]); + break; + } + if (selection_target_idx < SELECTION_TARGET_MAX) { + /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ + /* for clipboard, primary, secondary, select, or cut buffers 0 through 7 + * respectively */ + term->selection_target[selection_target_idx] = buffer[i]; + selection_target_idx++; + } else { /* len of Pc should not >12 just ignore this cmd,am I wrong? */ + return 0; + } + } + return 1; +} static int handle_osc_cmd(Term *term, int cmd, char *buffer) { if (cmd == 51) { char subCmd = '0'; @@ -1002,7 +1046,10 @@ static int handle_osc_cmd(Term *term, int cmd, char *buffer) { return 0; } subCmd = buffer[0]; + /* ++ skip the subcmd char */ return handle_osc_cmd_51(term, subCmd, ++buffer); + } else if (cmd == 52) { + return handle_osc_cmd_52(term, buffer); } return 0; } @@ -1019,10 +1066,13 @@ static int osc_callback(const char *command, size_t cmdlen, void *user) { } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '1' && buffer[2] == ';' && buffer[3] == 'E') { return handle_osc_cmd_51(term, 'E', &buffer[4]); + } else if (cmdlen > 4 && buffer[0] == '5' && buffer[1] == '2' && + buffer[2] == ';') { + /* OSC 52 ; Pc ; Pd BEL */ + return handle_osc_cmd_52(term, &buffer[3]); } return 0; } - static VTermParserCallbacks parser_callbacks = { .text = NULL, .control = NULL, @@ -1042,10 +1092,16 @@ static int osc_callback(int cmd, VTermStringFragment frag, void *user) { /* "51;E" executes elisp code */ /* The elisp code is executed in term_redraw */ + /* "52;[cpqs01234567];data" Manipulate Selection Data */ + /* I think libvterm has bug ,sometimes when the data is long enough ,the final + * fragment is missed */ + /* printf "\033]52;c;$(printf "%s" $(ruby -e 'print "x"*999999')|base64)\a" + */ + Term *term = (Term *)user; if (frag.initial) { - /* drop old fragment,because this is a initial fragment */ + /* drop old fragment,because this is a initial fragment */ if (term->cmd_buffer) { free(term->cmd_buffer); term->cmd_buffer = NULL; @@ -1128,6 +1184,7 @@ emacs_value Fvterm_new(emacs_env *env, ptrdiff_t nargs, emacs_value args[], term->directory_changed = false; term->elisp_code = NULL; term->elisp_code_changed = false; + term->selection_data = NULL; term->cmd_buffer = NULL; @@ -1337,6 +1394,8 @@ int emacs_module_init(struct emacs_runtime *ert) { Fvterm_get_color = env->make_global_ref(env, env->intern(env, "vterm--get-color")); Fvterm_eval = env->make_global_ref(env, env->intern(env, "vterm--eval")); + Fvterm_selection = + env->make_global_ref(env, env->intern(env, "vterm--selection")); // Exported functions emacs_value fun; diff --git a/vterm-module.h b/vterm-module.h index 2b585411..63308275 100644 --- a/vterm-module.h +++ b/vterm-module.h @@ -38,6 +38,10 @@ enum { VTERM_PROP_CURSOR_NOT_VISIBLE = 5, }; +/* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ +/* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ +#define SELECTION_TARGET_MAX 12 + typedef struct Cursor { int row, col; int cursor_type; @@ -75,6 +79,11 @@ typedef struct Term { char *elisp_code; bool elisp_code_changed; + /* c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 */ + /* clipboard, primary, secondary, select, or cut buffers 0 through 7 */ + char selection_target[SELECTION_TARGET_MAX]; + char *selection_data; + /* the size of dirs almost = window height, value = directory of that line */ LineInfo **lines; int lines_len; diff --git a/vterm.el b/vterm.el index f1cd6637..2c7a216d 100644 --- a/vterm.el +++ b/vterm.el @@ -269,6 +269,22 @@ information on the how to configure the shell." :type 'string :group 'vterm) +(defcustom vterm-enable-manipulate-selection-data-by-osc52 nil + "Support OSC 52 MANIPULATE SELECTION DATA. + +Support copy text to emacs kill ring and system clipboard by using OSC 52. +For example: send base64 encoded 'foo' to kill ring: echo -en '\e]52;c;Zm9v\a', +tmux can share its copy buffer to terminals by supporting osc52(like iterm2 xterm), +you can enable this feature for tmux by : +set -g set-clipboard on #osc 52 copy paste share with iterm +set -ga terminal-overrides ',xterm*:XT:Ms=\E]52;%p1%s;%p2%s\007' +set -ga terminal-overrides ',screen*:XT:Ms=\E]52;%p1%s;%p2%s\007' + +The clipboard querying/clearing functionality offered by OSC 52 is not implemented here, +And for security reason, this feature is disabled by default." + :type 'boolean + :group 'vterm) + ;; TODO: Improve doc string, it should not point to the readme but it should ;; be self-contained. (defcustom vterm-eval-cmds '(("find-file" find-file) @@ -922,6 +938,31 @@ Argument BUFFER the terminal buffer." (when (cl-member (selected-window) windows :test #'eq) (set-window-hscroll (selected-window) 0)))))))) +(defun vterm--selection (targets data) + "OSC 52 Manipulate Selection Data. +Search Manipulate Selection Data in + https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ." + (when vterm-enable-manipulate-selection-data-by-osc52 + (unless (or (string-equal data "?") + (string-empty-p data)) + (let ((decoded-data (decode-coding-string + (base64-decode-string data) locale-coding-system)) + (select-enable-clipboard select-enable-clipboard) + (select-enable-primary select-enable-primary)) + ;; https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + ;; c , p , q , s , 0 , 1 , 2 , 3 , 4 , 5 , 6 , and 7 + ;; clipboard, primary, secondary, select, or cut buffers 0 through 7 + (unless (string-empty-p targets) + (setq select-enable-clipboard nil) + (setq select-enable-primary nil)) + (when (cl-find ?c targets) + (setq select-enable-clipboard t)) + (when (cl-find ?p targets) + (setq select-enable-primary t)) + + (kill-new decoded-data) + (message "kill-ring is updated by vterm OSC 52(Manipulate Selection Data)"))))) + ;;; Entry Points ;;;###autoload