fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
"""Clipboard image extraction for macOS, Linux, and WSL2.
|
2026-03-05 17:53:58 -08:00
|
|
|
|
|
|
|
|
Provides a single function `save_clipboard_image(dest)` that checks the
|
|
|
|
|
system clipboard for image data, saves it to *dest* as PNG, and returns
|
|
|
|
|
True on success. No external Python dependencies — uses only OS-level
|
|
|
|
|
CLI tools that ship with the platform (or are commonly installed).
|
|
|
|
|
|
|
|
|
|
Platform support:
|
|
|
|
|
macOS — osascript (always available), pngpaste (if installed)
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
WSL2 — powershell.exe via .NET System.Windows.Forms.Clipboard
|
|
|
|
|
Linux — wl-paste (Wayland), xclip (X11)
|
2026-03-05 17:53:58 -08:00
|
|
|
"""
|
|
|
|
|
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
import base64
|
2026-03-05 17:53:58 -08:00
|
|
|
import logging
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
import os
|
2026-03-05 17:53:58 -08:00
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
# Cache WSL detection (checked once per process)
|
|
|
|
|
_wsl_detected: bool | None = None
|
|
|
|
|
|
2026-03-05 17:53:58 -08:00
|
|
|
|
|
|
|
|
def save_clipboard_image(dest: Path) -> bool:
|
|
|
|
|
"""Extract an image from the system clipboard and save it as PNG.
|
|
|
|
|
|
|
|
|
|
Returns True if an image was found and saved, False otherwise.
|
|
|
|
|
"""
|
|
|
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
if sys.platform == "darwin":
|
|
|
|
|
return _macos_save(dest)
|
|
|
|
|
return _linux_save(dest)
|
|
|
|
|
|
|
|
|
|
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
def has_clipboard_image() -> bool:
|
|
|
|
|
"""Quick check: does the clipboard currently contain an image?
|
|
|
|
|
|
|
|
|
|
Lighter than save_clipboard_image — doesn't extract or write anything.
|
|
|
|
|
"""
|
|
|
|
|
if sys.platform == "darwin":
|
|
|
|
|
return _macos_has_image()
|
|
|
|
|
if _is_wsl():
|
|
|
|
|
return _wsl_has_image()
|
|
|
|
|
if os.environ.get("WAYLAND_DISPLAY"):
|
|
|
|
|
return _wayland_has_image()
|
|
|
|
|
return _xclip_has_image()
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 17:53:58 -08:00
|
|
|
# ── macOS ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _macos_save(dest: Path) -> bool:
|
|
|
|
|
"""Try pngpaste first (fast, handles more formats), fall back to osascript."""
|
|
|
|
|
return _macos_pngpaste(dest) or _macos_osascript(dest)
|
|
|
|
|
|
|
|
|
|
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
def _macos_has_image() -> bool:
|
|
|
|
|
"""Check if macOS clipboard contains image data."""
|
|
|
|
|
try:
|
|
|
|
|
info = subprocess.run(
|
|
|
|
|
["osascript", "-e", "clipboard info"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
return "«class PNGf»" in info.stdout or "«class TIFF»" in info.stdout
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 17:53:58 -08:00
|
|
|
def _macos_pngpaste(dest: Path) -> bool:
|
|
|
|
|
"""Use pngpaste (brew install pngpaste) — fastest, cleanest."""
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["pngpaste", str(dest)],
|
|
|
|
|
capture_output=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
if r.returncode == 0 and dest.exists() and dest.stat().st_size > 0:
|
|
|
|
|
return True
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass # pngpaste not installed
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("pngpaste failed: %s", e)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _macos_osascript(dest: Path) -> bool:
|
|
|
|
|
"""Use osascript to extract PNG data from clipboard (always available)."""
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
if not _macos_has_image():
|
2026-03-05 17:53:58 -08:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Extract as PNG
|
|
|
|
|
script = (
|
|
|
|
|
'try\n'
|
|
|
|
|
' set imgData to the clipboard as «class PNGf»\n'
|
|
|
|
|
f' set f to open for access POSIX file "{dest}" with write permission\n'
|
|
|
|
|
' write imgData to f\n'
|
|
|
|
|
' close access f\n'
|
|
|
|
|
'on error\n'
|
|
|
|
|
' return "fail"\n'
|
|
|
|
|
'end try\n'
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["osascript", "-e", script],
|
|
|
|
|
capture_output=True, text=True, timeout=5,
|
|
|
|
|
)
|
|
|
|
|
if r.returncode == 0 and "fail" not in r.stdout and dest.exists() and dest.stat().st_size > 0:
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("osascript clipboard extract failed: %s", e)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Linux ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
def _is_wsl() -> bool:
|
|
|
|
|
"""Detect if running inside WSL (1 or 2)."""
|
|
|
|
|
global _wsl_detected
|
|
|
|
|
if _wsl_detected is not None:
|
|
|
|
|
return _wsl_detected
|
|
|
|
|
try:
|
|
|
|
|
with open("/proc/version", "r") as f:
|
|
|
|
|
_wsl_detected = "microsoft" in f.read().lower()
|
|
|
|
|
except Exception:
|
|
|
|
|
_wsl_detected = False
|
|
|
|
|
return _wsl_detected
|
|
|
|
|
|
|
|
|
|
|
2026-03-05 17:53:58 -08:00
|
|
|
def _linux_save(dest: Path) -> bool:
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
"""Try clipboard backends in priority order: WSL → Wayland → X11."""
|
|
|
|
|
if _is_wsl():
|
|
|
|
|
if _wsl_save(dest):
|
|
|
|
|
return True
|
|
|
|
|
# Fall through — WSLg might have wl-paste or xclip working
|
|
|
|
|
|
|
|
|
|
if os.environ.get("WAYLAND_DISPLAY"):
|
|
|
|
|
if _wayland_save(dest):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return _xclip_save(dest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── WSL2 (powershell.exe) ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
# PowerShell script: get clipboard image as base64-encoded PNG on stdout.
|
|
|
|
|
# Using .NET System.Windows.Forms.Clipboard — always available on Windows.
|
|
|
|
|
_PS_CHECK_IMAGE = (
|
|
|
|
|
"Add-Type -AssemblyName System.Windows.Forms;"
|
|
|
|
|
"[System.Windows.Forms.Clipboard]::ContainsImage()"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_PS_EXTRACT_IMAGE = (
|
|
|
|
|
"Add-Type -AssemblyName System.Windows.Forms;"
|
|
|
|
|
"Add-Type -AssemblyName System.Drawing;"
|
|
|
|
|
"$img = [System.Windows.Forms.Clipboard]::GetImage();"
|
|
|
|
|
"if ($null -eq $img) { exit 1 }"
|
|
|
|
|
"$ms = New-Object System.IO.MemoryStream;"
|
|
|
|
|
"$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png);"
|
|
|
|
|
"[System.Convert]::ToBase64String($ms.ToArray())"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wsl_has_image() -> bool:
|
|
|
|
|
"""Check if Windows clipboard has an image (via powershell.exe)."""
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command",
|
|
|
|
|
_PS_CHECK_IMAGE],
|
|
|
|
|
capture_output=True, text=True, timeout=8,
|
|
|
|
|
)
|
|
|
|
|
return r.returncode == 0 and "True" in r.stdout
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
logger.debug("powershell.exe not found — WSL clipboard unavailable")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("WSL clipboard check failed: %s", e)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wsl_save(dest: Path) -> bool:
|
|
|
|
|
"""Extract clipboard image via powershell.exe → base64 → decode to PNG."""
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command",
|
|
|
|
|
_PS_EXTRACT_IMAGE],
|
|
|
|
|
capture_output=True, text=True, timeout=15,
|
|
|
|
|
)
|
|
|
|
|
if r.returncode != 0:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
b64_data = r.stdout.strip()
|
|
|
|
|
if not b64_data:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
png_bytes = base64.b64decode(b64_data)
|
|
|
|
|
dest.write_bytes(png_bytes)
|
|
|
|
|
return dest.exists() and dest.stat().st_size > 0
|
|
|
|
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
logger.debug("powershell.exe not found — WSL clipboard unavailable")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("WSL clipboard extraction failed: %s", e)
|
|
|
|
|
dest.unlink(missing_ok=True)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Wayland (wl-paste) ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _wayland_has_image() -> bool:
|
|
|
|
|
"""Check if Wayland clipboard has image content."""
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["wl-paste", "--list-types"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
return r.returncode == 0 and any(
|
|
|
|
|
t.startswith("image/") for t in r.stdout.splitlines()
|
|
|
|
|
)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
logger.debug("wl-paste not installed — Wayland clipboard unavailable")
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wayland_save(dest: Path) -> bool:
|
|
|
|
|
"""Use wl-paste to extract clipboard image (Wayland sessions)."""
|
|
|
|
|
try:
|
|
|
|
|
# Check available MIME types
|
|
|
|
|
types_r = subprocess.run(
|
|
|
|
|
["wl-paste", "--list-types"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
if types_r.returncode != 0:
|
|
|
|
|
return False
|
|
|
|
|
types = types_r.stdout.splitlines()
|
|
|
|
|
|
|
|
|
|
# Prefer PNG, fall back to other image formats
|
|
|
|
|
mime = None
|
|
|
|
|
for preferred in ("image/png", "image/jpeg", "image/bmp",
|
|
|
|
|
"image/gif", "image/webp"):
|
|
|
|
|
if preferred in types:
|
|
|
|
|
mime = preferred
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if not mime:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Extract the image data
|
|
|
|
|
with open(dest, "wb") as f:
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["wl-paste", "--type", mime],
|
|
|
|
|
stdout=f, stderr=subprocess.DEVNULL, timeout=5, check=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not dest.exists() or dest.stat().st_size == 0:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# BMP needs conversion to PNG (common in WSLg where only BMP
|
|
|
|
|
# is bridged from Windows clipboard via RDP).
|
|
|
|
|
if mime == "image/bmp":
|
|
|
|
|
return _convert_to_png(dest)
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
logger.debug("wl-paste not installed — Wayland clipboard unavailable")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("wl-paste clipboard extraction failed: %s", e)
|
|
|
|
|
dest.unlink(missing_ok=True)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _convert_to_png(path: Path) -> bool:
|
|
|
|
|
"""Convert an image file to PNG in-place (requires Pillow or ImageMagick)."""
|
|
|
|
|
# Try Pillow first (likely installed in the venv)
|
|
|
|
|
try:
|
|
|
|
|
from PIL import Image
|
|
|
|
|
img = Image.open(path)
|
|
|
|
|
img.save(path, "PNG")
|
|
|
|
|
return True
|
|
|
|
|
except ImportError:
|
|
|
|
|
pass
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("Pillow BMP→PNG conversion failed: %s", e)
|
|
|
|
|
|
|
|
|
|
# Fall back to ImageMagick convert
|
2026-03-07 19:27:23 +03:00
|
|
|
tmp = path.with_suffix(".bmp")
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
try:
|
|
|
|
|
path.rename(tmp)
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["convert", str(tmp), "png:" + str(path)],
|
|
|
|
|
capture_output=True, timeout=5,
|
|
|
|
|
)
|
|
|
|
|
if r.returncode == 0 and path.exists() and path.stat().st_size > 0:
|
2026-03-07 19:27:23 +03:00
|
|
|
tmp.unlink(missing_ok=True)
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
return True
|
2026-03-07 19:27:23 +03:00
|
|
|
else:
|
|
|
|
|
# Convert failed — restore the original file
|
|
|
|
|
tmp.rename(path)
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
except FileNotFoundError:
|
|
|
|
|
logger.debug("ImageMagick not installed — cannot convert BMP to PNG")
|
2026-03-08 17:22:27 -07:00
|
|
|
if tmp.exists() and not path.exists():
|
2026-03-07 19:27:23 +03:00
|
|
|
tmp.rename(path)
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("ImageMagick BMP→PNG conversion failed: %s", e)
|
2026-03-08 17:22:27 -07:00
|
|
|
if tmp.exists() and not path.exists():
|
2026-03-07 19:27:23 +03:00
|
|
|
tmp.rename(path)
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
|
|
|
|
|
# Can't convert — BMP is still usable as-is for most APIs
|
|
|
|
|
return path.exists() and path.stat().st_size > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── X11 (xclip) ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
def _xclip_has_image() -> bool:
|
|
|
|
|
"""Check if X11 clipboard has image content."""
|
|
|
|
|
try:
|
|
|
|
|
r = subprocess.run(
|
|
|
|
|
["xclip", "-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
return r.returncode == 0 and "image/png" in r.stdout
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _xclip_save(dest: Path) -> bool:
|
|
|
|
|
"""Use xclip to extract clipboard image (X11 sessions)."""
|
2026-03-05 17:53:58 -08:00
|
|
|
# Check if clipboard has image content
|
|
|
|
|
try:
|
|
|
|
|
targets = subprocess.run(
|
|
|
|
|
["xclip", "-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
|
|
|
capture_output=True, text=True, timeout=3,
|
|
|
|
|
)
|
|
|
|
|
if "image/png" not in targets.stdout:
|
|
|
|
|
return False
|
|
|
|
|
except FileNotFoundError:
|
fix: clipboard image paste on WSL2, Wayland, and VSCode terminal
The original implementation only supported xclip (X11), which silently
fails on WSL2 (can't access Windows clipboard for images), Wayland
desktops (xclip is X11-only), and VSCode terminal on WSL2.
Clipboard backend changes (hermes_cli/clipboard.py):
- WSL2: detect via /proc/version, use powershell.exe with .NET
System.Windows.Forms.Clipboard to extract images as base64 PNG
- Wayland: use wl-paste with MIME type detection, auto-convert BMP
to PNG for WSLg environments (via Pillow or ImageMagick)
- Dispatch order: WSL → Wayland → X11 (xclip), with fallthrough
- New has_clipboard_image() for lightweight clipboard checks
- Cache WSL detection result per-process
CLI changes (cli.py):
- /paste command: explicit clipboard image check for terminals where
BracketedPaste doesn't fire (image-only clipboard in VSCode/WinTerm)
- Ctrl+V keybinding: fallback for Linux terminals where Ctrl+V sends
raw byte instead of triggering bracketed paste
Tests: 80 tests (up from 37) covering WSL, Wayland, X11 dispatch,
BMP conversion, has_clipboard_image, and /paste command.
2026-03-05 20:22:44 -08:00
|
|
|
logger.debug("xclip not installed — X11 clipboard image paste unavailable")
|
2026-03-05 17:53:58 -08:00
|
|
|
return False
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Extract PNG data
|
|
|
|
|
try:
|
|
|
|
|
with open(dest, "wb") as f:
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["xclip", "-selection", "clipboard", "-t", "image/png", "-o"],
|
|
|
|
|
stdout=f, stderr=subprocess.DEVNULL, timeout=5, check=True,
|
|
|
|
|
)
|
|
|
|
|
if dest.exists() and dest.stat().st_size > 0:
|
|
|
|
|
return True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug("xclip image extraction failed: %s", e)
|
|
|
|
|
dest.unlink(missing_ok=True)
|
|
|
|
|
return False
|