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

@ -1,6 +1,9 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
#[cfg(feature = "xdg-portal")]
use std::os::fd::AsFd;
use iced::window;
/// Initiates a window drag.
@ -43,3 +46,24 @@ pub fn set_windowed<M>(id: window::Id) -> iced::Task<crate::Action<M>> {
pub fn toggle_maximize<M>(id: window::Id) -> iced::Task<crate::Action<M>> {
iced_runtime::window::toggle_maximize(id)
}
#[cfg(feature = "xdg-portal")]
pub fn file_transfer_send(writeable: bool, auto_stop: bool, files: Vec<impl AsFd + Send + Sync + 'static>) -> iced::Task<ashpd::Result<String>> {
iced::Task::future(async move {
let file_transfer = ashpd::documents::FileTransfer::new().await?;
let key = file_transfer.start_transfer(writeable, auto_stop).await?;
file_transfer.add_files(&key, &files).await?;
Ok(key)
})
}
/// Receive the files offered over the xdg share portal using the `key`.
/// Returns a list of file paths.
#[cfg(feature = "xdg-portal")]
pub fn file_transfer_receive(key: String) -> iced::Task<ashpd::Result<Vec<String>>> {
dbg!(&key);
iced::Task::future(async move {
let file_transfer = ashpd::documents::FileTransfer::new().await?;
file_transfer.retrieve_files(&key).await
})
}

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,