From b1322fbe04d31b5bf2f91ab2c03056664f9fe351 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 13 Sep 2021 12:32:40 +0530 Subject: [PATCH] Prompt for permission when reading from clipboard by default Fixes #4022 --- docs/changelog.rst | 4 ++++ kittens/clipboard/main.py | 10 +++++----- kitty/options/definition.py | 13 +++++++------ kitty/options/types.py | 2 +- kitty/window.py | 36 +++++++++++++++++++++++++++++++++--- 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 19ff4d72e..9027c45a5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,10 @@ To update |kitty|, :doc:`follow the instructions `. - Allow the user to supply a custom Python function to draw tab bar. See :opt:`tab_bar_style` +- When programs ask to read from the clipboard prompt the user to allow + the request by default instead of denying by default. See + :opt:`clipboard_control` for details (:iss:`4022`) + - Fix a regression that caused :option:`kitty --title` to not work when opening new OS windows using :option:`kitty --single-instance` (:iss:`3893`) diff --git a/kittens/clipboard/main.py b/kittens/clipboard/main.py index d34d60522..8adf6d850 100644 --- a/kittens/clipboard/main.py +++ b/kittens/clipboard/main.py @@ -57,9 +57,9 @@ def on_eot(self) -> None: --get-clipboard default=False type=bool-set -Output the current contents of the clipboard to stdout. Note that this -will not work if you have not enabled the option to allow reading the clipboard -in kitty.conf +Output the current contents of the clipboard to STDOUT. Note that by default +kitty will prompt you asking to allow access to the clipboard. Can be controlled +by :opt:`clipboard_control`. --use-primary @@ -80,8 +80,8 @@ def on_eot(self) -> None: To set the clipboard text, pipe in the new text on stdin. Use the :option:`--get-clipboard` option to output the current clipboard contents to -:file:`stdout`. Note that you must enable reading of clipboard in -:file:`kitty.conf` first. +:file:`stdout`. Note that reading the clipboard will cause a permission +popup, see :opt:`clipboard_control` for details. ''' usage = '' diff --git a/kitty/options/definition.py b/kitty/options/definition.py index c0aeaa0ee..ac95f1983 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2544,16 +2544,17 @@ ''' ) -opt('clipboard_control', 'write-clipboard write-primary', +opt('clipboard_control', 'write-clipboard write-primary read-clipboard-ask read-primary-ask', option_type='clipboard_control', long_text=''' 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. See also -:opt:`clipboard_max_size`. +:code:`write-clipboard read-clipboard write-primary read-primary read-clipboard-ask read-primary-ask`. +The default is to allow writing to the clipboard and primary selection and to +ask for permission when a program tries to read from the clipboard. Note that +disabling the read confirmation is a security risk as it means that any +program, even one running on a remote server via SSH can read your clipboard. +See also :opt:`clipboard_max_size`. ''' ) diff --git a/kitty/options/types.py b/kitty/options/types.py index 16fbec8bd..19a5de570 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -463,7 +463,7 @@ class Options: clear_all_mouse_actions: bool = False clear_all_shortcuts: bool = False click_interval: float = -1.0 - clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary') + clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary', 'read-clipboard-ask', 'read-primary-ask') clipboard_max_size: float = 64.0 close_on_child_death: bool = False command_on_bell: typing.List[str] = ['none'] diff --git a/kitty/window.py b/kitty/window.py index 63ab3fbf8..cf7c140b5 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -328,6 +328,7 @@ def __init__( ): self.watchers = watchers or Watchers() self.current_mouse_event_button = 0 + self.current_clipboard_read_ask: Optional[bool] = None self.prev_osc99_cmd = NotificationCommand() self.action_on_close: Optional[Callable] = None self.action_on_removal: Optional[Callable] = None @@ -842,15 +843,17 @@ def clipboard_control(self, data: str, is_partial: bool = False) -> None: if text == '?': response = None if 's' in where or 'c' in where: + if 'read-clipboard-ask' in cc: + return self.ask_to_read_clipboard(False) response = get_clipboard_string() if 'read-clipboard' in cc else '' loc = 'c' elif 'p' in where: + if 'read-primary-ask' in cc: + return self.ask_to_read_clipboard(True) response = get_primary_selection() if 'read-primary' in cc else '' loc = 'p' response = response or '' - from base64 import standard_b64encode - self.screen.send_escape_code_to_child(OSC, '52;{};{}'.format( - loc, standard_b64encode(response.encode('utf-8')).decode('ascii'))) + self.send_osc52(loc, response or '') else: from base64 import standard_b64decode @@ -867,6 +870,33 @@ def clipboard_control(self, data: str, is_partial: bool = False) -> None: set_primary_selection(text) self.clipboard_pending = None + def send_osc52(self, loc: str, response: str) -> None: + from base64 import standard_b64encode + self.screen.send_escape_code_to_child(OSC, '52;{};{}'.format( + loc, standard_b64encode(response.encode('utf-8')).decode('ascii'))) + + def ask_to_read_clipboard(self, primary: bool = False) -> None: + if self.current_clipboard_read_ask is not None: + self.current_clipboard_read_ask = primary + return + self.current_clipboard_read_ask = primary + get_boss()._run_kitten('ask', ['--type=yesno', '--message', _( + 'A program running in this window wants to read from the system clipboard.' + ' Allow it do so, once?')], + window=self, + custom_callback=self.handle_clipboard_confirmation + ) + + def handle_clipboard_confirmation(self, data: Dict[str, Any], *a: Any) -> None: + try: + loc = 'p' if self.current_clipboard_read_ask else 'c' + response = '' + if data['response'] == 'y': + response = get_primary_selection() if self.current_clipboard_read_ask else get_clipboard_string() + self.send_osc52(loc, response) + finally: + self.current_clipboard_read_ask = None + def manipulate_title_stack(self, pop: bool, title: str, icon: Any) -> None: if title: if pop: