A small libwayland-direct tool that puts files on the Wayland clipboard with
the same MIME types Nautilus uses — including the application/vnd.portal.*
types required by GFile-aware paste consumers like Claude Code.
GNOME's built-in Screenshot UI (the one behind the Print key on stock GNOME,
or invocable directly via xdg-desktop-portal) puts only image/png on the
Wayland clipboard:
$ gdbus call --session \
--dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.Screenshot.Screenshot \
"" "{'interactive': <true>, 'modal': <true>}"
(objectpath '/org/freedesktop/portal/desktop/request/1_2119/t',)
$ wl-paste --list-types
image/png
Just image/png is enough for image editors, but it's not what apps like
Claude Code, Slack, the file-upload widgets on most websites in Chromium-
based browsers, or anything else built on GFile-style paste actually read.
Those apps expect the same MIME types a file manager produces — including
the portal types that reference the file by D-Bus handle rather than by
opaque image bytes.
$ shotclip ~/Pictures/Screenshots/whatever.png
$ wl-paste --list-types
image/png
application/vnd.portal.files
application/vnd.portal.filetransfer
text/plain
text/plain;charset=utf-8
x-special/gnome-copied-files
text/uri-list
That's the same set Nautilus produces when you right-click → Copy a file. So the resulting clipboard pastes correctly into apps that expect a file reference, not just raw image bytes.
On GNOME Wayland (tested on Ubuntu 26.04 / Mutter 50), every off-the-shelf clipboard tool falls short for the use case of "paste a screenshot into Claude Code":
wl-copy --type image/pngproduces onlyimage/png. Claude Code readsapplication/vnd.portal.filetransfer, so the paste does nothing.wl-copy --type text/uri-listproduces the right URI string but no portal registration, so portal-aware receivers can't open the file.- Flameshot's built-in
-cclipboard, copyq, PyGObjectGdk.ContentProvidersubclasses, PyQt'sQMimeData, even a hand-written GTK4 C program — all end up advertising onlyimage/pngor onlytext/plain. The compositor doesn't filter MIME types; the tooling silently drops them upstream.
The actual problem is twofold:
- The
application/vnd.portal.filetransferandapplication/vnd.portal.filesMIME types aren't raw data — they're keys to live D-Bus sessions registered withorg.freedesktop.portal.FileTransferandorg.freedesktop.portal.Documents. You have to call the portal yourself to mint them. wl_data_device.set_selectionon Wayland is only honored when accompanied by a serial from a recent input event. GTK4's clipboard layer, when called from a non-interactive code path, passes serial0and Mutter silently discards the request — even thoughwl_data_source.offerfor the non-text MIME types fired correctly.wl-copyworks because it talks to libwayland directly, opens a hidden surface, and uses thewl_keyboard.enterserial it receives once the surface gets focus.
shotclip does what wl-copy does for the serial trick, plus the portal
D-Bus dance:
- Bind the standard Wayland globals plus
xdg_wm_baseand (optionally)zwp_primary_selection_device_manager_v1. - Pre-call
org.freedesktop.portal.FileTransfer.StartTransfer+AddFilesandorg.freedesktop.portal.Documents.AddFullover D-Bus so the portal keys are ready before any paste request arrives. - Create a 1×1
xdg_toplevelsurface, wait forwl_keyboard.enter, capture the serial. - Build a
wl_data_source, advertise the full MIME set, callwl_data_device.set_selection(source, captured_serial). - Immediately destroy the surface.
wl_data_sourcelives on thewl_data_device(per-seat), not on the surface, so the clipboard ownership survives — and the focus window stops showing up in Alt-Tab. - Serve
wl_data_source.sendevents from the now-windowless process until the compositor cancels the source (something else took the clipboard) or--paste-onceexit fires.
Build dependencies on Ubuntu/Debian:
sudo apt install libwayland-dev wayland-protocols libglib2.0-dev pkg-config build-essentialThen build and install to ~/.local:
make
make install # -> ~/.local/bin/shotclip + ~/.local/bin/shotclip-flameshotOr system-wide:
sudo make install PREFIX=/usr/localYou also need an xdg-desktop-portal backend that implements FileTransfer
and Documents (on GNOME this is xdg-desktop-portal-gnome plus
xdg-desktop-portal-gtk).
shotclip [OPTIONS] FILE [FILE ...]
shotclip forks to the background after set_selection succeeds (matching
wl-copy's default), and stays alive serving paste requests until the
compositor cancels the selection. Pass multiple files to copy them as a set,
the way file managers do.
If FILE is omitted and standard input is not a TTY, paths are read from
stdin, one per line — handy for piping find/ls/fd output:
ls ~/Pictures/Screenshots/2026-05-19*.png | shotclip
find ~/Documents -name '*.pdf' -newer /tmp/marker | shotclipWith --stdin-bytes, raw binary data is read from stdin instead and saved
to a tempfile in $TMPDIR. The MIME type is detected from the bytes; the
tempfile gets a matching extension. The clipboard then references that
tempfile path. Useful for piping from a tool that writes its output to
stdout, e.g. Flameshot in raw mode:
flameshot gui -r | shotclip --stdin-bytesThe tempfile is intentionally left on disk so portal MIME types can still
be resolved after shotclip exits. If you'd rather have a stable, predictable
path, save to disk yourself first: flameshot gui -p ~/Pictures/ ; shotclip <newest>.
| Flag | Behavior |
|---|---|
-p, --primary |
Set the primary selection instead of the regular clipboard. |
-o, --paste-once |
Exit after the first paste of a data MIME type. Good for screenshot workflows where you only need one paste. Note: pasting into XWayland windows is known to break with this flag — same caveat as wl-copy(1). |
-f, --foreground |
Don't fork — stay attached to the terminal. |
-c, --clear |
Clear the clipboard (or primary selection with -p). Takes no FILE args. |
-t, --type MIME |
Only advertise this single MIME type. Useful for -t text/uri-list to force a URI-only clipboard. |
-s, --seat NAME |
Use the seat with this name (as advertised in wl_seat.name) instead of the first one. Only relevant on multi-seat systems. |
--stdin-bytes |
Read raw bytes from stdin into a tempfile (with an extension matched to the detected MIME) and use it as the single source. Mutually exclusive with FILE args. |
--no-image-data |
Don't offer the file's content-type bytes; URI/portal types only. |
--keep-visible |
Don't destroy the focus window (debugging). |
-h, --help |
Usage. |
-V, --version |
Version. |
For a single file (e.g. shotclip foo.png):
- the file's detected content type (
image/png,application/pdf, …) text/uri-listwith onefile://URIx-special/gnome-copied-files(Nautilus convention)text/plain;charset=utf-8+text/plain(the path)application/vnd.portal.filetransfer+application/vnd.portal.files(portal handles)
For multiple files (e.g. shotclip a.png b.png):
- same set, but no content-type bytes (Nautilus does the same — bytes-of-one only makes sense for single files)
text/uri-listis multiline,x-special/gnome-copied-filesincludes all URIs, portal keys reference all files
examples/shotclip-flameshot.sh (installed as ~/.local/bin/shotclip-flameshot)
captures a screenshot with Flameshot, saves it to ~/Pictures/Screenshots/,
and then runs shotclip --paste-once on the saved file:
- Bind
~/.local/bin/shotclip-flameshotto yourPrintkey in Settings → Keyboard → View and Customize Shortcuts → Custom Shortcuts. - Press Print, drag a region, annotate in Flameshot, save.
- The screenshot lands in
~/Pictures/Screenshots/and on your clipboard. - Paste into Claude Code with Ctrl+Shift+V. Image appears.
For Flameshot itself to work on GNOME Wayland, you currently need a build
that includes flameshot-org/flameshot#4664
(non-empty parent_window to xdg-desktop-portal). At the time of writing,
that fix is in master but not in any stable release.
examples/shotclip-gnome-screenshot.py (installed as
~/.local/bin/shotclip-gnome-screenshot) invokes the GNOME / xdg-desktop-portal
Screenshot interface directly — same UI as the built-in Print key on stock
GNOME — then runs shotclip on the saved file.
It's the equivalent of:
gdbus call --session \
--dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.Screenshot.Screenshot \
"" "{'interactive': <true>, 'modal': <true>}"followed by handing the resulting file to shotclip. The script does the
Response signal handshake properly via Gio.DBusConnection.signal_subscribe
(the gdbus one-liner just returns the request handle and the actual URI is
delivered asynchronously, so a thin bash wrapper around gdbus call won't
know where the screenshot landed).
Wire it to your Print key the same way as the Flameshot example:
- Bind
/home/USER/.local/bin/shotclip-gnome-screenshottoPrintin Settings → Keyboard → View and Customize Shortcuts → Custom Shortcuts. - Press Print, use GNOME's screenshot UI to select a region (or window or full screen), save it.
- The screenshot lands in
~/Pictures/Screenshots/(wherever GNOME's portal chooses) and on your clipboard with the full Nautilus MIME set. - Paste into Claude Code with Ctrl+Shift+V.
Requires python3-gi (preinstalled on most GNOME systems).
Since python-pillow/Pillow#7094
(merged 2023-05), Pillow's ImageGrab.grabclipboard() on Wayland calls
wl-paste -t image/png when image/png is among the offered MIME types. For
single-image shotclip copies, image/png is always offered alongside the
file's detected content type, so a Python script can pick up a shotclip'd
image directly:
shotclip ~/Pictures/Screenshots/test.pngfrom PIL import ImageGrab
im = ImageGrab.grabclipboard() # returns a PIL.Image of the screenshot
im.save("/tmp/from-clipboard.png")This gives you a shotclip foo.png → Python pipeline without temporary file
juggling or shelling out to wl-paste yourself.
- GNOME 50 / Mutter on Ubuntu 26.04 is the only environment this has been
tested on. Should work on any compositor that supports
xdg-shell,wl_data_device_manager, and survives the focus-window destroy trick. - The 1×1 focus window flashes briefly during
set_selection. It then destroys itself, so Alt-Tab is clean. - The portal
FileTransfersession uses defaultautostop=true, so the portal MIME types resolve only once. After that single paste, the other MIME types (image/png,text/uri-list, etc.) keep working as long asshotclipis running — but if you specifically need the portal types multiple times, run a secondshotclipinvocation.
bugaevc/wl-clipboard#71
— "wl-copy: how to craft a multi-mimetype copy" — open since 2019, still open
in 2025. Users have repeatedly asked for wl-copy to advertise more than one
MIME type, with use cases including pasting browser-grabbed images, restoring
rich text in Confluence/Office round-trips, GIMP clipboard history, and —
exactly what shotclip targets — "copy file paths as text/uri-list, and
paste them into file managers… I'd like to copy a version of absolute paths
as text/plain too. That's what thunar does." (@lilydjwg, 2021).
The wl-clipboard maintainer's position there is that generic CLI-driven
multi-MIME is too design-y for a small tool — every proposed flag syntax
(-t a,b, -C converter, --extra MIME <(…)) brings open questions, and
the real consumers are clipboard managers that should use libwayland
directly. Fair.
shotclip takes the narrower, opinionated path instead: it doesn't try
to solve "arbitrary MIME N-tuples." It solves "I have one or more files, give
me the clipboard a file manager would give me" — including the
application/vnd.portal.* keys that didn't even come up in that thread but
turn out to be the missing piece for pasting into modern GFile-aware apps
like Claude Code on GNOME Wayland. Where wl-copy is a cat-for-clipboard,
shotclip is a cp-to-clipboard.
Other relevant tooling:
wl-clipboard(wl-copy/wl-paste) — the upstream Wayland clipboard CLI. shotclip mimics its flag names and fork-to-background default where they carry over.xdg-desktop-portalorg.freedesktop.portal.FileTransferandorg.freedesktop.portal.Documents— the D-Bus services whose keys end up in our portal MIME payloads.- Nautilus (
nautilus-clipboard.c) — the reference implementation of the exact MIME set we mimic.
GPL-3.0-or-later. See LICENSE.