Feat: let a drag leaving a blade hand files to the system - #13
Open
YuriRCosta wants to merge 1 commit into
Open
YuriRCosta wants to merge 1 commit into
YuriRCosta wants to merge 1 commit into
Conversation
Dragging a row out of a blade only ever produced a synthetic drop: the backend found the window under the cursor and typed the paths into it. An application that expects files, such as a browser upload field, got nothing, because the QtQuick drag never left the process. BrowserRow already carried a uri-list payload, but with the default internal drag type Qt never built a system drag and the mime data was inert. A Wayland drag owns the pointer and the keyboard for as long as it lasts, so it cannot coexist with the drop wheel inside one gesture: the wheel opens on a key the compositor no longer delivers. The drag therefore starts internal and is promoted only when it leaves the blade, and only when the new dragOut setting asks for it. Pressing the drop-wheel key first opens the wheel and cancels the promotion, so that path keeps every key, the pointer and its paste actions. dragOut defaults to paste, so nothing changes until it is set to system. Row drop targets now accept text/uri-list beside the internal drag key, because a system offer carries mime types instead. That is what lets a promoted drag still land on a folder row, and it also accepts files dragged in from other applications. PathText.droppedPath resolves an offered url whether or not the compositor percent-encodes it, so a name with a space arrives whole; selectionUris separates and terminates the list with CRLF as RFC 2483 requires, which starts to matter once the payload leaves the process.
|
Test report from an Omarchy user:
This confirms the root cause and supports the configurable |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebase note
This replaces #4, which GitHub closed by itself when the
0.1.3base branch wasdeleted for the release. Rebased onto
0.2.0. The settings lifecycle, the IPCstatus payload, the
FilesSettingsrow, the expectation and the VM block allneeded conflict resolution, and the expectation is now E-17-15 because
0.2.0already uses E-17-14 for the font-size shortcuts.
Gate status on 0.2.0
tests/runstops before it finishes, ontests/qml/tst_branches_module.qml,which fails on a clean
0.2.0checkout with nothing applied. I swept all 85 QMLsuites on both sides:
0.2.0alone fails five —tst_branches_module,tst_operations_forms,tst_remote_containment,tst_usage_heatmap,tst_wheel_release— and this branch fails exactly the same five and no others.Every Rust suite passes, including the new source-contract test, and
qmllintisclean. Nothing here adds a failure, and I could not produce a fully green
tests/runon this base, so the box below stays unchecked.screenrecording-2026-09-11_22-15-30.mp4
What this changes
Adds an optional
dragOutmode so a file drag can leave a blade as a realWayland drag and hand the files to the application it is dropped on. The
default,
paste, keeps today's behavior exactly.BrowserRowalready declared atext/uri-listpayload, but with the defaultinternal drag type Qt never built a system drag, so the mime data was inert.
A compositor drag owns the pointer and the keyboard for as long as it lasts, so
it cannot share a gesture with the drop wheel, which opens on a key the
compositor no longer delivers. The button therefore chooses the gesture:
dragOut: systemdragOut: paste(default)Row drop targets now accept
text/uri-listbeside the internal drag key,because a system offer carries mime types instead of the key. That keeps folder
drops working, and it also makes folder rows accept files dragged in from other
applications.
PathText.droppedPathresolves an offered url whether or not thecompositor percent-encodes it, so a name containing a space arrives intact —
without it, dropping a folder named
folder testsilently did nothing.selectionUrisnow separates and terminates the list with CRLF per RFC 2483,which starts to matter once the payload leaves the process.
User documentation:
docs/agent-written/dragging-files.md, registered in thedocumentation catalogue.
README.mdis human-maintained, so I did not touch it;if you want a line there, something like "hold the right button to drag with the
wheel, or turn on Drag out to hand files to other applications" would cover it.
Justification
Dragging a file from FileBlade into a browser upload field, a chat window or any
other client that takes files did not work at all: the only drag-out was the
synthetic one, which types paths into the window under the cursor. That is the
right behavior for a terminal and the wrong one for an application that expects
files. This adds the second case without taking the first away, and leaves the
default alone so existing users see no change until they opt in.
How it was tested
The bundle now verifies too, since the pinned 1.98.0 toolchain is installed
here; this change is QML and docs only, so the bundle is untouched.
fileblade-bin: OKconfirms the checksum and source id are untouched; the change is QML and docs
only, so no bundle rebuild is required. Every Rust, contract, qmllint and QML
suite passes, including a new
tst_path_textcase for dropped urls with spacesand a new source-contract test for the setting and the gesture.
No VM scenarios were run: the
ovmharness fromtest-omarchy-pluginis notavailable on this machine.
tests/vm/expectations/17-settings.shhas theE-17-14 block written but unexecuted, so please treat the live checks below as
the evidence instead.
Live checks on Omarchy 4.0.2, Hyprland, docked blades, running this branch's QML
in the installed plugin:
dragOut: system, left button: one file and three files land in a Firefoxupload field; three arrive as three
highlights follow the pointer, scroll and keys work, actions run on the
dragged files, and cancelling runs nothing
named with a space, which is the bug the url decoding fixes
dragOut: paste: wheel, wheel key, scroll, drag scroll, folder drops and pathpastes all behave as they do on
0.1.3Worth flagging: an earlier revision of this branch passed
tests/rungreenwhile the blade rendered empty at runtime, because a normalizer was not
re-exposed on
Serviceandqmllintcannot resolve a call across objects. Onlythe live check caught it. That is an argument for the live-check rule in
CONTRIBUTING, not against the gate.
Notes on what was ruled out
I tried to keep both gestures in one drag by starting internal and promoting to
a system drag when the pointer leaves the blade.
Drag.startDrag()works thatway in an isolated Quickshell panel but not from a blade: it runs and the
compositor never takes over. Direct, deferred with
Qt.callLater, and with thefocus grab released first all behaved the same, and I could not identify the
cause, so the gesture is decided at press instead.
Probes did establish that a drop wheel could survive inside a system drag if its
trigger moved to
hyprland-global-shortcuts: a global shortcut fires with thedrag active, a surface created mid-drag receives the offer and the drop with its
urls, pointer motion reaches it (541 events in one drag), and scroll and keys
arrive through compositor binds. That would mean registering temporary binds for
the duration of every drag and leaking them if the process dies mid-gesture, and
it rewrites the wheel's input model, so it is not in this PR. If you want that
direction, the measurements are here and I am happy to do it as a follow-up.
Review
A coding agent reviewed the diff before this PR and found three things, all
fixed here:
the row drop target was gated on
dropAllowed, which derives from theinternal drag's paths, so an external drag into a fresh session was refused
and, after any internal drag, was judged against a stale selection. The gate
now only applies
canDropwhile an internal drag is active.releasing a right-button drag while the wheel was already open marked the drag
consumed, so
endDragskipped the branch that resolves the wheel; the wheelstayed open with
wheelFromDragset and the next drag ran an action the usernever chose.
droppedPathnow rejects query and fragment suffixes, malformed percentescapes, control characters and bare
/, and external drops are validatedwith
DragPlan.canDropagainst the paths actually offered. Newtst_path_textcases cover..,%2e%2e,%00,file:///and the rest.tests/runpasses — everything passes excepttools/bundle verify, whichneeds
rustupwith the pinned 1.98.0 toolchain and is unavailable on thismachine;
fileblade-bin: OKshows the bundle is untouched by this changeIf a source contract test changed, the reason is described above
I ran a coding agent to review the implementation work, if the original work was generated by another coding agent
I have checked for any possible security issues