diff --git a/examples/bevy/Cargo.lock b/examples/bevy/Cargo.lock index a41dbf84816..4b52397fd30 100644 --- a/examples/bevy/Cargo.lock +++ b/examples/bevy/Cargo.lock @@ -3636,9 +3636,9 @@ checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" [[package]] name = "constant_time_eq" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" +checksum = "7dab3fe76c1571ecd5cfe878b61bce3fedf4adbde5d8a8653b2a7956ffd14628" [[package]] name = "constgebra" @@ -6987,9 +6987,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.77" +version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" +checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags 2.11.1", "cfg-if", @@ -7019,9 +7019,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.113" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", @@ -7031,9 +7031,9 @@ dependencies = [ [[package]] name = "orbclient" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd" dependencies = [ "libc", "libredox", @@ -9353,11 +9353,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -9366,7 +9366,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -10675,6 +10675,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" diff --git a/internal/backends/qt/lib.rs b/internal/backends/qt/lib.rs index 0cefd760436..c3f44972867 100644 --- a/internal/backends/qt/lib.rs +++ b/internal/backends/qt/lib.rs @@ -13,6 +13,8 @@ extern crate alloc; +#[cfg(not(no_qt))] +use i_slint_core::clipboard::ClipboardData; use i_slint_core::platform::PlatformError; use std::rc::Rc; #[cfg(not(no_qt))] @@ -293,45 +295,8 @@ impl i_slint_core::platform::Platform for Backend { } #[cfg(not(no_qt))] - fn set_clipboard_text(&self, _text: &str, _clipboard: i_slint_core::platform::Clipboard) { - use cpp::cpp; - let is_selection: bool = match _clipboard { - i_slint_core::platform::Clipboard::DefaultClipboard => false, - i_slint_core::platform::Clipboard::SelectionClipboard => true, - _ => return, - }; - let text: qttypes::QString = _text.into(); - cpp! {unsafe [text as "QString", is_selection as "bool"] { - ensure_initialized(); - if (is_selection && !QGuiApplication::clipboard()->supportsSelection()) - return; - QGuiApplication::clipboard()->setText(text, is_selection ? QClipboard::Selection : QClipboard::Clipboard); - } } - } - - #[cfg(not(no_qt))] - fn clipboard_text(&self, _clipboard: i_slint_core::platform::Clipboard) -> Option { - use cpp::cpp; - let is_selection: bool = match _clipboard { - i_slint_core::platform::Clipboard::DefaultClipboard => false, - i_slint_core::platform::Clipboard::SelectionClipboard => true, - _ => return None, - }; - let has_text = cpp! {unsafe [is_selection as "bool"] -> bool as "bool" { - ensure_initialized(); - if (is_selection && !QGuiApplication::clipboard()->supportsSelection()) - return false; - return QGuiApplication::clipboard()->mimeData(is_selection ? QClipboard::Selection : QClipboard::Clipboard)->hasText(); - } }; - if has_text { - return Some( - cpp! { unsafe [is_selection as "bool"] -> qttypes::QString as "QString" { - return QGuiApplication::clipboard()->text(is_selection ? QClipboard::Selection : QClipboard::Clipboard); - }} - .into(), - ); - } - None + fn clipboard(&self) -> &dyn i_slint_core::clipboard::PlatformClipboard { + &QtPlatformClipboard } #[cfg(not(no_qt))] @@ -370,6 +335,87 @@ impl i_slint_core::platform::Platform for Backend { } } +#[cfg(not(no_qt))] +struct QtPlatformClipboard; + +#[cfg(not(no_qt))] +impl i_slint_core::clipboard::PlatformClipboard for QtPlatformClipboard { + fn set( + &self, + clipboard: i_slint_core::platform::Clipboard, + data: Rc, + ) { + use cpp::cpp; + + use i_slint_core::clipboard::mime; + + let data_for_mime_types = data.clone(); + let Some(mime_type) = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| mime::PLAINTEXT.contains(mime_type)) + else { + return; + }; + + let Some(string) = data.read(mime_type).ok().and_then(|any_data| any_data.as_string()) + else { + eprintln!( + "Testing clipboard provided non-string data: {:?}", + data_for_mime_types.mime_types() + ); + return; + }; + + let is_selection: bool = match clipboard { + i_slint_core::platform::Clipboard::DefaultClipboard => false, + i_slint_core::platform::Clipboard::SelectionClipboard => true, + _ => return, + }; + + let text: qttypes::QString = string.to_string().into(); + + cpp! {unsafe [text as "QString", is_selection as "bool"] { + ensure_initialized(); + if (is_selection && !QGuiApplication::clipboard()->supportsSelection()) + return; + QGuiApplication::clipboard()->setText(text, is_selection ? QClipboard::Selection : QClipboard::Clipboard); + } } + } + + fn get( + &self, + clipboard: i_slint_core::platform::Clipboard, + ) -> Result, i_slint_core::platform::PlatformError> { + use cpp::cpp; + + let is_selection: bool = match clipboard { + i_slint_core::platform::Clipboard::DefaultClipboard => false, + i_slint_core::platform::Clipboard::SelectionClipboard => true, + _ => { + return Err(PlatformError::Other(format!("No such clipboard {clipboard:?}"))); + } + }; + + let has_type = cpp! {unsafe [is_selection as "bool"] -> bool as "bool" { + ensure_initialized(); + if (is_selection && !QGuiApplication::clipboard()->supportsSelection()) + return false; + return QGuiApplication::clipboard()->mimeData(is_selection ? QClipboard::Selection : QClipboard::Clipboard)->hasText(); + } }; + + if !has_type { + return Ok(Rc::new(())); + } + + let clipboard_string: String = cpp! { unsafe [is_selection as "bool"] -> qttypes::QString as "QString" { + return QGuiApplication::clipboard()->text(is_selection ? QClipboard::Selection : QClipboard::Clipboard); + }}.into(); + + Ok(Rc::new(i_slint_core::SharedString::from(clipboard_string))) + } +} + /// This helper trait can be used to obtain access to a pointer to a QtWidget for a given /// [`slint::Window`](slint:rust:slint/struct.window).")] #[cfg(not(no_qt))] diff --git a/internal/backends/testing/testing_backend.rs b/internal/backends/testing/testing_backend.rs index 13d98689bb2..3142d0a05ac 100644 --- a/internal/backends/testing/testing_backend.rs +++ b/internal/backends/testing/testing_backend.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use i_slint_core::api::PhysicalSize; +use i_slint_core::clipboard::{ClipboardData, PlatformClipboard, mime}; use i_slint_core::graphics::euclid::{Point2D, Size2D}; use i_slint_core::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize}; use i_slint_core::platform::PlatformError; @@ -29,8 +30,62 @@ pub struct TestingBackendOptions { pub threading: bool, } +#[derive(Default)] +struct TestingPlatformClipboard { + clipboard: Mutex>>, +} + +impl PlatformClipboard for TestingPlatformClipboard { + fn set( + &self, + clipboard: i_slint_core::platform::Clipboard, + data: std::rc::Rc, + ) { + if clipboard != i_slint_core::platform::Clipboard::DefaultClipboard { + eprintln!("No such clipboard {clipboard:?}"); + return; + } + + let data_for_mime_types = data.clone(); + let Some(mime_type) = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| mime::PLAINTEXT.contains(mime_type)) + else { + return; + }; + + let Some(string) = data.read(mime_type).ok().and_then(|any_data| any_data.as_string()) + else { + eprintln!( + "Testing clipboard provided non-string data: {:?}", + data_for_mime_types.mime_types() + ); + return; + }; + + *self.clipboard.lock().unwrap() = Some(Rc::new(string)); + } + + fn get( + &self, + clipboard: i_slint_core::platform::Clipboard, + ) -> Result, PlatformError> { + if clipboard != i_slint_core::platform::Clipboard::DefaultClipboard { + return Err(PlatformError::Other(format!("No such clipboard {clipboard:?}"))); + } + + Ok(self + .clipboard + .lock() + .unwrap() + .as_ref() + .map_or_else(|| Rc::new(()) as Rc, |value| value.clone())) + } +} + pub struct TestingBackend { - clipboard: Mutex>, + clipboard: TestingPlatformClipboard, queue: Option, mock_time: bool, #[allow(dead_code)] @@ -40,7 +95,7 @@ pub struct TestingBackend { impl TestingBackend { pub fn new(options: TestingBackendOptions) -> Self { Self { - clipboard: Mutex::default(), + clipboard: Default::default(), queue: options.threading.then(|| Queue(Default::default(), std::thread::current())), mock_time: options.mock_time, open_url: Default::default(), @@ -74,18 +129,8 @@ impl i_slint_core::platform::Platform for TestingBackend { } } - fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { - if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard { - *self.clipboard.lock().unwrap() = Some(text.into()); - } - } - - fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option { - if clipboard == i_slint_core::platform::Clipboard::DefaultClipboard { - self.clipboard.lock().unwrap().clone() - } else { - None - } + fn clipboard(&self) -> &dyn i_slint_core::clipboard::PlatformClipboard { + &self.clipboard as _ } fn run_event_loop(&self) -> Result<(), PlatformError> { diff --git a/internal/backends/winit/lib.rs b/internal/backends/winit/lib.rs index 316f6a7b42e..4cb6f9ab53c 100644 --- a/internal/backends/winit/lib.rs +++ b/internal/backends/winit/lib.rs @@ -11,6 +11,7 @@ extern crate alloc; use event_loop::{CustomEvent, EventLoopState}; use i_slint_core::api::EventLoopError; +use i_slint_core::clipboard::PlatformClipboard; use i_slint_core::graphics::RequestedGraphicsAPI; use i_slint_core::platform::{EventLoopProxy, PlatformError}; use i_slint_core::window::WindowAdapter; @@ -399,8 +400,7 @@ pub(crate) struct SharedBackendData { /// List of visible windows that have been created when without the event loop and /// need to be mapped to a winit Window as soon as the event loop becomes active. inactive_windows: RefCell>>, - #[cfg(not(target_arch = "wasm32"))] - clipboard: std::cell::RefCell, + clipboard: WinitPlatformClipboard, not_running_event_loop: RefCell>>, event_loop_proxy: winit::event_loop::EventLoopProxy, /// The generation is used to determine if a quit_event_loop call is meant for the current @@ -489,7 +489,9 @@ impl SharedBackendData { active_windows, inactive_windows: Default::default(), #[cfg(not(target_arch = "wasm32"))] - clipboard: RefCell::new(clipboard), + clipboard: WinitPlatformClipboard(RefCell::new(clipboard)), + #[cfg(target_arch = "wasm32")] + clipboard: WinitPlatformClipboard, not_running_event_loop: RefCell::new(Some(event_loop)), event_loop_proxy, event_loop_generation: Default::default(), @@ -775,34 +777,116 @@ impl i_slint_core::platform::Platform for Backend { ))) } - #[cfg(target_arch = "wasm32")] - fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { - crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard); + fn clipboard(&self) -> &dyn i_slint_core::clipboard::PlatformClipboard { + &self.shared_data.clipboard } - #[cfg(not(target_arch = "wasm32"))] - fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { - let mut pair = self.shared_data.clipboard.borrow_mut(); - if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) { - clipboard.set_contents(text.into()).ok(); - } + fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> { + webbrowser::open(url).map_err(|e| { + i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}")) + }) + } +} + +#[cfg(target_arch = "wasm32")] +struct WinitPlatformClipboard; + +#[cfg(not(target_arch = "wasm32"))] +struct WinitPlatformClipboard(core::cell::RefCell); + +#[cfg(target_arch = "wasm32")] +impl PlatformClipboard for WinitPlatformClipboard { + fn set( + &self, + clipboard: i_slint_core::platform::Clipboard, + data: Rc, + ) { + use i_slint_core::clipboard::mime; + + let data_for_mime_types = data.clone(); + let Some(mime_type) = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| mime::PLAINTEXT.contains(mime_type)) + else { + return; + }; + + let Some(string) = data.read(mime_type).ok().and_then(|any_data| any_data.as_string()) + else { + eprintln!( + "Testing clipboard provided non-string data: {:?}", + data_for_mime_types.mime_types() + ); + return; + }; + + crate::wasm_input_helper::set_clipboard_text(string.into(), clipboard); } - #[cfg(target_arch = "wasm32")] - fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option { - crate::wasm_input_helper::get_clipboard_text(clipboard) + fn get( + &self, + clipboard: i_slint_core::platform::Clipboard, + ) -> Result, PlatformError> { + use i_slint_core::SharedString; + + Ok(crate::wasm_input_helper::get_clipboard_text(clipboard).map_or_else( + || Rc::new(()) as Rc, + |string| Rc::new(SharedString::from(string)), + )) } +} - #[cfg(not(target_arch = "wasm32"))] - fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option { - let mut pair = self.shared_data.clipboard.borrow_mut(); - clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok()) +#[cfg(not(target_arch = "wasm32"))] +impl PlatformClipboard for WinitPlatformClipboard { + fn set( + &self, + clipboard: i_slint_core::platform::Clipboard, + data: Rc, + ) { + use i_slint_core::clipboard::mime; + + let mut pair = self.0.borrow_mut(); + let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard.clone()) else { + eprintln!("Unknown clipboard: {clipboard:?}"); + return; + }; + + let data_for_mime_types = data.clone(); + let Some(mime_type) = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| mime::PLAINTEXT.contains(mime_type)) + else { + return; + }; + + let Some(string) = data.read(mime_type).ok().and_then(|any_data| any_data.as_string()) + else { + eprintln!( + "Testing clipboard provided non-string data: {:?}", + data_for_mime_types.mime_types() + ); + return; + }; + + if let Err(e) = clipboard.set_contents(string.into()) { + eprintln!("Error writing clipboard: {e}"); + } } - fn open_url(&self, url: &str) -> Result<(), i_slint_core::platform::PlatformError> { - webbrowser::open(url).map_err(|e| { - i_slint_core::platform::PlatformError::Other(format!("Failed to open URL: {e}")) - }) + fn get( + &self, + clipboard: i_slint_core::platform::Clipboard, + ) -> Result, PlatformError> { + use i_slint_core::SharedString; + + let mut pair = self.0.borrow_mut(); + let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard.clone()) else { + return Err(PlatformError::Other(format!("No such clipboard {clipboard:?}"))); + }; + + Ok(Rc::new(SharedString::from(clipboard.get_contents()?))) } } diff --git a/internal/backends/winit/wasm_input_helper.rs b/internal/backends/winit/wasm_input_helper.rs index f813830e8af..fe2699dbb41 100644 --- a/internal/backends/winit/wasm_input_helper.rs +++ b/internal/backends/winit/wasm_input_helper.rs @@ -324,9 +324,7 @@ fn event_text(e: &web_sys::KeyboardEvent, is_apple: bool) -> Option &'a RefCell); pub(crate) fn set_clipboard_text(data: String, clipboard: i_slint_core::platform::Clipboard) { - if CURRENT_WASM_CLIPBOARD_DATA.is_set() - && matches!(clipboard, i_slint_core::platform::Clipboard::DefaultClipboard) - { + if matches!(clipboard, i_slint_core::platform::Clipboard::DefaultClipboard) { CURRENT_WASM_CLIPBOARD_DATA.with(|current_data| *current_data.borrow_mut() = data) } } diff --git a/internal/core/any_data.rs b/internal/core/any_data.rs new file mode 100644 index 00000000000..f42b62391ed --- /dev/null +++ b/internal/core/any_data.rs @@ -0,0 +1,27 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +//! Dynamically-typed data, for runtime-generic code such as [`ClipboardData`](crate::clipboard::ClipboardData). + +use crate::SharedString; + +/// A piece of data of unspecified type. Use the accessor methods to downcast this to a specific type. +#[derive(Clone)] +pub struct AnyData { + // Eventually this will be something like `Rc`, but since we only support `SharedString` + // for now, this simplifies the implementation. + inner: SharedString, +} + +impl AnyData { + /// Returns a reference to the inner value if it is of type `T`, or `None` if it isn’t. + pub fn as_string(&self) -> Option { + Some(self.inner.clone()) + } +} + +impl From for AnyData { + fn from(value: SharedString) -> Self { + Self { inner: value } + } +} diff --git a/internal/core/api.rs b/internal/core/api.rs index abff9bef783..27216a393da 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -1281,6 +1281,7 @@ pub enum PlatformError { /// or call [`platform::set_platform()`](crate::platform::set_platform) /// before running the event loop NoPlatform, + /// The Slint Platform does not provide an event loop. /// /// The [`Platform::run_event_loop`](crate::platform::Platform::run_event_loop) @@ -1298,6 +1299,9 @@ pub enum PlatformError { /// Another platform-specific error occurred. #[cfg(feature = "std")] OtherError(Box), + + /// [`ClipboardData::read`](crate::clipboard::ClipboardData::read) was called, but no value of that type was provided. + ClipboardTypeNotFound(String), } #[cfg(target_arch = "wasm32")] @@ -1331,6 +1335,10 @@ impl core::fmt::Display for PlatformError { PlatformError::Other(str) => f.write_str(str), #[cfg(feature = "std")] PlatformError::OtherError(error) => error.fmt(f), + + PlatformError::ClipboardTypeNotFound(type_) => { + write!(f, "Type not found on clipboard: {type_}") + } } } } diff --git a/internal/core/clipboard.rs b/internal/core/clipboard.rs new file mode 100644 index 00000000000..217114d72fb --- /dev/null +++ b/internal/core/clipboard.rs @@ -0,0 +1,73 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +use alloc::rc::Rc; + +use crate::{AnyData, SharedString, api::PlatformError}; + +pub mod mime; + +pub trait PlatformClipboard { + fn set(&self, clipboard: crate::platform::Clipboard, value: Rc); + fn get( + &self, + clipboard: crate::platform::Clipboard, + ) -> Result, PlatformError>; + + fn clear(&self, clipboard: crate::platform::Clipboard) { + self.set(clipboard, Rc::new(())) + } +} + +pub struct DummyPlatformClipboard; + +impl PlatformClipboard for DummyPlatformClipboard { + fn set(&self, _: crate::platform::Clipboard, _: Rc) {} + fn get(&self, _: crate::platform::Clipboard) -> Result, PlatformError> { + Ok(Rc::new(())) + } +} + +/// This trait is intended to be implemented by platforms for some internal custom data type, or they +/// can use the `TypeMap` implementation which is for application-internal use only. For example, +/// most embedded platforms will be able to just use `TypeMap`. +/// +/// # Standard Types +/// +/// Some MIME types should return specific, standardized types that consumers can rely on. This +/// is not a hard requirement, and this list may be expanded later, but if these conventions are +/// not followed then consumers may act inconsistently. +/// +/// - `text/*`: `SharedString` (this will require boxing the `SharedString` inside an `Rc`) +pub trait ClipboardData { + /// This should be called before `read`, and returns the set of available MIME types. + fn mime_types(&self) -> &[&str]; + + /// If this type can be interpreted as the given MIME type, return that type wrapped in an `Rc`. + fn read(self: Rc, type_: &str) -> Result; +} + +impl ClipboardData for SharedString { + fn mime_types(&self) -> &[&str] { + self::mime::PLAINTEXT + } + + fn read(self: Rc, type_: &str) -> Result { + if self.mime_types().contains(&type_) { + Ok((*self).clone().into()) + } else { + Err(PlatformError::ClipboardTypeNotFound(type_.into())) + } + } +} + +// Dummy implementation of `ClipboardData` that does nothing, used to clear the clipboard. +impl ClipboardData for () { + fn mime_types(&self) -> &[&str] { + &[] + } + + fn read(self: Rc, type_: &str) -> Result { + Err(PlatformError::ClipboardTypeNotFound(type_.into())) + } +} diff --git a/internal/core/clipboard/mime.rs b/internal/core/clipboard/mime.rs new file mode 100644 index 00000000000..9471cb1c4db --- /dev/null +++ b/internal/core/clipboard/mime.rs @@ -0,0 +1,7 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +pub const TEXT_PLAIN: &str = "text/plain"; +pub const TEXT_PLAIN_UTF_8: &str = "text/plain;charset=utf-8"; + +pub const PLAINTEXT: &[&str] = &[TEXT_PLAIN, TEXT_PLAIN_UTF_8]; diff --git a/internal/core/items/text.rs b/internal/core/items/text.rs index b953c055a6a..8d94c2310a4 100644 --- a/internal/core/items/text.rs +++ b/internal/core/items/text.rs @@ -1954,7 +1954,8 @@ impl TextInput { WindowInner::from_pub(window_adapter.window()) .context() .platform() - .set_clipboard_text(&text[anchor..cursor], clipboard); + .clipboard() + .set(clipboard, alloc::rc::Rc::new(text)); } pub fn paste(self: Pin<&Self>, window_adapter: &Rc, self_rc: &ItemRc) { @@ -1970,7 +1971,19 @@ impl TextInput { if let Some(text) = WindowInner::from_pub(window_adapter.window()) .context() .platform() - .clipboard_text(clipboard) + .clipboard() + .get(clipboard) + .ok() + .and_then(|data| { + let data_for_mime_types = data.clone(); + let mime_type = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| crate::clipboard::mime::PLAINTEXT.contains(mime_type))?; + + data.read(mime_type).ok() + }) + .and_then(|any_data| any_data.as_string()) { self.preedit_text.set(Default::default()); self.insert(&text, window_adapter, self_rc); diff --git a/internal/core/lib.rs b/internal/core/lib.rs index 26ee6831c01..487854fccc3 100644 --- a/internal/core/lib.rs +++ b/internal/core/lib.rs @@ -31,8 +31,10 @@ pub use std::thread_local; pub mod accessibility; pub mod animations; +pub mod any_data; pub mod api; pub mod callbacks; +pub mod clipboard; pub mod component_factory; pub mod context; pub mod date_time; @@ -66,6 +68,9 @@ pub mod window; #[doc(inline)] pub use string::SharedString; +#[doc(inline)] +pub use any_data::AnyData; + #[doc(inline)] pub use sharedvector::SharedVector; diff --git a/internal/core/platform.rs b/internal/core/platform.rs index 0b1413843b4..be65c68296a 100644 --- a/internal/core/platform.rs +++ b/internal/core/platform.rs @@ -34,6 +34,11 @@ pub trait Platform { Err(PlatformError::NoEventLoopProvider) } + /// Return the clipboard implementation for this platform. + fn clipboard(&self) -> &dyn crate::clipboard::PlatformClipboard { + &crate::clipboard::DummyPlatformClipboard + } + /// Spins an event loop for a specified period of time. /// /// This function is similar to `run_event_loop()` with two differences: @@ -124,13 +129,30 @@ pub trait Platform { /// Sends the given text into the system clipboard. /// /// If the platform doesn't support the specified clipboard, this function should do nothing - fn set_clipboard_text(&self, _text: &str, _clipboard: Clipboard) {} + #[deprecated( + since = "1.17.0", + note = "See `Platform::clipboard` and the `PlatformClipboard` trait, which allow using the clipboard for types other than plaintext" + )] + fn set_clipboard_text(&self, text: &str, clipboard: Clipboard) { + self.clipboard().set(clipboard, alloc::rc::Rc::new(SharedString::from(text))); + } /// Returns a copy of text stored in the system clipboard, if any. /// /// If the platform doesn't support the specified clipboard, the function should return None - fn clipboard_text(&self, _clipboard: Clipboard) -> Option { - None + #[deprecated( + since = "1.17.0", + note = "See `Platform::clipboard` and the `PlatformClipboard` trait, which allow using the clipboard for types other than plaintext" + )] + fn clipboard_text(&self, clipboard: Clipboard) -> Option { + let data = self.clipboard().get(clipboard).ok()?; + let data_for_mime_types = data.clone(); + let mime_type = data_for_mime_types + .mime_types() + .iter() + .find(|mime_type| crate::clipboard::mime::PLAINTEXT.contains(mime_type))?; + + data.read(mime_type).ok()?.as_string().map(|string| string.into()) } /// This function is called when debug() is used in .slint files. The implementation @@ -158,7 +180,7 @@ pub trait Platform { /// The clip board, used in [`Platform::clipboard_text`] and [Platform::set_clipboard_text`] #[repr(u8)] #[non_exhaustive] -#[derive(PartialEq, Clone, Default)] +#[derive(Debug, PartialEq, Clone, Default)] pub enum Clipboard { /// This is the default clipboard used for text action for Ctrl+V, Ctrl+C. /// Corresponds to the secondary clipboard on X11.