pythonqtpyqtclipboardx11

Setting a persistent clipboard with multiple targets on X11


General overview

I am trying to set an image in clipboard into different linux’s targets. As example, depending of the context, the same image could be paste in png, jpg, or whatever else.

The problem

The big problem I am facing is I didn’t find a way to have in the same time i) many targets, and ii) a persistent modes.

With the simple solutions as Xclip, I can set one target at the time and last one overwrite the previous. However, with Qt’s modules, I can get the desired behavior, but this behavior ended when I close the Qt application and goes back to the situation before Qt.

My tries

1. Xclip

xclip -selection clipboard -t image/png -i image.png
xclip -selection clipboard -t image/jpg -i image.jpg

When I inspect the clipboard situation, I just find the last one:

xclip -selection clipboard -t TARGETS -o
TARGETS
image/jpg

Well, basically, Xclip doesn’t support it.

2. Using Qt

import base64
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QImage, QClipboard
from PyQt5.QtCore import QMimeData, QByteArray, QBuffer, QIODevice
import sys

# Example: load an image in base64 (from a real file here for testing)
with open("image.png", "rb") as f:
    base64_data = base64.b64encode(f.read()).decode()

# Decode base64 → bytes
image_bytes = base64.b64decode(base64_data)

# Load image into a QImage
qimage = QImage.fromData(image_bytes)
if qimage.isNull():
    raise ValueError("Invalid image")

# Start Qt
app = QApplication([])

# Create a QMimeData
mime_data = QMimeData()

# Add native image
mime_data.setImageData(qimage)

# Add image/png format
png_bytes = QByteArray()
buffer = QBuffer(png_bytes)
buffer.open(QIODevice.WriteOnly)
qimage.save(buffer, "PNG")
buffer.close()
mime_data.setData("image/png", png_bytes)

# Add image/jpeg format
jpeg_bytes = QByteArray()
buffer = QBuffer(jpeg_bytes)
buffer.open(QIODevice.WriteOnly)
qimage.save(buffer, "JPEG")
buffer.close()
mime_data.setData("image/jpeg", jpeg_bytes)

# Copy to clipboard
QApplication.clipboard().setMimeData(mime_data)
# QApplication.clipboard().setMimeData(mime_data, QClipboard.Clipboard)

print("Image copied to clipboard with multiple formats.")

# Optional: keep app alive a bit so clipboard survives
app.processEvents()
input("Press Enter to quit...")

I verify with Xclip and it works:

% xclip -selection clipboard -t TARGETS -o
application/x-qt-image
image/png
image/jpeg
image/avif
image/bmp
image/bw
image/cur
image/eps
image/epsf
image/epsi
image/heic
image/heif
image/icns
image/ico
image/jpg
image/jxl
image/pbm
BITMAP
image/pcx
image/pgm
image/pic
image/ppm
PIXMAP
image/rgb
image/rgba
image/sgi
image/tga
image/tif
image/tiff
image/wbmp
image/webp
image/xbm
image/xpm
TARGETS
MULTIPLE
TIMESTAMP
SAVE_TARGETS

But it works until the app is runing. When I push Enter, the clipboard change.

Some observations

Lot of softwares do it. Look at what’s happens when you copy image or text from your browser, or when you copy files from your file browser. So it doesn’t seems to be something particularly exceptional.

The question

How to get the same behavior as the Qt solution but with a persistent mode, not depending of the application’s execution?


Solution

  • But it works until the app is runing. When I push Enter, the clipboard change.

    Yes, that's how the X11 clipboard works. It is not a central buffer where any program can store data – rather, the clipboard is claimed by the app which has something copied, and then all "paste" or "get clipboard" operations actually retrieve contents directly from the original app.1

    This means it is not possible for clipboard contents to be provided simultaneously by two separate instances of xclip. As soon as the 2nd xclip runs, the 1st one loses clipboard ownership, so it's all or nothing.

    Your Qt program is the correct approach, and it has to continue running for as long as necessary. There is no "persistent mode" in X. That's actually what xclip does, too – it doesn't exit, it hides in background (aka "daemonizes") and if you run e.g.:

    $ echo "Hello" | xclip -in -sel clipboard
    
    $ pgrep -alf xclip
    

    you will see that there is an xclip process running in background, serving the clipboard contents. (It exits as soon as it loses ownership.) I don't know whether there's a Qt-specific way to do this or whether you just have to fork() manually.

    Going through Qt documentation I found QClipboard::ownsClipboard() as a one-time check, and I think the QClipboard::dataChanged signal is emitted when you lose clipboard ownership and it's time to exit.

    Any persistence you might have seen in X11 desktop environments would be provided by:


    1 It also means that the app can convert "on demand" when specific targets are requested. Here you have Qt offering loads of image formats even though you only provided two – but they're not all stored in memory; instead, if some program requests image/pcx, Qt will convert on the fly.

    It means you don't need to manually provide MIME types at all – just do the .setImageData() and Qt should handle the rest.