From 04615e84bc495ce70563436eecf238ef7ec51866 Mon Sep 17 00:00:00 2001 From: Daniel Schwarz Date: Mon, 6 Mar 2023 19:30:54 -0500 Subject: [PATCH] Add cop[y] status feature - copies status text to clipboard This relies on the OSC 52 terminal feature, which is widely supported (Windows Terminal, iTerm2, XTerm, Kitty, others) --- toot/tui/app.py | 13 ++++++++++++- toot/tui/overlays.py | 1 + toot/tui/timeline.py | 6 ++++++ toot/tui/utils.py | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/toot/tui/app.py b/toot/tui/app.py index 2ded137..3d94666 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -14,7 +14,7 @@ from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource, StatusL from .overlays import StatusDeleteConfirmation, Account from .poll import Poll from .timeline import Timeline -from .utils import parse_content_links, show_media +from .utils import parse_content_links, show_media, copy_to_clipboard logger = logging.getLogger(__name__) @@ -111,6 +111,7 @@ class TUI(urwid.Frame): self.overlay = None self.exception = None self.can_translate = False + self.screen = urwid.raw_display.Screen() super().__init__(self.body, header=self.header, footer=self.footer) @@ -210,6 +211,9 @@ class TUI(urwid.Frame): def _clear(*args): self.clear_screen() + def _copy(timeline, status): + self.copy_status(status) + urwid.connect_signal(timeline, "account", _account) urwid.connect_signal(timeline, "bookmark", self.async_toggle_bookmark) urwid.connect_signal(timeline, "compose", _compose) @@ -226,6 +230,7 @@ class TUI(urwid.Frame): urwid.connect_signal(timeline, "zoom", _zoom) urwid.connect_signal(timeline, "translate", self.async_translate) urwid.connect_signal(timeline, "clear-screen", _clear) + urwid.connect_signal(timeline, "copy-status", _copy) def build_timeline(self, name, statuses, local): def _close(*args): @@ -657,6 +662,12 @@ class TUI(urwid.Frame): return self.run_in_thread(_delete, done_callback=_done) + def copy_status(self, status): + # TODO: copy a better version of status content + # including URLs + copy_to_clipboard(self.screen, status.original.data["content"]) + self.footer.set_message(f"Status {status.original.id} copied") + # --- Overlay handling ----------------------------------------------------- default_overlay_options = dict( diff --git a/toot/tui/overlays.py b/toot/tui/overlays.py index 51601da..67b3c1e 100644 --- a/toot/tui/overlays.py +++ b/toot/tui/overlays.py @@ -206,6 +206,7 @@ class Help(urwid.Padding): yield urwid.Text(h(" [L] - Show the status links")) yield urwid.Text(h(" [U] - Show the status data in JSON as received from the server")) yield urwid.Text(h(" [V] - Open status in default browser")) + yield urwid.Text(h(" [Y] - Copy status to clipboard")) yield urwid.Text(h(" [Z] - Open status in scrollable popup window")) yield urwid.Divider() yield urwid.Text(("bold", "Links")) diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 695662d..03450e7 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -41,6 +41,7 @@ class Timeline(urwid.Columns): "save", # Save current timeline "zoom", # Open status in scrollable popup window "clear-screen", # Clear the screen (used internally) + "copy-status", # Copy status to clipboard ] def __init__(self, name, statuses, can_translate, followed_tags=[], focus=0, is_thread=False): @@ -120,6 +121,7 @@ class Timeline(urwid.Columns): "So[u]rce", "[Z]oom", "Tra[n]slate" if self.can_translate else "", + "Cop[y]", "[H]elp", ] options = "\n" + " ".join(o for o in options if o) @@ -261,6 +263,10 @@ class Timeline(urwid.Columns): self._emit("poll", status) return + if key in ("y", "Y"): + self._emit("copy-status", status) + return + return super().keypress(size, key) def append_status(self, status): diff --git a/toot/tui/utils.py b/toot/tui/utils.py index cfc0778..2f49362 100644 --- a/toot/tui/utils.py +++ b/toot/tui/utils.py @@ -1,3 +1,5 @@ +import base64 +import urwid from html.parser import HTMLParser import math import os @@ -144,3 +146,20 @@ def parse_content_links(content): parser = LinkParser() parser.feed(content) return parser.links[:] + + +def copy_to_clipboard(screen: urwid.raw_display.Screen, text: str): + """ copy text to clipboard using OSC 52 + This escape sequence is documented + here https://iterm2.com/documentation-escape-codes.html + It has wide support - XTerm, Windows Terminal, + Kitty, iTerm2, others. Some terminals may require a setting to be + enabled in order to use OSC 52 clipboard functions. + """ + + text_bytes = text.encode("utf-8") + b64_bytes = base64.b64encode(text_bytes) + b64_text = b64_bytes.decode("utf-8") + + screen.write(f"\033]52;c;{b64_text}\a") + screen.flush()