2025, Dec 12 19:00

Making X11 Clipboard Data Persist with Multiple Image Formats: xclip Limitations and a Qt-Based Solution

Learn how the X11 clipboard works, why xclip can't expose multiple targets, and how to keep image/png and image/jpeg persistent with Qt or a clipboard manager.

Copying an image to the X11 clipboard with multiple targets like image/png and image/jpeg sounds trivial until you try to make it persist beyond the lifetime of the program that set it. Tools such as xclip let you push data to the clipboard, but only one target at a time, and a later call overwrites the previous one. Frameworks like Qt can expose many formats at once, but as soon as the application exits, the clipboard falls back to whatever else is around. Here’s why that happens, what it means for your tooling, and how to implement the behavior you actually want.

Problem demonstration

First, trying the obvious route with xclip shows that the last write wins; earlier targets are lost.

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

Using Qt produces the desired multi-target behavior while the app is running. The example below loads an image, publishes it both as a native image and as explicit image/png and image/jpeg targets, and then exits when the user presses Enter. Variable names are different from any other snippet you might have seen, but the logic is identical.

import base64
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QImage, QClipboard
from PyQt5.QtCore import QMimeData, QByteArray, QBuffer, QIODevice
# Load a file and stage it as base64 text, then back to bytes
with open("image.png", "rb") as fh:
    b64_blob = base64.b64encode(fh.read()).decode()
img_raw = base64.b64decode(b64_blob)
# Build a QImage from the byte stream
pic = QImage.fromData(img_raw)
if pic.isNull():
    raise ValueError("Invalid image")
# Start Qt stack
gui = QApplication([])
# Prepare clipboard payload
payload = QMimeData()
# Expose the native image
payload.setImageData(pic)
# Also expose image/png explicitly
bin_png = QByteArray()
io_slot = QBuffer(bin_png)
io_slot.open(QIODevice.WriteOnly)
pic.save(io_slot, "PNG")
io_slot.close()
payload.setData("image/png", bin_png)
# And expose image/jpeg explicitly
bin_jpg = QByteArray()
io_slot = QBuffer(bin_jpg)
io_slot.open(QIODevice.WriteOnly)
pic.save(io_slot, "JPEG")
io_slot.close()
payload.setData("image/jpeg", bin_jpg)
# Publish to clipboard
QApplication.clipboard().setMimeData(payload)
print("Image placed on clipboard in multiple formats.")
# Keep the app alive temporarily so the clipboard remains available
gui.processEvents()
input("Press Enter to quit...")

Inspecting the clipboard targets at this point shows a long list of image formats exposed by Qt. But when the application quits, the clipboard changes.

What’s actually happening and why

On X11, the clipboard is not a central buffer. The application that performs “copy” claims ownership of the selection. When another application requests a paste or fetches a specific target, it asks the current owner directly. As a consequence, two separate providers cannot simultaneously supply different targets; the second one to claim the clipboard becomes the sole owner and the previous one loses ownership. That’s why the last xclip command wins and everything before it disappears.

Qt behaves differently because it exposes many targets at once through a single owner. While your Qt app is running, it can advertise a wide range of formats and convert on demand when a consumer requests a specific MIME type. When the app exits, it stops serving the selection and the clipboard falls back to whoever claims it next. There is no “persistent mode” in X11 that survives the producer exiting.

The apparent persistence people observe in desktop environments usually comes from a clipboard manager that listens for changes, pulls the data from the current owner, and then takes over ownership to keep serving the same content after the original program exits. The other common case is simply that the source application never closed in the first place. Even xclip demonstrates this behavior by staying in the background to keep ownership until it loses it:

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

Solution: keep the provider alive or let a manager take over

If you want multi-target image data to remain available, the provider must remain alive as the selection owner, or a clipboard manager must proactively fetch and re-serve the payload. For a Qt program, you can keep running and exit only after your ownership is gone. It’s also unnecessary to set individual MIME types manually; setting the image is enough, as Qt can convert to many formats on demand.

The example below sets a QImage on the clipboard and stays alive. It connects to the dataChanged signal and checks ownsClipboard; once ownership is lost, the application exits. The program focuses on being a clipboard provider and lets Qt expose many image targets without explicitly populating each MIME type.

from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QImage
from PyQt5.QtCore import QTimer
# Load an image and publish as the native image only
with open("image.png", "rb") as f:
    raw_bytes = f.read()
img_obj = QImage.fromData(raw_bytes)
if img_obj.isNull():
    raise ValueError("Invalid image")
app_ctx = QApplication([])
cb = app_ctx.clipboard()
cb.setImage(img_obj)
# Quit when we no longer own the clipboard
def stop_when_lost():
    if not cb.ownsClipboard():
        app_ctx.quit()
cb.dataChanged.connect(stop_when_lost)
# Start the event loop and serve the clipboard until ownership changes
QTimer.singleShot(0, lambda: None)
app_ctx.exec_()

Why this matters

Building tools around the X11 clipboard requires aligning with its ownership model. Assuming a central buffer leads to fragile behaviors: multiple xclip calls clobber each other, and a helper that exits promptly cannot keep content available. Understanding that the provider must stay alive clarifies why browsers and file managers appear to offer persistent rich clipboard data, and why a minimal tool must either keep running or rely on a clipboard manager that mirrors the data.

Takeaways

Treat the X11 clipboard as a live contract between one producer and many consumers. Use a single program to advertise all formats at once instead of chaining multiple writers. If you are using Qt, setting the native image is enough; Qt will offer a broad set of MIME targets and convert on demand. Keep the process alive and close it when you lose ownership, or hand off persistence to a clipboard manager if your environment already runs one.