diff --git a/README.rst b/README.rst index 506dc53..7cb2ad4 100644 --- a/README.rst +++ b/README.rst @@ -149,6 +149,12 @@ RTV will respect the following environment variables when accessing external pro ``$RTV_URLVIEWER`` A url viewer is a tool that can be used to extract hyperlinks from inside of blocks of text. `urlview `_ and `urlscan `_ are known to be compatible with rtv. These applications don't come pre-installed, but are available through most systems' package managers. +---- +Copy +---- +RTV supports copying submission links to the OS clipboard. For macOS it is supported out of the box, +in Linux systems RTV will need `xsel `_ or `xclip `_ commands to be installed in the system. + === FAQ === diff --git a/rtv/clipboard.py b/rtv/clipboard.py new file mode 100644 index 0000000..fa16c61 --- /dev/null +++ b/rtv/clipboard.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +import os +import sys +import platform +import subprocess + +from .exceptions import ProgramError + +def _subprocess_copy(text, args_list): + p = subprocess.Popen(args_list, stdin=subprocess.PIPE, close_fds=True) + p.communicate(input=text.encode('utf-8')) + +def copy(text): + """ + Copy text to OS clipboard. + """ + + if os.name == 'mac' or platform.system() == 'Darwin': + return copy_osx(text) + elif os.name == 'posix' or platform.system() == 'Linux': + return copy_linux(text) + else: + raise NotImplementedError + +def copy_osx(text): + _subprocess_copy(text, ['pbcopy', 'w']) + +def copy_linux(text): + def get_command_name(): + # Checks for the installation of xsel or xclip + for cmd in ['xsel', 'xclip']: + cmd_exists = subprocess.call( + ['which', cmd], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) is 0 + if cmd_exists: + return cmd + return None + cmd_args = {'xsel' : ['xsel', '-b', '-i'], + 'xclip' : ['xclip', '-selection', 'c']} + cmd_name = get_command_name() + if cmd_name is None: + raise ProgramError("External copy application not installed") + _subprocess_copy(text, cmd_args.get(cmd_name)) diff --git a/rtv/docs.py b/rtv/docs.py index 49f9c1f..e07f882 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -57,6 +57,8 @@ https://github.com/michael-lazar/rtv o : Open the submission or comment url SPACE : Fold or expand the selected comment tree b : Display urls with urlview + y : Copy submission permalink to clipboard + Y : Copy submission link to clipboard [Prompt] The `/` prompt accepts subreddits in the following formats diff --git a/rtv/page.py b/rtv/page.py index 1d400d1..b2ad471 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -5,6 +5,7 @@ import os import sys import time import curses +import logging from functools import wraps import six @@ -12,9 +13,11 @@ from kitchen.text.display import textual_width from . import docs from .objects import Controller, Color, Command -from .exceptions import TemporaryFileError +from .clipboard import copy +from .exceptions import TemporaryFileError, ProgramError from .__version__ import __version__ +_logger = logging.getLogger(__name__) def logged_in(f): """ @@ -50,6 +53,7 @@ class Page(object): self.active = True self._row = 0 self._subwindows = None + self.copy_to_clipboard = copy def refresh_content(self, order=None, name=None): raise NotImplementedError @@ -318,6 +322,38 @@ class Page(object): message = 'New Messages' if inbox > 0 else 'No New Messages' self.term.show_notification(message) + @PageController.register(Command('COPY_SUBMISSION_PERMALINK')) + def copy_submission_permalink(self): + """ + Copies submission permalink to OS clipboard + """ + + data = self.get_selected_item() + url = data.get('permalink') + if url is not None: + try: + self.copy_to_clipboard(url) + except ProgramError as e: + _logger.exception(e) + self.term.show_notification( + 'Failed to copy {} to clipboard, {}'.format(url, str(e))) + + @PageController.register(Command('COPY_SUBMISSION_URL')) + def copy_post_permalink(self): + """ + Copies submission url to OS clipboard + """ + + data = self.get_selected_item() + url = data.get('url') + if url is not None: + try: + self.copy_to_clipboard(url) + except ProgramError as e: + _logger.exception(e) + self.term.show_notification( + 'Failed to copy {} to clipboard, {}'.format(url, str(e))) + def clear_input_queue(self): """ Clear excessive input caused by the scroll wheel or holding down a key diff --git a/rtv/templates/rtv.cfg b/rtv/templates/rtv.cfg index 7395d1c..3de6cbc 100644 --- a/rtv/templates/rtv.cfg +++ b/rtv/templates/rtv.cfg @@ -118,6 +118,8 @@ INBOX = i REFRESH = r, PROMPT = / SAVE = w +COPY_SUBMISSION_PERMALINK = y +COPY_SUBMISSION_URL = Y ; Submission page SUBMISSION_TOGGLE_COMMENT = 0x20 diff --git a/setup.py b/setup.py index ce0eb00..1fb777f 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ install_requires = [ 'kitchen', 'requests >=2.4.0', # https://github.com/michael-lazar/rtv/issues/325 'six', + 'pyperclip' ] tests_require = [