Suddenly got error: "Failed to read dropped files: Could not convert data from application/vnd.portal.filetransfer to GdkFileList"

Problem

I recently included drag and drop in my GTK4/Libadwaita/GNOME 50 application and it worked. Now, I suddenly get the following error:

Failed to read dropped files: Could not convert data from application/vnd.portal.filetransfer to GdkFileList

My file manager is Nautilus (GNOME 50) version as well and, as far as I know, a native package, i.e., installed via apt. I also run the Python application natively, i.e., I have no flatpak packaging yet.

I’m really not sure how this error came to be, and it seems it has nothing to do with my code because it exists when I read the files from the Gdk.Drop.

One notable difference to my previous tests was that I now opened Nautilus from PyCharm via “Open in…”. The Nautilus window then showed the icon of PyCharm in the app overview as well. Only after closing Nautilus and waiting a minute or so, I could make it work again (when Nautilus showed the usual Nautilus icon again).

Question

Now, I’m worried that the drag and drop mechanism I implemented is not universal and may break on some systems. How can I improve it? Are there reference GNOME applications for how to implement drag and drop well?

Thanks for any help regarding this question.

Example

A minimal working example of my implementation:

#!/usr/bin/env python3

import gi

gi.require_version("Gtk", "4.0")

from gi.repository import Gdk, Gio, GLib, GObject, Gtk


class DropArea(Gtk.Overlay):
    __gtype_name__ = "DropArea"

    __gsignals__ = {
        "files-dropped": (
            GObject.SignalFlags.RUN_LAST,
            None,
            (Gdk.FileList,),
        ),
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.set_hexpand(True)
        self.set_vexpand(True)

        placeholder = Gtk.Label(
            label="Drop files here",
            hexpand=True,
            vexpand=True,
        )
        placeholder.set_size_request(400, 250)
        self.set_child(placeholder)

        drop_label = Gtk.Label(label="Release to drop files")
        self._drop_revealer = Gtk.Revealer(
            child=drop_label,
            transition_type=Gtk.RevealerTransitionType.CROSSFADE,
            hexpand=True,
            vexpand=True,
        )
        self._drop_revealer.set_halign(Gtk.Align.FILL)
        self._drop_revealer.set_valign(Gtk.Align.FILL)
        self.add_overlay(self._drop_revealer)

        formats = Gdk.ContentFormats.new_for_gtype(Gdk.FileList)
        target = Gtk.DropTargetAsync.new(
            formats=formats,
            actions=Gdk.DragAction.COPY,
        )
        target.connect("drag-enter", self._on_drop_enter)
        target.connect("drag-leave", self._on_drop_leave)
        target.connect("drop", self._on_drop)
        self.add_controller(target)

    def _on_drop_enter(
        self,
        _target: Gtk.DropTargetAsync,
        drop: Gdk.Drop,
        _x: float,
        _y: float,
    ) -> Gdk.DragAction:
        print("Offered formats:", drop.get_formats().to_string())
        self._drop_revealer.set_reveal_child(True)
        return drop.get_actions() & Gdk.DragAction.COPY

    def _on_drop_leave(
        self,
        _target: Gtk.DropTargetAsync,
        _drop: Gdk.Drop,
    ) -> None:
        self._drop_revealer.set_reveal_child(False)

    def _on_drop(
        self,
        _target: Gtk.DropTargetAsync,
        drop: Gdk.Drop,
        _x: float,
        _y: float,
    ) -> bool:
        drop.read_value_async(
            type=Gdk.FileList,
            io_priority=GLib.PRIORITY_DEFAULT,
            cancellable=None,
            callback=self._on_drop_data_ready,
        )
        return True

    def _on_drop_data_ready(
        self,
        drop: Gdk.Drop,
        result: Gio.AsyncResult,
    ) -> None:
        try:
            files = drop.read_value_finish(result)
        except GLib.Error as error:
            print(f"Failed to read dropped files: {error.message}")
            drop.finish(Gdk.DragAction(0))
        else:
            self.emit("files-dropped", files)
            drop.finish(Gdk.DragAction.COPY)
        finally:
            self._drop_revealer.set_reveal_child(False)


class Application(Gtk.Application):
    def __init__(self):
        super().__init__(
            application_id="org.example.DragAndDrop",
            flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
        )

    def do_activate(self) -> None:
        drop_area = DropArea()
        drop_area.connect("files-dropped", self._on_files_dropped)

        window = Gtk.ApplicationWindow(
            application=self,
            title="Drag and Drop Example",
            default_width=600,
            default_height=400,
            child=drop_area,
        )
        window.present()

    @staticmethod
    def _on_files_dropped(
        _drop_area: DropArea,
        file_list: Gdk.FileList,
    ) -> None:
        print("Dropped files:")

        for file in file_list.get_files():
            print(f"  path: {file.get_path()}")
            print(f"  URI:  {file.get_uri()}")


if __name__ == "__main__":
    Application().run()

Output when dragging a folder onto it after having opened Nautilus through PyCharm:

Offered formats: GdkFileList GFile gchararray text/plain;charset=utf-8 text/uri-list application/vnd.portal.filetransfer application/vnd.portal.files
Failed to read dropped files: Could not convert data from application/vnd.portal.filetransfer to GdkFileList

And when I close Nautilus and wait a minute before re-opening:

Offered formats: GdkFileList GFile gchararray text/plain;charset=utf-8 text/uri-list application/vnd.portal.filetransfer application/vnd.portal.files
Dropped files:
  path: /home/user/Pictures
  URI:  file:///home/user/Pictures

Edit

It looks like these can be read if I add the offered formats to the target’s formats:

g_formats = Gdk.ContentFormats.new_for_gtype(Gdk.FileList)
m_formats = Gdk.ContentFormats.new(["text/plain;charset=utf-8",
                                    "text/uri-list",
                                    "application/vnd.portal.filetransfer",
                                    "application/vnd.portal.files"])
formats = g_formats.union(m_formats)
target = Gtk.DropTargetAsync.new(
    formats=formats,
    actions=Gdk.DragAction.COPY,
)

and read them as str:

drop.read_value_async(
    type=str,
    io_priority=GLib.PRIORITY_DEFAULT,
    cancellable=None,
    callback=self._on_drop_data_ready,
 )

However, having the view of someone experienced on this question or some reference codebase would be great.