138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
# very simple daemon that monitors the clipboard for wayland (using wl-clipboard) and X (using xclip)
|
|
# whenever one changes, clipboard-daemon ensures the other changes as well
|
|
#
|
|
# currently supports syncing text/plain and image/png mimetypes between clipboards
|
|
#
|
|
# needs wl-paste and xclip in $PATH
|
|
|
|
from typing import Optional
|
|
import subprocess
|
|
from abc import ABC
|
|
from dataclasses import dataclass
|
|
import time
|
|
|
|
INTERVAL_MS = 100
|
|
|
|
class ClipboardContents(ABC):
|
|
pass
|
|
|
|
@dataclass
|
|
class TextContents(ClipboardContents):
|
|
text: bytes
|
|
|
|
def __repr__(self):
|
|
if len(self.text) < 20:
|
|
return f"TextContents(text={self.text!r})"
|
|
else:
|
|
return f"TextContents(text={self.text[:20]!r}..)"
|
|
|
|
def __str__(self):
|
|
return repr(self)
|
|
|
|
@dataclass
|
|
class ImageContents(ClipboardContents):
|
|
png_data: bytes
|
|
|
|
def __repr__(self):
|
|
return "ImageContents(png_data=...)"
|
|
|
|
def __str__(self):
|
|
return repr(self)
|
|
|
|
|
|
def get_wayland_clipboard() -> Optional[ClipboardContents]:
|
|
formats = subprocess.run(["wl-paste", "-l"], capture_output=True).stdout.strip(b"\n")
|
|
|
|
if b"image/png" in formats:
|
|
contents = subprocess.run(["wl-paste", "-t", "image/png"], capture_output=True).stdout
|
|
return ImageContents(png_data=contents)
|
|
|
|
if b"text/plain" in formats:
|
|
contents = subprocess.run(["wl-paste", "-n", "-t", "text/plain"], capture_output=True).stdout
|
|
return TextContents(text=contents)
|
|
|
|
return None
|
|
|
|
def set_wayland_clipboard(data: ClipboardContents):
|
|
if isinstance(data, TextContents):
|
|
subprocess.run(
|
|
["wl-copy", "-t", "text/plain", "-n"],
|
|
input=data.text,
|
|
)
|
|
elif isinstance(data, ImageContents):
|
|
subprocess.run(
|
|
["wl-copy", "-t", "image/png"],
|
|
input=data.png_data,
|
|
)
|
|
else:
|
|
raise ValueError(f"Unknown clipboard data: {data}")
|
|
|
|
def get_x_clipboard() -> Optional[ClipboardContents]:
|
|
formats = subprocess.run(
|
|
["xclip", "-out", "-selection", "clipboard", "-target", "TARGETS"],
|
|
capture_output=True,
|
|
).stdout.strip(b"\n")
|
|
|
|
if b"image/png" in formats:
|
|
contents = subprocess.run(
|
|
["xclip", "-out", "-selection", "clipboard", "-target", "image/png"],
|
|
capture_output=True,
|
|
).stdout
|
|
return ImageContents(png_data=contents)
|
|
|
|
if b"text/plain" in formats:
|
|
contents = subprocess.run(
|
|
["xclip", "-out", "-selection", "clipboard", "-target", "text/plain"],
|
|
capture_output=True,
|
|
).stdout
|
|
return TextContents(text=contents)
|
|
|
|
return None
|
|
|
|
def set_x_clipboard(data: ClipboardContents):
|
|
if isinstance(data, TextContents):
|
|
subprocess.run(
|
|
["xclip", "-in", "-selection", "clipboard", "-target", "text/plain"],
|
|
input=data.text,
|
|
)
|
|
elif isinstance(data, ImageContents):
|
|
subprocess.run(
|
|
["xclip", "-in", "-selection", "clipboard", "-target", "image/png"],
|
|
input=data.png_data,
|
|
)
|
|
else:
|
|
raise ValueError(f"Unknown clipboard data: {data}")
|
|
|
|
def clipboard_sync():
|
|
last_wayland_contents = get_wayland_clipboard()
|
|
last_x_contents = get_x_clipboard()
|
|
|
|
while True:
|
|
time.sleep(INTERVAL_MS/1000)
|
|
|
|
wayland_contents = get_wayland_clipboard()
|
|
x_contents = get_x_clipboard()
|
|
|
|
if wayland_contents != last_wayland_contents:
|
|
print(f"wayland clipborad updated from {last_wayland_contents} to {wayland_contents}")
|
|
if wayland_contents is not None:
|
|
print(" sycning wayland -> x")
|
|
set_x_clipboard(wayland_contents)
|
|
x_contents = wayland_contents
|
|
else:
|
|
print(" unknown type, not syncing")
|
|
elif x_contents != last_x_contents:
|
|
print(f"x clipborad updated from {last_x_contents} to {x_contents}")
|
|
if x_contents is not None:
|
|
print(" sycning x -> wayland")
|
|
set_wayland_clipboard(x_contents)
|
|
wayland_contents_contents = x_contents
|
|
else:
|
|
print(" unknown type, not syncing")
|
|
|
|
last_wayland_contents = wayland_contents
|
|
last_x_contents = x_contents
|
|
|
|
print("starting x-wayland-clipboard-daemon")
|
|
clipboard_sync()
|