New drag and drop API#4571
Conversation
|
I haven't read very much into impl. details, but from what I've seen:
In the current API, we have this URI list preloaded, but I think this is solved, we should only load with async-ish API once user asks, I think, if you want to follow Wayland. If the API is not lazy, I'm not sure how it should be done, so if yo can provide tl;dr it would help (haven't found clear wording in your research).
Well, because it's a lot of work, you need to negotiate what you want to read (image/text/html/whatever), then both ends should do non-blocking writing/reading to an FD, thus implying that you plug that FD into epoll based event loop or something. And ensure that you don't hang each other. And compositor does very little here actually, it just lets you exchange FDs with other end, but all this negotiation + writing the right mime type is on the client. So for example, when something drags object/pastes, you have a bunch of mime types you can use to query data, e.g. image/text/audio, then you ask for audio, and other end should provide audio. Then you want to initiate drag and drop, and you started dragging something, but this something is either text or image, and then depending on what the other end picks(e.g. you drag from winit to firefox, and firefox picks one of two mimetypes you gave to it), winit must reply with the right data. Note that you don't create buffer for both image and text before hand, you only create them once you get event what type of data other end wants. It can also ask you for both, or ask you something from time to time as long as you have advertised something. |
|
@kchibisov Thanks for your response. I appreciate you clarifying the way that data transfer (i.e. clipboard and drag-and-drop) works, but it might be worth reading through the PR since I have an extensive doc comment explaining the exact concerns that you bring up and how the API addresses them. In particular:
The implementation in this PR only addresses drag-and-drop, but it is specifically designed to support clipboard operations in the future. Clipboard operations are planned in a follow-up PR, and only left unimplemented for now for two reasons:
The type hierarchy is like so:
The API flow as-implemented by this PR is like so:
Note that we cannot just return a potentially-blocking Regarding moving |
But we can pass Also, X11 is nearly dead, so no point in design around it. |
|
Yeah, as of the latest few commits I’ve moved the whole drag-and-drop API to the event loop. Fetching still needs to be done asynchronously to support X11 though unfortunately, there’s not really a way around that without running the risk of deadlocking the event loop. The user can always immediately try to read the data without waiting for the I’ve also updated the dnd example to show how to use the new API. |
kchibisov
left a comment
There was a problem hiding this comment.
Generally using event will be fine, I guess. The API should certainly be async, sync won't work on Wayland as well.
650e7ee to
2e99f38
Compare
a0c95c8 to
ea394ea
Compare
|
This PR is now ready for review. The commit history is still really messy, my usual preference would be to either squash-and-merge, or (if the project doesn't squash PRs) manually clean the history up as the final step before merging so I can ensure that any changes in response to PR comments are included in the cleaned-up commits and I don't have to clean up the history multiple times. If you'd prefer that I clean the history up before review then just say so and I'll do that first, I know this is a big block of work so I'm happy to do anything on my end that makes it easier for you to review. Most of the new API surface is in |
|
Looks like CI / Test nightly Windows 32bit MSVC (pull_request) failed for reasons unrelated to the PR, but it won't let me manually re-run it. Does anyone know what happened there, and what I can do about it? |
|
The general API you propose LGTM. The only minor things I have are 1) Maybe |
kchibisov
left a comment
There was a problem hiding this comment.
I've looked more or less through wayland and somehow x11
For Walyand, I really suggest to move all the transfers via calloop and non-blocking APIs, since winit should never block on IO and right now PR uses write_all.
Also, I'll suggest to e.g. try to drag some big data (more than unix PIPE limit) to itself(e.g. drag and drop between 2 winit windows of the same application), outside.
21b44cf to
4b02286
Compare
4b02286 to
c2d04f1
Compare
7ca7e6a to
11fefa1
Compare
As part of re-implementing drag-and-drop, we want to re-add each platform as a separate commit. To prevent making the history too messy and to ensure that each commit compiles separately (to allow `git bisect` to work better in the future), we remove the old implementation first.
This implements a `DataTransfer`-style API for sending and receiving clipboard and drag-and-drop data. Implementations for each platform will be added in follow-up commits
This prepares for implementing the `DataTransfer` style drop API for Windows.
This adds an implementation of the `DataTransfer`-style drag-and-drop/clipboard API for Windows.
This adds an implementation of the `DataTransfer`-style drag-and-drop/clipboard API for macOS.
11fefa1 to
113ff1a
Compare
tronical
left a comment
There was a problem hiding this comment.
@kchibisov @madsmtm From my POV this is good to go. I believe the comments have been addressed (separating out the code, recent utf-16 removal, etc.). I'm good with this and would like to merge that in the coming days, unless there are any objections.
@kchibisov Yeah, I changed it so cancel is an explicit separate event instead of reusing |
| @@ -0,0 +1,808 @@ | |||
| //! Types related to drag-and-drop and data transfer on Wayland. | |||
| match std::io::copy(&mut encoder, file) { | ||
| Ok(0) => { | ||
| break PostAction::Remove; | ||
| }, | ||
| Ok(_) => {}, | ||
| Err(e) if e.kind() == ErrorKind::WouldBlock => { | ||
| break PostAction::Continue; | ||
| }, | ||
| Err(_) => { | ||
| break PostAction::Remove; |
There was a problem hiding this comment.
Does it perform some buffering under the hood or how does it work with WouldBlock, because e.g. usually with write you send chunks of PIPE_SIZE over it and have some local buffer?
There was a problem hiding this comment.
If the writer returns WouldBlock then it shouldn't consume any data from the reader. In general, if the writer can only write n bytes then it's expected to return Ok(n), then only return Err(..) without consuming anything upon next calling write. There shouldn't be any cases of data being consumed without ended up on the pipe.
There was a problem hiding this comment.
Hm, but if you write more than pipe size wouldn't it be always WouldBlock because it won't fit on the call time, so any retry will just fail because transfer is very big? I just know that common practice is do the small buffer and just write slowly via it, but I don't know what copy does under the hood, maybe it's how it works, as in tries to write slowly until it would block.
| seat_state.data_device = None; | ||
|
|
There was a problem hiding this comment.
Why it tied to pointer capability? I think there's no such restriction anywhere at all and it works with touch/keyboard/tablet/whatever.
The only requirement for data transfers, I believe, is that it should have some serial from input that is recent enough, but other than that pointer is irrelevant here.
You should also clean-up this dependency in other places.
There was a problem hiding this comment.
It's just because we need to call latest_button_serial. Should I instead do something like this:
seat.pointer_data()
.map(|pd| pd.latest_button_serial())
.or_else(|| {
seat.touch_data()
.map(|td| td.latest_down_serial()
})I'd need to add fn touch_data(&self) to WinitSeatState, to mirror fn pointer_data(&self).
The only thing is that this would require setting up/tearing down the data device when adding/removing SeatCapability::Touch rather than just SeatCapability::Pointer, and I'd rather not even try to support touch-only dnd right now. I don't have a device to test it on and I'd rather it be unimplemented than implemented but untested. We're hopefully going to put in a follow-up PR to have DnD work on iOS and Android eventually, in which case I might be able to convince my employer to get a Volla or something similar to test Wayland touch support too, but until then I don't want to speculatively implement it.
There was a problem hiding this comment.
I mean that you should drop data device once you don't have any capabilities and create once you get any of them, that should be simpler, I believe.
113ff1a to
b3e3ae7
Compare
This adds an implementation of the `DataTransfer`-style drag-and-drop/clipboard API for Linux + Wayland.
This adds an implementation of the `DataTransfer`-style drag-and-drop/clipboard API for Linux + X11.
b3e3ae7 to
c744765
Compare
| _: &QueueHandle<Self>, | ||
| _: &WlDataSource, | ||
| mime: String, | ||
| fd: WritePipe, |
There was a problem hiding this comment.
I believe that we must mark this fd non-blocking ourselves.
This PR implements a new API for drag and drop, with a
DataTransfertype which abstracts over the various clipboard/drag and drop APIs across different platforms. I built this on top of #2429 in order to ensure @SludgePhD gets credit if it gets merged, although admittedly I ended up removing pretty much all of their work while I was reworking the design.This is being built in order to help support drag-and-drop work in Slint's winit backend. As part of that work, I did extensive research on how drag-and-drop and clipboard APIs are implemented across different platforms, and wrote a (still WIP) research document that can be found here.
Some platforms (Wayland, X11) always transfer bytes with a MIME type, other platforms have a set of standardised transferrable types. However, all types that are supported cross-platform (images, RTF, HTML, plaintext, URIs/URI lists) are cleanly expressible using MIME types on all supported platforms. As part of this PR, I've written up a quick-and-dirty summary of which types are supported on different platforms (EDIT: the Windows section of that gist was based on WinRT, but I've since discovered that winit uses win32 and therefore can only send/receive file paths).
Design
The new API is inspired by the browser's
DataTransferAPI. The main complexity comes from supporting both the common set of capabilities (the types and traits inwinit-core/src/data_transfer.rs) while also allowing a consumer to use the platform-specific APIs.The design may look somewhat complex, and I am open to suggestions for simplifying it, but OS drag-and-drop/clipboard/etc APIs are just fundamentally complex. Unfortunately, there's going to be a lot of complexity here no matter the implementation. This article by a Wayland maintainer describes it as "arguably one of the most complicated parts of the core Wayland protocol", and from researching the design for other platforms it seems like Wayland actually has the simplest API.
In general, the API is designed around the idea that the user should be able to supply/read both cross-platform types and platform-specific types. Not all of the platform-specific types are fully implemented, although many of them are.
Current state
Both receiving and initiating a drag operation are implemented on Windows, Wayland and macOS. X11 supports receiving dropped data, but initiating a drag on X11 is not planned as part of this PR.
Example
The drag-and-drop example has been updated, so both sending and receiving drag operations is now shown off.
macOS
Sending and receiving a dragging operation:
2026-06-03.17-28-00.mov
Dragging styled text (via the HTML clipboard type) into the notes app on macOS:
2026-06-03.17-38-15.mov
Wayland
Video_2026-06-04_17-29-06.mp4
Windows
2026-06-04.18-41-50.mp4
AI disclaimer
Unsure if this is important to anyone reading this PR, but I thought I should mention just in case since I know that it's a common issue for OSS projects:
AI was NOT used for the development of the core winit API, nor the X11, Wayland, or macOS implementations, nor any other code, documentation, or comments contributed to this branch by me. It was also not used to write the PR description, nor any comments made by me. The Windows implementation was done by a colleague, so I do not know for sure if AI was used. I can ask him if the reviewer would like to know, but I can at least confirm that I've manually reviewed every line of his PR to my branch.