From 889ca7791244253cb08fbc3eca8883a87fb943a7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 May 2018 09:57:39 +0530 Subject: [PATCH] Allow programs running in kitty to read/write from the clipboard By default only writing is allowed. There is a config option to enable reading, if needed. --- kitty/config.py | 1 + kitty/kitty.conf | 8 ++++++++ kitty/parser.c | 3 +++ kitty/screen.c | 5 +++++ kitty/screen.h | 1 + kitty/window.py | 43 +++++++++++++++++++++++++++++++++++++------ 6 files changed, 55 insertions(+), 6 deletions(-) diff --git a/kitty/config.py b/kitty/config.py index 63ebdf8f3..9b2cef5f9 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -355,6 +355,7 @@ type_map = { 'bell_on_tab': to_bool, 'kitty_mod': to_modifiers, 'clear_all_shortcuts': to_bool, + 'clipboard_control': lambda x: frozenset(x.lower().split()), } for name in ( diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 8e9effa02..fa6e9bb90 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -318,6 +318,14 @@ close_on_child_death no # Note that this even works over ssh connections. allow_remote_control no +# Allow programs running in kitty to read and write from the clipboard. You can +# control exactly which actions are allowed. The set of possible actions is: +# write-clipboard read-clipboard write-primary read-primary +# The default is to allow writing to the clipboard and primary selection. Note +# that enabling the read functionality is a security risk as it means that any +# program, even one running on a remote server via SSH can read your clipboard. +clipboard_control write-clipboard write-primary + # The value of the TERM environment variable to set. Changing this can break # many terminal programs, only change it if you know what you are doing, not # because you read some advice on Stack Overflow to change it. diff --git a/kitty/parser.c b/kitty/parser.c index e2cbffb50..43fb2e9a1 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -341,6 +341,9 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { case 119: SET_COLOR(set_dynamic_color); break; + case 52: + DISPATCH_OSC(clipboard_control); + break; default: REPORT_ERROR("Unknown OSC code: %u", code); break; diff --git a/kitty/screen.c b/kitty/screen.c index f2e019364..e5c30ecc1 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1194,6 +1194,11 @@ set_dynamic_color(Screen *self, unsigned int code, PyObject *color) { else { CALLBACK("set_dynamic_color", "IO", code, color); } } +void +clipboard_control(Screen *self, PyObject *data) { + CALLBACK("clipboard_control", "O", data); +} + void set_color_table_color(Screen *self, unsigned int code, PyObject *color) { if (color == NULL) { CALLBACK("set_color_table_color", "Is", code, ""); } diff --git a/kitty/screen.h b/kitty/screen.h index a4c5eb68f..fd30e9fa1 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -143,6 +143,7 @@ void screen_use_latin1(Screen *, bool); void set_title(Screen *self, PyObject*); void set_icon(Screen *self, PyObject*); void set_dynamic_color(Screen *self, unsigned int code, PyObject*); +void clipboard_control(Screen *self, PyObject*); void set_color_table_color(Screen *self, unsigned int code, PyObject*); uint32_t* translation_table(uint32_t which); void screen_request_capabilities(Screen *, char, PyObject *); diff --git a/kitty/window.py b/kitty/window.py index 193a48f6a..95ea1dc67 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -17,17 +17,17 @@ from .fast_data_types import ( BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CSI, CURSOR_PROGRAM, DCS, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, OSC, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, - add_window, compile_program, glfw_post_empty_event, init_cell_program, - init_cursor_program, set_clipboard_string, set_titlebar_color, - set_window_render_data, update_window_title, update_window_visibility, - viewport_for_window + add_window, compile_program, get_clipboard_string, glfw_post_empty_event, + init_cell_program, init_cursor_program, set_clipboard_string, + set_titlebar_color, set_window_render_data, update_window_title, + update_window_visibility, viewport_for_window ) from .keys import keyboard_mode_name from .rgb import to_color from .terminfo import get_capabilities from .utils import ( - color_as_int, load_shaders, open_cmd, open_url, parse_color_set, - sanitize_title + color_as_int, get_primary_selection, load_shaders, log_error, open_cmd, + open_url, parse_color_set, sanitize_title, set_primary_selection ) @@ -331,6 +331,37 @@ class Window: def send_cmd_response(self, response): self.screen.send_escape_code_to_child(DCS, '@kitty-cmd' + json.dumps(response)) + def clipboard_control(self, data): + where, text = data.partition(';')[::2] + if text == '?': + response = None + if 'read-clipboard' in self.opts.clipboard_control and ('s' in where or 'c' in where): + response = get_clipboard_string() + loc = 'c' + elif 'p' in where and 'read-primary' in self.opts.clipboard_control: + response = get_primary_selection() + loc = 'p' + if response is not None: + from base64 import standard_b64encode + self.screen.send_escape_code_to_child(OSC, '{};{}'.format( + loc, standard_b64encode(response.encode('utf-8')).decode('ascii'))) + + else: + from base64 import standard_b64decode + try: + text = standard_b64decode(text).decode('utf-8') + except Exception: + log_error('Invalid data to write to clipboard received, ignoring') + return + if 's' in where or 'c' in where: + if 'write-clipboard' in self.opts.clipboard_control: + set_clipboard_string(text) + if 'p' in where: + if self.opts.copy_on_select: + if 'write-clipboard' in self.opts.clipboard_control: + set_clipboard_string(text) + if 'write-primary' in self.opts.clipboard_control: + set_primary_selection(text) # }}} def text_for_selection(self):