feat: expose raw drag text in DropDoneDetails for portal integration - #492
Conversation
There was a problem hiding this comment.
Pull request overview
Exposes Linux drag payloads through DropDoneEvent and widget callbacks for portal integrations.
Changes:
- Adds optional
rawTextfields and Linux propagation. - Exports raw drop events publicly.
- Adds Linux tests and a 0.8.1 changelog entry.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
packages/desktop_drop/lib/src/events.dart |
Adds raw payload data to drop events. |
packages/desktop_drop/lib/src/drop_target.dart |
Passes raw payloads to widget callbacks. |
packages/desktop_drop/lib/src/channel.dart |
Attaches Linux channel text to events. |
packages/desktop_drop/lib/desktop_drop.dart |
Exports event types publicly. |
packages/desktop_drop/test/channel_linux_test.dart |
Tests Linux payload parsing and preservation. |
packages/desktop_drop/CHANGELOG.md |
Documents version 0.8.1. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| _notifyEvent(DropDoneEvent( | ||
| location: Offset(offset[0], offset[1]), | ||
| files: paths.map((e) => DropItemFile(e)).toList(), | ||
| rawText: text, |
There was a problem hiding this comment.
One GTK 3 detail changes the diagnosis here: since GTK 3.24.37, gtk_drag_dest_add_uri_targets() already adds both application/vnd.portal.filetransfer and the legacy application/vnd.portal.files when the FileTransfer portal is available. Therefore the portal payload can reach this callback on current GTK without another explicit target entry.
There is still a negotiation problem worth addressing. gtk_drag_dest_find_target() selects the first destination target also offered by the source, while this plugin registers STRING first and gtk_target_list_add_uri_targets() appends text/uri-list before the portal targets. If a source offers both URI and portal representations, the inaccessible URI representation may still win. The native layer should prefer the portal target and pass the selected MIME type alongside its payload so Dart does not infer the type from the text.
The tests should also use the payloads GTK can actually deliver: either a URI list or a key-only portal payload. A combined file://...\nportal-key payload is not produced by MIME negotiation.
There was a problem hiding this comment.
Thanks for the detailed review @boyan01,pushed fixes for all of it:
- target name is NULL-checked now (gdk_atom_name can return null) and freed with g_autofree
- selection data gets copied with g_strndup using the actual length instead of trusting a null terminator
- docs fixed: smb:// would've been treated as a portal key the way I had it. keys are detected by Uri.hasScheme now, and GTK never actually mixes uris + key in one payload so the example was wrong too
- toString() doesn't print rawText anymore, no point leaking a one-time key into logs
- bumped pubspec to 0.8.1 to match the changelog
- tests use realistic payloads now (uris only or key only) + one for performOperation_portal
also went one step further: performOperation_portal resolves the key through org.freedesktop.portal.FileTransfer.RetrieveFiles and emits the document portal paths (/run/user/$UID/doc/...) as regular DropItemFiles.
reason: since the portal target wins negotiation now, old apps that don't know about rawText would get an empty file list where they used to at least get paths. resolving in the plugin keeps files meaning "openable paths" so nobody has to change their code, and doc paths are readable both inside sandboxes (per-app grant) and outside.
rawText still has the key if an app wants it, and if RetrieveFiles fails the event just fires with an empty list. adds dbus as a pure dart dep — happy to move this into native GDBus if you'd rather keep dart deps out.
| /// The portal key line is NOT a file URI and can be distinguished | ||
| /// by not starting with `file://`. It is only present when the | ||
| /// drag source used the XDG Desktop Portal (typical for sandboxed | ||
| /// apps on Wayland). |
| /// The portal key line is NOT a file URI and can be distinguished | ||
| /// by not starting with `file://`. It is only present when the | ||
| /// drag source used the XDG Desktop Portal (typical for sandboxed | ||
| /// apps on Wayland). |
| @@ -1,5 +1,9 @@ | |||
| # Changelog | |||
|
|
|||
| ## 0.8.1 | |||
| @override | ||
| String toString() { | ||
| return '$runtimeType($location, $files)'; | ||
| return '$runtimeType($location, $files, rawText: $rawText)'; |
…mbined payload tests
The same performOperation_portal payload was asserted twice.
gdk_atom_name() may return NULL; strcmp on it is undefined behavior.
gdk_atom_name() returns a heap-allocated string; leaking it on every drop.
Selection payload is not guaranteed NUL-terminated; casting the raw buffer to a C string could read past its end. Copy exactly length bytes with g_strndup and bail out on a negative length. Also release the FlValue argument list with g_autoptr instead of leaking it.
GTK delivers either a URI list or a single portal transfer key, never both mixed; the previous example implied otherwise. Changelog now covers the new channel message and target registration.
…user/doc/* When a drop is negotiated as application/vnd.portal.filetransfer, the payload is a one-time key rather than a path. Ask org.freedesktop.portal.FileTransfer.RetrieveFiles for that key; the document portal exports each dropped file and returns paths that are readable both inside and outside a Flatpak sandbox. Consumers receive ordinary DropItemFile paths as before, so no changes are needed in apps using desktop_drop. On resolution failure the event still fires with an empty file list, and rawText keeps the key.
Problem
Flutter apps running in Flatpak sandboxes on Wayland cannot receive files dragged from outside their permitted directories (e.g.,
~/Documents/). The XDG Desktop PortalFileTransfermechanism mediates this: the source app registers the file and passes a key via theapplication/vnd.portal.filetransfermimetype. The target app must callRetrieveFiles(key)to get a sandbox-accessible path.Currently,
desktop_droponly exposes parsedfile://URIs viaDropDoneDetails.files. The raw drag text containing the portal key is discarded.Solution
Expose the raw drag text received from GTK through the event chain:
DropDoneEvent.rawText— optional field carrying the original drag dataDropDoneDetails.rawText— passed through to the widget callbackevents.dartso consumers can accessDropDoneEventDropDoneEventChanges
packages/desktop_drop/lib/src/events.dart: AddrawTexttoDropDoneEventwith documentationpackages/desktop_drop/lib/src/drop_target.dart: AddrawTexttoDropDoneDetails, populate from event, with documentationpackages/desktop_drop/lib/src/channel.dart: PassrawText: textin LinuxperformOperation_linuxcasepackages/desktop_drop/lib/desktop_drop.dart: Exportevents.dartpackages/desktop_drop/CHANGELOG.md: Version 0.8.1 entryCross-Platform Behavior
rawTextValuenull(structured data only)null(paths list only)null(structured data only)Optional field, only populated where platform provides it.
Testing
~/Downloads/→ works (existing permission)~/Documents/→ now works via portal (key retrieved,RetrieveFilesreturns accessible path)rawTextis optional, defaults tonullFollow-up
This enables LocalSend (and other Flatpak Flutter apps) to implement proper portal-based drag-and-drop. Separate PR to LocalSend will consume this API.