Source code for simvx.core.ui.clipboard

"""System clipboard access — GLFW-first with subprocess fallback.

The graphics backend sets ``set_backend(copy_fn, paste_fn)`` at startup.
Widgets call ``copy(text)`` and ``paste() -> str`` which route to the backend
or fall back to xclip/xsel/wl-copy subprocess calls.
"""


from __future__ import annotations

import logging
import subprocess
from collections.abc import Callable

log = logging.getLogger(__name__)

_copy_fn: Callable[[str], None] | None = None
_paste_fn: Callable[[], str] | None = None


[docs] def set_backend(copy_fn: Callable[[str], None], paste_fn: Callable[[], str]) -> None: """Set clipboard backend (called by graphics App at startup).""" global _copy_fn, _paste_fn _copy_fn = copy_fn _paste_fn = paste_fn
[docs] def copy(text: str) -> None: """Copy text to the system clipboard.""" if _copy_fn: try: _copy_fn(text) return except (OSError, RuntimeError, ValueError): pass _subprocess_copy(text)
[docs] def paste() -> str: """Return text from the system clipboard.""" if _paste_fn: try: return _paste_fn() except (OSError, RuntimeError, ValueError): pass return _subprocess_paste()
def _subprocess_copy(text: str) -> None: for cmd in (["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"], ["wl-copy"]): try: subprocess.run(cmd, input=text.encode(), check=True, timeout=2) return except (FileNotFoundError, subprocess.SubprocessError, OSError): continue def _subprocess_paste() -> str: for cmd in ( ["xclip", "-selection", "clipboard", "-o"], ["xsel", "--clipboard", "--output"], ["wl-paste", "--no-newline"], ): try: r = subprocess.run(cmd, capture_output=True, check=True, timeout=2) return r.stdout.decode() except (FileNotFoundError, subprocess.SubprocessError, OSError): continue return ""