From 4155edab8d123b767ddad67e24ca2d4c50f31ece Mon Sep 17 00:00:00 2001 From: Mattias Eriksson Date: Wed, 7 Feb 2024 17:25:40 +0100 Subject: Add support for primary clipboard --- core/src/clipboard.rs | 12 ++++++++++++ runtime/src/clipboard.rs | 14 ++++++++++++++ runtime/src/command/action.rs | 9 +++++++++ src/lib.rs | 4 +++- winit/src/application.rs | 12 ++++++++++++ winit/src/clipboard.rs | 34 ++++++++++++++++++++++++++++++++++ winit/src/multi_window.rs | 12 ++++++++++++ 7 files changed, 96 insertions(+), 1 deletion(-) diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index 081b4004..ff2e31d0 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -8,6 +8,12 @@ pub trait Clipboard { /// Writes the given text contents to the [`Clipboard`]. fn write(&mut self, contents: String); + + /// Reads the current content of Primary as text. + fn read_primary(&self) -> Option; + + /// Writes the given text contents to Primary. + fn write_primary(&mut self, contents: String); } /// A null implementation of the [`Clipboard`] trait. @@ -20,4 +26,10 @@ impl Clipboard for Null { } fn write(&mut self, _contents: String) {} + + fn read_primary(&self) -> Option { + None + } + + fn write_primary(&mut self, _contents: String) {} } diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index bc450912..49044a0f 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -51,3 +51,17 @@ pub fn read( pub fn write(contents: String) -> Command { Command::single(command::Action::Clipboard(Action::Write(contents))) } + +/// Read the current contents of primary. +pub fn read_primary( + f: impl Fn(Option) -> Message + 'static, +) -> Command { + Command::single(command::Action::ClipboardPrimary(Action::Read(Box::new( + f, + )))) +} + +/// Write the given contents to primary. +pub fn write_primary(contents: String) -> Command { + Command::single(command::Action::ClipboardPrimary(Action::Write(contents))) +} diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs index c9ffe801..f04c642c 100644 --- a/runtime/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -26,6 +26,9 @@ pub enum Action { /// Run a clipboard action. Clipboard(clipboard::Action), + /// Run a clipboard action on primary. + ClipboardPrimary(clipboard::Action), + /// Run a window action. Window(window::Action), @@ -66,6 +69,9 @@ impl Action { Self::Future(future) => Action::Future(Box::pin(future.map(f))), Self::Stream(stream) => Action::Stream(Box::pin(stream.map(f))), Self::Clipboard(action) => Action::Clipboard(action.map(f)), + Self::ClipboardPrimary(action) => { + Action::ClipboardPrimary(action.map(f)) + } Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(operation) => { @@ -88,6 +94,9 @@ impl fmt::Debug for Action { Self::Clipboard(action) => { write!(f, "Action::Clipboard({action:?})") } + Self::ClipboardPrimary(action) => { + write!(f, "Action::ClipboardPrimary({action:?})") + } Self::Window(action) => { write!(f, "Action::Window({action:?})") } diff --git a/src/lib.rs b/src/lib.rs index 3cd145f8..0804c9ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,7 +200,9 @@ pub use crate::core::{ pub mod clipboard { //! Access the clipboard. - pub use crate::runtime::clipboard::{read, write}; + pub use crate::runtime::clipboard::{ + read, read_primary, write, write_primary, + }; } pub mod executor { diff --git a/winit/src/application.rs b/winit/src/application.rs index 77e2c83e..2d3ac52c 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -715,6 +715,18 @@ pub fn run_command( clipboard.write(contents); } }, + command::Action::ClipboardPrimary(action) => match action { + clipboard::Action::Read(tag) => { + let message = tag(clipboard.read_primary()); + + proxy + .send_event(message) + .expect("Send message to event loop"); + } + clipboard::Action::Write(contents) => { + clipboard.write_primary(contents); + } + }, command::Action::Window(action) => match action { window::Action::Close(_id) => { *should_exit = true; diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 8f5c5e63..a1496fa3 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -52,6 +52,32 @@ impl Clipboard { State::Unavailable => {} } } + + /// Reads the current content of primary as text. + pub fn read_primary(&self) -> Option { + match &self.state { + State::Connected(clipboard) => { + clipboard.read_primary().and_then(|r| r.ok()) + } + State::Unavailable => None, + } + } + + /// Writes the given text contents to primary. + pub fn write_primary(&mut self, contents: String) { + match &mut self.state { + State::Connected(clipboard) => { + match clipboard.write_primary(contents) { + Some(Ok(())) => {} + Some(Err(error)) => { + log::warn!("error writing to primary: {error}"); + } + None => {} + } + } + State::Unavailable => {} + } + } } impl crate::core::Clipboard for Clipboard { @@ -62,4 +88,12 @@ impl crate::core::Clipboard for Clipboard { fn write(&mut self, contents: String) { self.write(contents); } + + fn read_primary(&self) -> Option { + self.read_primary() + } + + fn write_primary(&mut self, contents: String) { + self.write_primary(contents); + } } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 23b2f3c4..33f521c4 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -887,6 +887,18 @@ fn run_command( clipboard.write(contents); } }, + command::Action::ClipboardPrimary(action) => match action { + clipboard::Action::Read(tag) => { + let message = tag(clipboard.read_primary()); + + proxy + .send_event(message) + .expect("Send message to event loop"); + } + clipboard::Action::Write(contents) => { + clipboard.write_primary(contents); + } + }, command::Action::Window(action) => match action { window::Action::Spawn(id, settings) => { let monitor = window_manager.last_monitor(); -- cgit From 508b3fe1f1405bdb8b860d0d63e2c7adfbbd51ca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Feb 2024 03:14:08 +0100 Subject: Introduce `Kind` in `core::clipboard` --- Cargo.toml | 2 +- core/src/clipboard.rs | 29 ++++++++++---------- runtime/src/clipboard.rs | 49 +++++++++++++++++++++------------ runtime/src/command/action.rs | 9 ------ widget/src/text_editor.rs | 11 +++++--- widget/src/text_input.rs | 19 ++++++++----- winit/src/application.rs | 20 +++----------- winit/src/clipboard.rs | 64 +++++++++++++++---------------------------- winit/src/multi_window.rs | 20 +++----------- 9 files changed, 95 insertions(+), 128 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de52cd80..ef7eae61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,5 +160,5 @@ web-sys = "=0.3.67" web-time = "0.2" wgpu = "0.19" winapi = "0.3" -window_clipboard = "0.4" +window_clipboard = "0.4.1" winit = { git = "https://github.com/iced-rs/winit.git", rev = "b91e39ece2c0d378c3b80da7f3ab50e17bb798a5" } diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index ff2e31d0..5df3e267 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -4,16 +4,21 @@ /// applications. pub trait Clipboard { /// Reads the current content of the [`Clipboard`] as text. - fn read(&self) -> Option; + fn read(&self, kind: Kind) -> Option; /// Writes the given text contents to the [`Clipboard`]. - fn write(&mut self, contents: String); - - /// Reads the current content of Primary as text. - fn read_primary(&self) -> Option; + fn write(&mut self, kind: Kind, contents: String); +} - /// Writes the given text contents to Primary. - fn write_primary(&mut self, contents: String); +/// The kind of [`Clipboard`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Kind { + /// The standard clipboard. + Standard, + /// The primary clipboard. + /// + /// Normally only present in X11 and Wayland. + Primary, } /// A null implementation of the [`Clipboard`] trait. @@ -21,15 +26,9 @@ pub trait Clipboard { pub struct Null; impl Clipboard for Null { - fn read(&self) -> Option { - None - } - - fn write(&mut self, _contents: String) {} - - fn read_primary(&self) -> Option { + fn read(&self, _kind: Kind) -> Option { None } - fn write_primary(&mut self, _contents: String) {} + fn write(&mut self, _kind: Kind, _contents: String) {} } diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index 49044a0f..dd47c47d 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,5 +1,6 @@ //! Access the clipboard. use crate::command::{self, Command}; +use crate::core::clipboard::Kind; use crate::futures::MaybeSend; use std::fmt; @@ -9,10 +10,10 @@ use std::fmt; /// [`Command`]: crate::Command pub enum Action { /// Read the clipboard and produce `T` with the result. - Read(Box) -> T>), + Read(Box) -> T>, Kind), /// Write the given contents to the clipboard. - Write(String), + Write(String, Kind), } impl Action { @@ -25,8 +26,10 @@ impl Action { T: 'static, { match self { - Self::Read(o) => Action::Read(Box::new(move |s| f(o(s)))), - Self::Write(content) => Action::Write(content), + Self::Read(o, target) => { + Action::Read(Box::new(move |s| f(o(s))), target) + } + Self::Write(content, target) => Action::Write(content, target), } } } @@ -34,8 +37,8 @@ impl Action { impl fmt::Debug for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Read(_) => write!(f, "Action::Read"), - Self::Write(_) => write!(f, "Action::Write"), + Self::Read(_, target) => write!(f, "Action::Read{target:?}"), + Self::Write(_, target) => write!(f, "Action::Write({target:?})"), } } } @@ -44,24 +47,34 @@ impl fmt::Debug for Action { pub fn read( f: impl Fn(Option) -> Message + 'static, ) -> Command { - Command::single(command::Action::Clipboard(Action::Read(Box::new(f)))) + Command::single(command::Action::Clipboard(Action::Read( + Box::new(f), + Kind::Standard, + ))) } -/// Write the given contents to the clipboard. -pub fn write(contents: String) -> Command { - Command::single(command::Action::Clipboard(Action::Write(contents))) -} - -/// Read the current contents of primary. +/// Read the current contents of the primary clipboard. pub fn read_primary( f: impl Fn(Option) -> Message + 'static, ) -> Command { - Command::single(command::Action::ClipboardPrimary(Action::Read(Box::new( - f, - )))) + Command::single(command::Action::Clipboard(Action::Read( + Box::new(f), + Kind::Primary, + ))) +} + +/// Write the given contents to the clipboard. +pub fn write(contents: String) -> Command { + Command::single(command::Action::Clipboard(Action::Write( + contents, + Kind::Standard, + ))) } -/// Write the given contents to primary. +/// Write the given contents to the primary clipboard. pub fn write_primary(contents: String) -> Command { - Command::single(command::Action::ClipboardPrimary(Action::Write(contents))) + Command::single(command::Action::Clipboard(Action::Write( + contents, + Kind::Primary, + ))) } diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs index f04c642c..c9ffe801 100644 --- a/runtime/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -26,9 +26,6 @@ pub enum Action { /// Run a clipboard action. Clipboard(clipboard::Action), - /// Run a clipboard action on primary. - ClipboardPrimary(clipboard::Action), - /// Run a window action. Window(window::Action), @@ -69,9 +66,6 @@ impl Action { Self::Future(future) => Action::Future(Box::pin(future.map(f))), Self::Stream(stream) => Action::Stream(Box::pin(stream.map(f))), Self::Clipboard(action) => Action::Clipboard(action.map(f)), - Self::ClipboardPrimary(action) => { - Action::ClipboardPrimary(action.map(f)) - } Self::Window(window) => Action::Window(window.map(f)), Self::System(system) => Action::System(system.map(f)), Self::Widget(operation) => { @@ -94,9 +88,6 @@ impl fmt::Debug for Action { Self::Clipboard(action) => { write!(f, "Action::Clipboard({action:?})") } - Self::ClipboardPrimary(action) => { - write!(f, "Action::ClipboardPrimary({action:?})") - } Self::Window(action) => { write!(f, "Action::Window({action:?})") } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index cbcab1eb..50f7fabb 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -1,4 +1,5 @@ //! Display a multi-line text input for text editing. +use crate::core::clipboard::{self, Clipboard}; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; @@ -10,7 +11,7 @@ use crate::core::text::highlighter::{self, Highlighter}; use crate::core::text::{self, LineHeight}; use crate::core::widget::{self, Widget}; use crate::core::{ - Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, + Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, }; use std::cell::RefCell; @@ -448,17 +449,19 @@ where } Update::Copy => { if let Some(selection) = self.content.selection() { - clipboard.write(selection); + clipboard.write(clipboard::Kind::Standard, selection); } } Update::Cut => { if let Some(selection) = self.content.selection() { - clipboard.write(selection.clone()); + clipboard.write(clipboard::Kind::Standard, selection); shell.publish(on_edit(Action::Edit(Edit::Delete))); } } Update::Paste => { - if let Some(contents) = clipboard.read() { + if let Some(contents) = + clipboard.read(clipboard::Kind::Standard) + { shell.publish(on_edit(Action::Edit(Edit::Paste( Arc::new(contents), )))); diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 0a7ed014..f5b57422 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -12,6 +12,7 @@ pub use value::Value; use editor::Editor; use crate::core::alignment; +use crate::core::clipboard::{self, Clipboard}; use crate::core::event::{self, Event}; use crate::core::keyboard; use crate::core::keyboard::key; @@ -26,8 +27,8 @@ use crate::core::widget::operation::{self, Operation}; use crate::core::widget::tree::{self, Tree}; use crate::core::window; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, - Shell, Size, Vector, Widget, + Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size, + Vector, Widget, }; use crate::runtime::Command; @@ -864,8 +865,10 @@ where if let Some((start, end)) = state.cursor.selection(value) { - clipboard - .write(value.select(start, end).to_string()); + clipboard.write( + clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); } } keyboard::Key::Character("x") @@ -874,8 +877,10 @@ where if let Some((start, end)) = state.cursor.selection(value) { - clipboard - .write(value.select(start, end).to_string()); + clipboard.write( + clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); } let mut editor = Editor::new(value, &mut state.cursor); @@ -894,7 +899,7 @@ where Some(content) => content, None => { let content: String = clipboard - .read() + .read(clipboard::Kind::Standard) .unwrap_or_default() .chars() .filter(|c| !c.is_control()) diff --git a/winit/src/application.rs b/winit/src/application.rs index 2d3ac52c..0fc67adc 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -704,27 +704,15 @@ pub fn run_command( runtime.run(stream); } command::Action::Clipboard(action) => match action { - clipboard::Action::Read(tag) => { - let message = tag(clipboard.read()); + clipboard::Action::Read(tag, kind) => { + let message = tag(clipboard.read(kind)); proxy .send_event(message) .expect("Send message to event loop"); } - clipboard::Action::Write(contents) => { - clipboard.write(contents); - } - }, - command::Action::ClipboardPrimary(action) => match action { - clipboard::Action::Read(tag) => { - let message = tag(clipboard.read_primary()); - - proxy - .send_event(message) - .expect("Send message to event loop"); - } - clipboard::Action::Write(contents) => { - clipboard.write_primary(contents); + clipboard::Action::Write(contents, kind) => { + clipboard.write(kind, contents); } }, command::Action::Window(action) => match action { diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index a1496fa3..5237ca01 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,5 +1,7 @@ //! Access the clipboard. +use crate::core::clipboard::Kind; + /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] @@ -33,46 +35,32 @@ impl Clipboard { } /// Reads the current content of the [`Clipboard`] as text. - pub fn read(&self) -> Option { + pub fn read(&self, kind: Kind) -> Option { match &self.state { - State::Connected(clipboard) => clipboard.read().ok(), + State::Connected(clipboard) => match kind { + Kind::Standard => clipboard.read().ok(), + Kind::Primary => clipboard.read_primary().and_then(Result::ok), + }, State::Unavailable => None, } } /// Writes the given text contents to the [`Clipboard`]. - pub fn write(&mut self, contents: String) { + pub fn write(&mut self, kind: Kind, contents: String) { match &mut self.state { - State::Connected(clipboard) => match clipboard.write(contents) { - Ok(()) => {} - Err(error) => { - log::warn!("error writing to clipboard: {error}"); - } - }, - State::Unavailable => {} - } - } - - /// Reads the current content of primary as text. - pub fn read_primary(&self) -> Option { - match &self.state { State::Connected(clipboard) => { - clipboard.read_primary().and_then(|r| r.ok()) - } - State::Unavailable => None, - } - } + let result = match kind { + Kind::Standard => clipboard.write(contents), + Kind::Primary => { + clipboard.write_primary(contents).unwrap_or(Ok(())) + } + }; - /// Writes the given text contents to primary. - pub fn write_primary(&mut self, contents: String) { - match &mut self.state { - State::Connected(clipboard) => { - match clipboard.write_primary(contents) { - Some(Ok(())) => {} - Some(Err(error)) => { - log::warn!("error writing to primary: {error}"); + match result { + Ok(()) => {} + Err(error) => { + log::warn!("error writing to clipboard: {error}"); } - None => {} } } State::Unavailable => {} @@ -81,19 +69,11 @@ impl Clipboard { } impl crate::core::Clipboard for Clipboard { - fn read(&self) -> Option { - self.read() - } - - fn write(&mut self, contents: String) { - self.write(contents); - } - - fn read_primary(&self) -> Option { - self.read_primary() + fn read(&self, kind: Kind) -> Option { + self.read(kind) } - fn write_primary(&mut self, contents: String) { - self.write_primary(contents); + fn write(&mut self, kind: Kind, contents: String) { + self.write(kind, contents); } } diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 33f521c4..ed00abdf 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -876,27 +876,15 @@ fn run_command( runtime.run(Box::pin(stream)); } command::Action::Clipboard(action) => match action { - clipboard::Action::Read(tag) => { - let message = tag(clipboard.read()); + clipboard::Action::Read(tag, kind) => { + let message = tag(clipboard.read(kind)); proxy .send_event(message) .expect("Send message to event loop"); } - clipboard::Action::Write(contents) => { - clipboard.write(contents); - } - }, - command::Action::ClipboardPrimary(action) => match action { - clipboard::Action::Read(tag) => { - let message = tag(clipboard.read_primary()); - - proxy - .send_event(message) - .expect("Send message to event loop"); - } - clipboard::Action::Write(contents) => { - clipboard.write_primary(contents); + clipboard::Action::Write(contents, kind) => { + clipboard.write(kind, contents); } }, command::Action::Window(action) => match action { -- cgit From 3ac2902f689fce8feaf86703b25535a93aa95209 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 13 Feb 2024 03:18:08 +0100 Subject: Update `CHANGELOG` --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 317bfc8f..816ea89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Themer` widget. [#2209](https://github.com/iced-rs/iced/pull/2209) - `Transform` primitive. [#2120](https://github.com/iced-rs/iced/pull/2120) - Cut functionality for `TextEditor`. [#2215](https://github.com/iced-rs/iced/pull/2215) -- Disabled support for `Checkbox`. [#2109](https://github.com/iced-rs/iced/pull/2109) +- Primary clipboard support. [#2240](https://github.com/iced-rs/iced/pull/2240) +- Disabled state for `Checkbox`. [#2109](https://github.com/iced-rs/iced/pull/2109) - `skip_taskbar` window setting for Windows. [#2211](https://github.com/iced-rs/iced/pull/2211) - `fetch_maximized` and `fetch_minimized` commands in `window`. [#2189](https://github.com/iced-rs/iced/pull/2189) - `run_with_handle` command in `window`. [#2200](https://github.com/iced-rs/iced/pull/2200) @@ -150,6 +151,7 @@ Many thanks to... - @nyurik - @Remmirad - @ripytide +- @snaggen - @Tahinli - @tarkah - @tzemanovic -- cgit