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 ""