feat(dnd_destination): xdg file transfer portal support

Requires the `xdg-portal` feature to be enabled to use these features.

- Adds `DndDestination::on_file_transfer` method to handle `application/vnd.portal.filetransfer` drop requests
- Adds `command::file_transfer_receive` function to handle the file transfer request messages
- Adds `command::file_transfer_send` to initiate a file transfer from the application
This commit is contained in:
Frieder Hannenheim 2026-02-16 15:41:35 +00:00 committed by GitHub
parent ae1f15f37e
commit 21c5a4f34a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 0 deletions

View file

@ -40,6 +40,8 @@ pub fn dnd_destination_for_data<'a, T: AllowedMimeTypes, Message: 'static>(
static DRAG_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
const DND_DEST_LOG_TARGET: &str = "libcosmic::widget::dnd_destination";
#[cfg(feature = "xdg-portal")]
pub const FILE_TRANSFER_MIME: &str = "application/vnd.portal.filetransfer";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DragId(pub u128);
@ -73,6 +75,8 @@ pub struct DndDestination<'a, Message> {
on_action_selected: Option<Box<dyn Fn(DndAction) -> Message>>,
on_data_received: Option<Box<dyn Fn(String, Vec<u8>) -> Message>>,
on_finish: Option<Box<dyn Fn(String, Vec<u8>, DndAction, f64, f64) -> Message>>,
#[cfg(feature = "xdg-portal")]
on_file_transfer: Option<Box<dyn Fn(String) -> Message>>,
}
impl<'a, Message: 'static> DndDestination<'a, Message> {
@ -99,6 +103,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> {
on_action_selected: None,
on_data_received: None,
on_finish: None,
#[cfg(feature = "xdg-portal")]
on_file_transfer: None,
}
}
@ -124,6 +130,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> {
on_finish: Some(Box::new(move |mime, data, action, _, _| {
on_finish(T::try_from((data, mime)).ok(), action)
})),
#[cfg(feature = "xdg-portal")]
on_file_transfer: None,
}
}
@ -159,6 +167,8 @@ impl<'a, Message: 'static> DndDestination<'a, Message> {
on_action_selected: None,
on_data_received: None,
on_finish: None,
#[cfg(feature = "xdg-portal")]
on_file_transfer: None,
}
}
@ -237,6 +247,20 @@ impl<'a, Message: 'static> DndDestination<'a, Message> {
self
}
/// Add a message that will be emitted instead of [`on_data_received`](Self::on_data_received) if the dropped files
/// are offered through the xdg share portal. You can then use [`crate::command::file_transfer_receive`]
/// with the key to receive the files.
#[cfg(feature = "xdg-portal")]
#[must_use]
pub fn on_file_transfer(mut self, f: impl Fn(String) -> Message + 'static) -> Self {
match self.mime_types.iter().position(|v| v == "text/uri-list") {
Some(i) => self.mime_types.insert(i, Cow::Borrowed(FILE_TRANSFER_MIME)),
None => self.mime_types.push(Cow::Borrowed(FILE_TRANSFER_MIME)),
}
self.on_file_transfer = Some(Box::new(f));
self
}
/// Returns the drag id of the destination.
///
/// # Panics
@ -496,6 +520,13 @@ impl<Message: 'static> Widget<Message, crate::Theme, crate::Renderer>
"offer data id={my_id:?} mime={mime_type:?} bytes={}",
data.len()
);
#[cfg(feature = "xdg-portal")]
if mime_type == FILE_TRANSFER_MIME && let Some(f) = self.on_file_transfer.as_ref() && let Ok(s) = String::from_utf8(data[..data.len() - 1].to_vec()) {
shell.publish(f(s));
return event::Status::Captured;
}
if let (Some(msg), ret) = state.on_data_received(
mime_type,
data,