Skip to content

feat: Add OSC 52 fallback backend for headless environments - #292

Open
iloveitaly wants to merge 2 commits into
asweigart:masterfrom
iloveitaly:feat-osc52-fallback
Open

iloveitaly wants to merge 2 commits into
asweigart:masterfrom
iloveitaly:feat-osc52-fallback

Conversation

@iloveitaly

@iloveitaly iloveitaly commented Mar 6, 2026

Copy link
Copy Markdown

This PR introduces a fallback clipboard backend using the OSC 52 terminal escape sequence. I'm using it in https://github.com/iloveitaly/ipython-copy

Currently, running pyperclip on a headless Linux server, over SSH, or inside a Docker container fails if GUI clipboard utilities (like xclip or wl-clipboard) aren't installed. This PR allows pyperclip.copy() to work automatically in these environments by communicating directly with the user's terminal emulator.

Details:

  • Automatic Fallback: If no GUI clipboard utilities are found and a TTY is detected, the library now automatically defaults to OSC 52.
  • Clean Output: Writes the escape sequence directly to /dev/tty (falling back to stderr) to prevent the payload from polluting standard output during script piping or redirection.
  • Multiplexer Support: Formats the sequence correctly when running inside tmux or screen.
  • Safety Constraints:
    • pyperclip.paste() is explicitly disabled to prevent indefinite hangs, as most terminal emulators block OSC 52 read requests by default due to security risks.
    • A hard payload limit of ~75 KB is enforced to prevent overwhelming or crashing the host terminal emulator.

Fixes #288
Fixes #161
Fixes #140


here's a script that helped me test this OSC stuff in various terminal environments

import os
import sys
import base64

def osc52_copy(text: str):
    """
    Attempts to copy text to the local system clipboard using OSC 52.
    Handles wrapping for terminal multiplexers like tmux and screen.
    """
    # 1. Base64 encode the payload (required by the OSC 52 protocol)
    b64_payload = base64.b64encode(text.encode('utf-8')).decode('ascii')
    
    # 2. Standard OSC 52 sequence: ESC ] 52 ; c ; <payload> BEL
    # \x1b is ESC, \x07 is BEL (the standard terminator)
    osc_seq = f"\x1b]52;c;{b64_payload}\x07"
    
    # 3. Handle Multiplexers (tmux/screen)
    # Multiplexers often 'swallow' escape codes unless they are wrapped 
    # in a special pass-through sequence.
    term = os.environ.get("TERM", "").lower()
    
    if "TMUX" in os.environ:
        # tmux wrap: ESC P tmux ; ESC <sequence> ESC \
        final_seq = f"\x1bPtmux;\x1b{osc_seq}\x1b\\"
        print("Detected: tmux (using pass-through wrapping)")
    elif term.startswith("screen"):
        # GNU Screen wrap: ESC P <sequence> ESC \
        final_seq = f"\x1bP{osc_seq}\x1b\\"
        print("Detected: GNU Screen (using pass-through wrapping)")
    else:
        # Standard terminal
        final_seq = osc_seq
        print(f"Detected: Standard Terminal (TERM={term})")

    # 4. Write directly to stdout and flush immediately
    sys.stdout.write(final_seq)
    sys.stdout.flush()

if __name__ == "__main__":
    test_string = "Hello from OSC 52! (Copied at " + os.popen('date').read().strip() + ")"
    
    print("-" * 50)
    print("OSC 52 Clipboard Test Script")
    print("-" * 50)
    print(f"Attempting to copy: '{test_string}'")
    
    osc52_copy(test_string)
    
    print("\n" + "=" * 50)
    print("FINISHED: The escape sequence has been sent to your terminal.")
    print("=" * 50)
    print("HOW TO VERIFY:")
    print("1. Switch to a DIFFERENT application (e.g., your browser, Notes, or a text editor).")
    print("2. Use your standard 'Paste' command (Cmd+V or Ctrl+V).")
    print("-" * 50)
    print("NOTE: If it doesn't work, your terminal emulator (e.g., iTerm2, Kitty, Windows Terminal)")
    print("might have 'Allow OSC 52' disabled in its settings.")
    print("-" * 50)

@iloveitaly iloveitaly changed the title feat osc52 fallback feat: Add OSC 52 fallback backend for headless environments Mar 6, 2026
@iloveitaly
iloveitaly marked this pull request as ready for review March 6, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: OSC 52 escape sequence support for remote/tmux clipboard Detect headless setups Cannot use it with Docker

1 participant