Skip to content

feat: expose raw drag text in DropDoneDetails for portal integration - #492

Merged
boyan01 merged 12 commits into
MixinNetwork:mainfrom
loucass:feat/raw-text-drop-details
Aug 25, 2026
Merged

feat: expose raw drag text in DropDoneDetails for portal integration#492
boyan01 merged 12 commits into
MixinNetwork:mainfrom
loucass:feat/raw-text-drop-details

Conversation

@loucass

@loucass loucass commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Flutter apps running in Flatpak sandboxes on Wayland cannot receive files dragged from outside their permitted directories (e.g., ~/Documents/). The XDG Desktop Portal FileTransfer mechanism mediates this: the source app registers the file and passes a key via the application/vnd.portal.filetransfer mimetype. The target app must call RetrieveFiles(key) to get a sandbox-accessible path.

Currently, desktop_drop only exposes parsed file:// URIs via DropDoneDetails.files. The raw drag text containing the portal key is discarded.

Solution

Expose the raw drag text received from GTK through the event chain:

  1. DropDoneEvent.rawText — optional field carrying the original drag data
  2. DropDoneDetails.rawText — passed through to the widget callback
  3. Export events.dart so consumers can access DropDoneEvent
  4. Linux channel now passes raw text to DropDoneEvent

Changes

  • packages/desktop_drop/lib/src/events.dart: Add rawText to DropDoneEvent with documentation
  • packages/desktop_drop/lib/src/drop_target.dart: Add rawText to DropDoneDetails, populate from event, with documentation
  • packages/desktop_drop/lib/src/channel.dart: Pass rawText: text in Linux performOperation_linux case
  • packages/desktop_drop/lib/desktop_drop.dart: Export events.dart
  • packages/desktop_drop/CHANGELOG.md: Version 0.8.1 entry

Cross-Platform Behavior

Platform rawText Value
Linux/Wayland Full GTK drag text (file:// URIs + portal keys)
macOS null (structured data only)
Windows null (paths list only)
Web null (structured data only)

Optional field, only populated where platform provides it.

Testing

  • Verified with LocalSend Flatpak on Fedora KDE Wayland (Plasma 6.7.4)
  • Drag from ~/Downloads/ → works (existing permission)
  • Drag from ~/Documents/ → now works via portal (key retrieved, RetrieveFiles returns accessible path)
  • Backward compatible — rawText is optional, defaults to null
  • No behavior change for non-portal drag sources

Follow-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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Exposes Linux drag payloads through DropDoneEvent and widget callbacks for portal integrations.

Changes:

  • Adds optional rawText fields 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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +47 to +50
/// 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).
Comment on lines +38 to +41
/// 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)';
loucass added 10 commits August 24, 2026 20:51
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.
@loucass
loucass requested a review from boyan01 August 24, 2026 22:16
@boyan01
boyan01 merged commit af1fb55 into MixinNetwork:main Aug 25, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants