From 89f1d2aaff843e3e8f0fa5144c511f9d52626dbc Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 8 Nov 2023 14:15:34 -0500 Subject: [PATCH] refactor: use iced clipboard for interacting with the selection --- .../platform_specific/wayland/data_device.rs | 22 ----- sctk/src/application.rs | 26 +++-- sctk/src/commands/data_device.rs | 39 -------- sctk/src/event_loop/mod.rs | 93 +---------------- sctk/src/event_loop/state.rs | 33 +------ sctk/src/handlers/data_device/data_device.rs | 25 +---- sctk/src/handlers/data_device/data_source.rs | 99 ++----------------- sctk/src/sctk_event.rs | 46 --------- 8 files changed, 34 insertions(+), 349 deletions(-) diff --git a/runtime/src/command/platform_specific/wayland/data_device.rs b/runtime/src/command/platform_specific/wayland/data_device.rs index 3bf76c3283..5a85eefcaf 100644 --- a/runtime/src/command/platform_specific/wayland/data_device.rs +++ b/runtime/src/command/platform_specific/wayland/data_device.rs @@ -34,21 +34,6 @@ pub trait DataFromMimeType { /// DataDevice Action pub enum ActionInner { - /// Indicate that you are setting the selection and will respond to events - /// with data of the advertised mime types. - SetSelection { - /// The mime types that the selection can be converted to. - mime_types: Vec, - /// The data to send. - data: Box, - }, - /// Unset the selection. - UnsetSelection, - /// Request the selection data from the clipboard. - RequestSelectionData { - /// The mime type that the selection should be converted to. - mime_type: String, - }, /// Start a drag and drop operation. When a client asks for the selection, an event will be delivered /// This is used for internal drags, where the client is the source of the drag. /// The client will be resposible for data transfer. @@ -116,13 +101,6 @@ impl fmt::Debug for ActionInner { Self::Accept(mime_type) => { f.debug_tuple("Accept").field(mime_type).finish() } - Self::SetSelection { mime_types, .. } => { - f.debug_tuple("SetSelection").field(mime_types).finish() - } - Self::UnsetSelection => f.debug_tuple("UnsetSelection").finish(), - Self::RequestSelectionData { mime_type } => { - f.debug_tuple("RequestSelection").field(mime_type).finish() - } Self::StartInternalDnd { origin_id, icon_id } => f .debug_tuple("StartInternalDnd") .field(origin_id) diff --git a/sctk/src/application.rs b/sctk/src/application.rs index 98958705cc..dd83176177 100644 --- a/sctk/src/application.rs +++ b/sctk/src/application.rs @@ -368,6 +368,7 @@ where let mut states: HashMap> = HashMap::new(); let mut interfaces = ManuallyDrop::new(HashMap::new()); + let mut simple_clipboard = Clipboard::unconnected(); { run_command( @@ -382,6 +383,7 @@ where || compositor.fetch_information(), &mut auto_size_surfaces, &mut Vec::new(), + &mut simple_clipboard, ); } runtime.track( @@ -419,7 +421,6 @@ where let mut mods: Modifiers = Modifiers::default(); let mut destroyed_surface_ids: HashMap = Default::default(); - let mut simple_clipboard = Clipboard::unconnected(); 'main: while let Some(event) = receiver.next().await { match event { @@ -937,6 +938,7 @@ where &mut Vec::new(), || compositor.fetch_information(), &mut auto_size_surfaces, + &mut simple_clipboard, ); interfaces = ManuallyDrop::new(build_user_interfaces( @@ -1101,6 +1103,7 @@ where &mut actions, || compositor.fetch_information(), &mut auto_size_surfaces, + &mut simple_clipboard, ); pure_states.insert(surface_id.inner(), cache); @@ -1789,6 +1792,7 @@ pub(crate) fn update( SurfaceIdWrapper, (u32, u32, Limits, bool), >, + clipboard: &mut Clipboard, ) where A: Application + 'static, E: Executor + 'static, @@ -1808,6 +1812,7 @@ pub(crate) fn update( debug, graphics_info, auto_size_surfaces, + clipboard, ) { actions.push(a); } @@ -1831,6 +1836,7 @@ pub(crate) fn update( graphics_info, auto_size_surfaces, actions, + clipboard, ) } @@ -1860,6 +1866,7 @@ fn run_command( (u32, u32, Limits, bool), >, actions: &mut Vec>, + clipboard: &mut Clipboard, ) where A: Application, E: Executor, @@ -1877,6 +1884,7 @@ fn run_command( debug, graphics_info, auto_size_surfaces, + clipboard, ) { actions.push(a); } @@ -1897,6 +1905,7 @@ fn handle_actions( SurfaceIdWrapper, (u32, u32, Limits, bool), >, + clipboard: &mut Clipboard, ) -> Option> where A: Application, @@ -1911,11 +1920,17 @@ where }))); } command::Action::Clipboard(action) => match action { - clipboard::Action::Read(..) => { - todo!(); + clipboard::Action::Read(s_to_msg) => { + if matches!(clipboard.state, crate::clipboard::State::Connected(_)) { + let contents = clipboard.read(); + let message = s_to_msg(contents); + proxy.send_event(Event::Message(message)); + } } - clipboard::Action::Write(..) => { - todo!(); + clipboard::Action::Write(contents) => { + if matches!(clipboard.state, crate::clipboard::State::Connected(_)) { + clipboard.write(contents) + } } }, command::Action::Window(..) => { @@ -2142,7 +2157,6 @@ fn event_is_for_surface( | SctkEvent::RemovedOutput(_) => false, SctkEvent::ScaleFactorChanged { id, .. } => &id.id() == object_id, SctkEvent::DndOffer { surface, .. } => &surface.id() == object_id, - SctkEvent::SelectionOffer(_) => true, SctkEvent::DataSource(_) => true, } } diff --git a/sctk/src/commands/data_device.rs b/sctk/src/commands/data_device.rs index df63b855d7..b009dca473 100644 --- a/sctk/src/commands/data_device.rs +++ b/sctk/src/commands/data_device.rs @@ -15,45 +15,6 @@ use iced_runtime::{ }; use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; -/// Set the selection. When a client asks for the selection, an event will be delivered to the -/// client with the fd to write the data to. -pub fn set_selection( - mime_types: Vec, - data: Box, -) -> Command { - Command::single(command::Action::PlatformSpecific( - platform_specific::Action::Wayland(wayland::Action::DataDevice( - wayland::data_device::ActionInner::SetSelection { - mime_types, - data, - } - .into(), - )), - )) -} - -/// unset the selection -pub fn unset_selection() -> Command { - Command::single(command::Action::PlatformSpecific( - platform_specific::Action::Wayland(wayland::Action::DataDevice( - wayland::data_device::ActionInner::UnsetSelection.into(), - )), - )) -} - -/// request the selection -/// This will trigger an event with a read pipe to read the data from. -pub fn request_selection(mime_type: String) -> Command { - Command::single(command::Action::PlatformSpecific( - platform_specific::Action::Wayland(wayland::Action::DataDevice( - wayland::data_device::ActionInner::RequestSelectionData { - mime_type, - } - .into(), - )), - )) -} - /// start an internal drag and drop operation. Events will only be delivered to the same client. /// The client is responsible for data transfer. pub fn start_internal_drag( diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 2ea07963a0..79f94e29fb 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -17,8 +17,7 @@ use crate::{ }, sctk_event::{ DndOfferEvent, IcedSctkEvent, LayerSurfaceEventVariant, - PopupEventVariant, SctkEvent, SelectionOfferEvent, StartCause, - WindowEventVariant, + PopupEventVariant, SctkEvent, StartCause, WindowEventVariant, }, settings, }; @@ -65,7 +64,7 @@ use wayland_backend::client::WaylandError; use self::{ control_flow::ControlFlow, - state::{Dnd, LayerSurfaceCreationError, SctkCopyPasteSource, SctkState}, + state::{Dnd, LayerSurfaceCreationError, SctkState}, }; #[derive(Debug, Default, Clone, Copy)] @@ -199,10 +198,8 @@ where frame_events: Vec::new(), pending_user_events: Vec::new(), token_ctr: 0, - selection_source: None, _accept_counter: 0, dnd_offer: None, - selection_offer: None, fractional_scaling_manager, viewporter_state, compositor_updates: Default::default(), @@ -1208,92 +1205,6 @@ where }; } } - platform_specific::wayland::data_device::ActionInner::RequestSelectionData { mime_type } => { - if let Some(selection_offer) = self.state.selection_offer.as_mut() { - let read_pipe = match selection_offer.offer.receive(mime_type.clone()) { - Ok(p) => p, - Err(_) => continue, // TODO error handling - }; - let loop_handle = self.event_loop.handle(); - match self.event_loop.handle().insert_source(read_pipe, move |_, f, state| { - let selection_offer = match state.selection_offer.as_mut() { - Some(s) => s, - None => return PostAction::Continue, - }; - let (mime_type, data, token) = match selection_offer.cur_read.take() { - Some(s) => s, - None => return PostAction::Continue, - }; - let mut reader = BufReader::new(f.as_ref()); - let consumed = match reader.fill_buf() { - Ok(buf) => { - if buf.is_empty() { - loop_handle.remove(token); - state.sctk_events.push(SctkEvent::SelectionOffer(SelectionOfferEvent::Data {mime_type, data })); - } else { - let mut data = data; - data.extend_from_slice(buf); - selection_offer.cur_read = Some((mime_type, data, token)); - } - buf.len() - }, - Err(e) if matches!(e.kind(), std::io::ErrorKind::Interrupted) => { - selection_offer.cur_read = Some((mime_type, data, token)); - return PostAction::Continue; - }, - Err(e) => { - error!("Error reading selection data: {}", e); - return PostAction::Continue; - }, - }; - reader.consume(consumed); - PostAction::Continue - }) { - Ok(token) => { - selection_offer.cur_read = Some((mime_type.clone(), Vec::new(), token)); - }, - Err(_) => continue, - }; - } - } - platform_specific::wayland::data_device::ActionInner::SetSelection { mime_types, data } => { - let qh = &self.state.queue_handle.clone(); - let seat = match self.state.seats.get(0) { - Some(s) => s, - None => continue, - }; - let serial = match seat.last_ptr_press { - Some(s) => s.2, - None => continue, - }; - // remove the old selection - self.state.selection_source = None; - // create a new one - let source = self - .state - .data_device_manager_state - .create_copy_paste_source(qh, mime_types.iter().map(|s| s.as_str()).collect::>()); - source.set_selection(&seat.data_device, serial); - self.state.selection_source = Some(SctkCopyPasteSource { - source, - cur_write: None, - accepted_mime_types: Vec::new(), - pipe: None, - data, - }); - } - platform_specific::wayland::data_device::ActionInner::UnsetSelection => { - let seat = match self.state.seats.get(0) { - Some(s) => s, - None => continue, - }; - let serial = match seat.last_ptr_press { - Some(s) => s.2, - None => continue, - }; - self.state.selection_source = None; - seat.data_device.unset_selection(serial); - } platform_specific::wayland::data_device::ActionInner::SetActions { preferred, accepted } => { if let Some(offer) = self.state.dnd_offer.as_ref() { offer.offer.set_actions(accepted, preferred); diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index 04db9b3ca6..a39d284fac 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -33,10 +33,8 @@ use sctk::{ activation::ActivationState, compositor::CompositorState, data_device_manager::{ - data_device::DataDevice, - data_offer::{DragOffer, SelectionOffer}, - data_source::{CopyPasteSource, DragSource}, - DataDeviceManagerState, WritePipe, + data_device::DataDevice, data_offer::DragOffer, + data_source::DragSource, DataDeviceManagerState, WritePipe, }, error::GlobalError, output::OutputState, @@ -233,12 +231,6 @@ impl Debug for Dnd { } } -#[derive(Debug)] -pub struct SctkSelectionOffer { - pub(crate) offer: SelectionOffer, - pub(crate) cur_read: Option<(String, Vec, RegistrationToken)>, -} - #[derive(Debug)] pub struct SctkDragOffer { pub(crate) dropped: bool, @@ -254,25 +246,6 @@ pub struct SctkPopupData { pub(crate) positioner: XdgPositioner, } -pub struct SctkCopyPasteSource { - pub accepted_mime_types: Vec, - pub source: CopyPasteSource, - pub data: Box, - pub(crate) pipe: Option, - pub(crate) cur_write: Option<(Vec, usize, RegistrationToken)>, -} - -impl Debug for SctkCopyPasteSource { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SctkCopyPasteSource") - .field(&self.accepted_mime_types) - .field(&self.source) - .field(&self.pipe) - .field(&self.cur_write) - .finish() - } -} - /// Wrapper to carry sctk state. pub struct SctkState { /// the cursor wl_surface @@ -302,9 +275,7 @@ pub struct SctkState { pub compositor_updates: Vec, /// data data_device - pub(crate) selection_source: Option, pub(crate) dnd_offer: Option, - pub(crate) selection_offer: Option, pub(crate) _accept_counter: u32, /// A sink for window and device events that is being filled during dispatching /// event loop and forwarded downstream afterwards. diff --git a/sctk/src/handlers/data_device/data_device.rs b/sctk/src/handlers/data_device/data_device.rs index 0af933207f..5eb4b36747 100644 --- a/sctk/src/handlers/data_device/data_device.rs +++ b/sctk/src/handlers/data_device/data_device.rs @@ -6,7 +6,7 @@ use sctk::{ }; use crate::{ - event_loop::state::{SctkDragOffer, SctkSelectionOffer, SctkState}, + event_loop::state::{SctkDragOffer, SctkState}, sctk_event::{DndOfferEvent, SctkEvent}, }; @@ -100,28 +100,9 @@ impl DataDeviceHandler for SctkState { &mut self, _conn: &Connection, _qh: &QueueHandle, - wl_data_device: &wl_data_device::WlDataDevice, + _wl_data_device: &wl_data_device::WlDataDevice, ) { - let data_device = if let Some(seat) = self - .seats - .iter() - .find(|s| s.data_device.inner() == wl_data_device) - { - &seat.data_device - } else { - return; - }; - - if let Some(offer) = data_device.data().selection_offer() { - let mime_types = offer.with_mime_types(|types| types.to_vec()); - self.sctk_events.push(SctkEvent::SelectionOffer( - crate::sctk_event::SelectionOfferEvent::Offer(mime_types), - )); - self.selection_offer = Some(SctkSelectionOffer { - offer: offer.clone(), - cur_read: None, - }); - } + // not handled here } fn drop_performed( diff --git a/sctk/src/handlers/data_device/data_source.rs b/sctk/src/handlers/data_device/data_source.rs index 2ad4550218..834cccc483 100644 --- a/sctk/src/handlers/data_device/data_source.rs +++ b/sctk/src/handlers/data_device/data_source.rs @@ -45,97 +45,17 @@ impl DataSourceHandler for SctkState { pipe: WritePipe, ) { let is_active_source = self - .selection_source + .dnd_source .as_ref() - .map(|s| s.source.inner() == source) - .unwrap_or(false) - || self - .dnd_source - .as_ref() - .and_then(|s| s.source.as_ref().map(|s| s.0.inner() == source)) - .unwrap_or(false); + .and_then(|s| s.source.as_ref().map(|s| s.0.inner() == source)) + .unwrap_or(false); if !is_active_source { source.destroy(); return; } - if let Some(my_source) = self - .selection_source - .as_mut() - .filter(|s| s.source.inner() == source) - { - match self.loop_handle.insert_source( - pipe, - move |_, f, state| -> PostAction { - let selection_source = match state.selection_source.as_mut() - { - Some(s) => s, - None => { - return PostAction::Continue; - } - }; - let (data, mut cur_index, token) = - match selection_source.cur_write.take() { - Some(s) => s, - None => { - return PostAction::Continue; - } - }; - let mut writer = BufWriter::new(f.as_ref()); - let slice = &data.as_slice()[cur_index - ..(cur_index + writer.capacity()).min(data.len())]; - match writer.write(slice) { - Ok(num_written) => { - cur_index += num_written; - let done = cur_index == data.len(); - if !done { - selection_source.cur_write = - Some((data, cur_index, token)); - } - if let Err(err) = writer.flush() { - error!("Failed to flush pipe: {}", err); - return PostAction::Remove; - } - if done { - PostAction::Remove - } else { - PostAction::Continue - } - } - Err(e) - if matches!( - e.kind(), - std::io::ErrorKind::Interrupted - ) => - { - // try again - selection_source.cur_write = - Some((data, cur_index, token)); - PostAction::Continue - } - Err(_) => { - error!("Failed to write to pipe"); - PostAction::Remove - } - } - }, - ) { - Ok(s) => { - my_source.cur_write = Some(( - my_source - .data - .from_mime_type(&mime) - .unwrap_or_default(), - 0, - s, - )); - } - Err(err) => { - error!("Failed to insert source: {}", err); - } - } - } else if let Some(source) = self.dnd_source.as_mut().filter(|s| { + if let Some(source) = self.dnd_source.as_mut().filter(|s| { s.source .as_ref() .map(|s| (s.0.inner() == source)) @@ -214,15 +134,10 @@ impl DataSourceHandler for SctkState { source: &WlDataSource, ) { let is_active_source = self - .selection_source + .dnd_source .as_ref() - .map(|s| s.source.inner() == source) - .unwrap_or(false) - || self - .dnd_source - .as_ref() - .and_then(|s| s.source.as_ref().map(|s| s.0.inner() == source)) - .unwrap_or(false); + .and_then(|s| s.source.as_ref().map(|s| s.0.inner() == source)) + .unwrap_or(false); if is_active_source { self.sctk_events .push(SctkEvent::DataSource(DataSourceEvent::DndCancelled)); diff --git a/sctk/src/sctk_event.rs b/sctk/src/sctk_event.rs index ba10fed6b1..d75ada42ff 100755 --- a/sctk/src/sctk_event.rs +++ b/sctk/src/sctk_event.rs @@ -189,7 +189,6 @@ pub enum SctkEvent { event: DndOfferEvent, surface: WlSurface, }, - SelectionOffer(SelectionOfferEvent), } #[derive(Debug, Clone)] @@ -216,19 +215,6 @@ pub enum DataSourceEvent { }, } -#[derive(Debug, Clone)] -pub enum SelectionOfferEvent { - /// A Selection offer has been introduced with the given mime types. - Offer(Vec), - /// Read the selection data from the clipboard. - Data { - /// The raww data - data: Vec, - /// mime type of the data to read - mime_type: String, - }, -} - #[derive(Debug, Clone)] pub enum DndOfferEvent { /// A DnD offer has been introduced with the given mime types. @@ -857,38 +843,6 @@ impl SctkEvent { .collect() } }, - SctkEvent::SelectionOffer(so) => match so { - SelectionOfferEvent::Offer(mime_types) => { - Some(iced_runtime::core::Event::PlatformSpecific( - PlatformSpecific::Wayland( - wayland::Event::SelectionOffer( - wayland::SelectionOfferEvent::Offer( - mime_types - .iter() - .map(|m| m.to_string()) - .collect(), - ), - ), - ), - )) - .into_iter() - .collect() - } - SelectionOfferEvent::Data { mime_type, data } => { - Some(iced_runtime::core::Event::PlatformSpecific( - PlatformSpecific::Wayland( - wayland::Event::SelectionOffer( - wayland::SelectionOfferEvent::Data { - data, - mime_type, - }, - ), - ), - )) - .into_iter() - .collect() - } - }, SctkEvent::DataSource(event) => match event { DataSourceEvent::DndDropPerformed => { Some(iced_runtime::core::Event::PlatformSpecific(