diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 30665f99b..053546261 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -346,7 +346,7 @@ pub fn init_backend( // Create relative pointer global RelativePointerManagerState::new::(&dh); - state.launch_xwayland(Some(primary)); + // state.launch_xwayland(Some(primary)); for (dev, path) in udev_dispatcher.as_source_ref().device_list() { state diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 0fb26f6b8..031d6c0f1 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -259,7 +259,7 @@ pub fn init_backend( seats.iter().cloned(), &state.common.event_loop_handle, ); - state.launch_xwayland(None); + // state.launch_xwayland(None); Ok(()) } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 60595d768..42446d133 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -382,7 +382,7 @@ pub fn init_backend( seats.iter().cloned(), &state.common.event_loop_handle, ); - state.launch_xwayland(None); + // state.launch_xwayland(None); event_loop .handle() diff --git a/src/state.rs b/src/state.rs index 3597e341f..6a376be80 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,6 +10,7 @@ use crate::{ shell::{layout::floating::SeatMoveGrabState, Shell}, utils::prelude::*, wayland::protocols::{ + data_control, drm::WlDrmState, output_configuration::OutputConfigurationState, screencopy::{BufferParams, ScreencopyState, Session as ScreencopySession}, @@ -117,6 +118,7 @@ pub struct Common { pub output_configuration_state: OutputConfigurationState, pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, + pub data_control_state: data_control::State, pub screencopy_state: ScreencopyState, pub seat_state: SeatState, pub shm_state: ShmState, @@ -248,6 +250,7 @@ impl State { let output_configuration_state = OutputConfigurationState::new(dh, |_| true); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); + let data_control_state = data_control::State::new::(dh); let screencopy_state = ScreencopyState::new::( dh, vec![CursorMode::Embedded, CursorMode::Hidden], @@ -299,6 +302,7 @@ impl State { output_configuration_state, presentation_state, primary_selection_state, + data_control_state, viewporter_state, wl_drm_state, kde_decoration_state, diff --git a/src/wayland/handlers/data_control.rs b/src/wayland/handlers/data_control.rs new file mode 100644 index 000000000..2de8bf39d --- /dev/null +++ b/src/wayland/handlers/data_control.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use crate::wayland::protocols::data_control; +use smithay::xwayland::xwm::{SelectionType, XwmId}; +use tracing::warn; + +use std::os::unix::io::OwnedFd; + +impl data_control::Handler for State { + fn new_selection(&mut self, source: Option) { + if let Some(state) = self.common.xwayland_state.as_mut() { + if let Some(xwm) = state.xwm.as_mut() { + if let Some(source) = &source { + if let Ok(Err(err)) = + data_control::source::with_source_metadata(source, |metadata| { + xwm.new_selection( + SelectionType::Clipboard, + Some(metadata.mime_types.clone()), + ) + }) + { + warn!(?err, "Failed to set Xwayland primary selection"); + } + } else if let Err(err) = xwm.new_selection(SelectionType::Clipboard, None) { + warn!(?err, "Failed to clear Xwayland primary selection"); + } + } + } + } + + fn send_selection(&mut self, mime_type: String, fd: OwnedFd) { + if let Some(xwm) = self + .common + .xwayland_state + .as_mut() + .and_then(|xstate| xstate.xwm.as_mut()) + { + if let Err(err) = xwm.send_selection( + SelectionType::Clipboard, + mime_type, + fd, + self.common.event_loop_handle.clone(), + ) { + warn!(?err, "Failed to send primary selection (X11 -> Wayland)."); + } + } + } +} + +data_control::delegate!(State); diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 56e4a9bea..2055faf18 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -2,6 +2,7 @@ pub mod buffer; pub mod compositor; +pub mod data_control; pub mod data_device; pub mod decoration; pub mod dmabuf; diff --git a/src/wayland/handlers/seat.rs b/src/wayland/handlers/seat.rs index 1af64bb7b..9212f4c17 100644 --- a/src/wayland/handlers/seat.rs +++ b/src/wayland/handlers/seat.rs @@ -3,6 +3,7 @@ use crate::{ shell::focus::target::{KeyboardFocusTarget, PointerFocusTarget}, state::State, + wayland::protocols::data_control, }; use smithay::{ delegate_seat, @@ -46,7 +47,8 @@ impl SeatHandler for State { .and_then(|s| dh.get_client(s.id()).ok()) { set_data_device_focus(dh, seat, Some(client.clone())); - set_primary_focus(dh, seat, Some(client)) + set_primary_focus(dh, seat, Some(client.clone())); + data_control::set_primary_focus(dh, seat, Some(client)) } } } diff --git a/src/wayland/protocols/data_control/device.rs b/src/wayland/protocols/data_control/device.rs new file mode 100644 index 000000000..77bd37093 --- /dev/null +++ b/src/wayland/protocols/data_control/device.rs @@ -0,0 +1,79 @@ +pub use super::server::zwlr_data_control_device_v1::{Request, ZwlrDataControlDeviceV1 as Device}; + +use std::cell::RefCell; + +use smithay::reexports::wayland_server::{ + protocol::wl_seat::WlSeat, Client, DataInit, Dispatch, DisplayHandle, Resource, +}; +use tracing::debug; + +use smithay::{ + input::{Seat, SeatHandler}, + wayland::seat::WaylandFocus, +}; + +use super::{source, Handler, Offer, SeatData, Selection, Source, State}; + +#[derive(Debug)] +pub struct Data { + wl_seat: WlSeat, +} + +impl State { + /// Registers a new [`Device`] created by the client. + pub(super) fn register_device( + &mut self, + new: New, + wl_seat: WlSeat, + di: DataInit<'_, D>, + ) -> Device { + let device = di.init(new, ()); + let _ = self.devices.insert(device.id(), Data { wl_seat }); + device + } +} + +impl Dispatch for State +where + D: Dispatch + Dispatch + Dispatch, + D: Handler, + D: SeatHandler, + ::KeyboardFocus: WaylandFocus, + D: 'static, +{ + fn request( + handler: &mut D, + client: &Client, + resource: &Device, + request: Request, + data: &Data, + dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + Request::SetSelection { source } => { + debug!(source, "set_selection"); + + handler.new_selection(source.clone()); + seat_data.borrow_mut().set_selection::( + dh, + source.map(Selection::Client).unwrap_or(Selection::Empty), + ); + } + Request::SetPrimarySelection { source } => { + debug!("set_primary_selection(source: {source:?})"); + } + Request::Destroy => { + debug!("destroy()"); + + // Clean up the known devices + seat.user_data() + .get::>() + .unwrap() + .borrow_mut() + .retain_devices(|ndd| ndd != resource) + } + _ => unreachable!(), + } + } +} diff --git a/src/wayland/protocols/data_control/manager.rs b/src/wayland/protocols/data_control/manager.rs new file mode 100644 index 000000000..22913d5a4 --- /dev/null +++ b/src/wayland/protocols/data_control/manager.rs @@ -0,0 +1,80 @@ +pub use super::server::zwlr_data_control_manager_v1::{ + Request, ZwlrDataControlManagerV1 as Manager, +}; + +use std::cell::RefCell; + +use smithay::reexports::wayland_server::{ + self, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, +}; +use tracing::{debug, error}; + +use smithay::input::{Seat, SeatHandler}; + +use super::{Handler, SeatData, State}; + +use super::{device, offer, source, Device, Offer, Source, State}; + +pub(super) fn new(dh: &DisplayHandle) -> GlobalId { + dh.create_global::(1, ()) +} + +impl GlobalDispatch for State +where + D: SeatHandler + GlobalDispatch, + D: Dispatch, + D: Dispatch, + D: Dispatch, + D: Handler, + D: 'static, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &wayland_server::Client, + resource: wayland_server::New, + _global_data: &(), + data_init: &mut wayland_server::DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } +} + +impl Dispatch for State +where + D: Dispatch, + D: Dispatch, + D: Dispatch, + D: Dispatch + Dispatch, + D: Handler, + D: SeatHandler, + D: 'static, +{ + fn request( + state: &mut D, + client: &wayland_server::Client, + _resource: &Manager, + request: Request, + _data: &(), + dhandle: &DisplayHandle, + data_init: &mut wayland_server::DataInit<'_, D>, + ) { + let state = state.data_control(); + match request { + Request::CreateDataSource { id } => { + debug!(id, "create_data_source"); + + state.register_source(id, data_init); + } + Request::GetDataDevice { id, seat } => { + debug!(id, seat, "get_data_device"); + + state.register_device(id, seat, data_init); + // seat_data.borrow_mut().add_device(device); + // seat_data.borrow_mut().send_selection::(dhandle); + } + Request::Destroy => debug!("destroy"), + _ => unreachable!(), + } + } +} diff --git a/src/wayland/protocols/data_control/mod.rs b/src/wayland/protocols/data_control/mod.rs new file mode 100644 index 000000000..0fd6f66cd --- /dev/null +++ b/src/wayland/protocols/data_control/mod.rs @@ -0,0 +1,323 @@ +use std::collections::HashMap; +use std::{cell::RefCell, sync::Arc}; + +use std::os::unix::io::{AsRawFd, OwnedFd}; + +use smithay::reexports::wayland_protocols_wlr::data_control::v1::server; +use smithay::reexports::wayland_server::Dispatch; +use smithay::reexports::wayland_server::{ + self, + backend::GlobalId, + backend::{protocol::Message, ClientId, Handle, ObjectData, ObjectId}, + Client, DisplayHandle, GlobalDispatch, Resource, +}; +use tracing::{debug, instrument}; + +use smithay::input::{Seat, SeatHandler}; + +pub use source::{with_source_metadata, Data, Metadata}; + +pub use self::{ + device::Device, + manager::Manager, + offer::Offer, + source::{Source, Sources}, +}; + +pub mod device; +pub mod manager; +pub mod offer; +pub mod source; + +// Manager -> Source +// -> Device -> Offer + + +/// Events that are generated by interactions of the clients with the data device +pub trait Handler: Sized { + fn data_control(&mut self) -> &mut State; + + /// A client has set the selection + #[allow(unused_variables)] + fn new_selection(&mut self, source: Option) {} + + /// A client requested to read the server-set selection + /// + /// * `mime_type` - the requested mime type + /// * `fd` - the fd to write into + #[allow(unused_variables)] + fn send_selection(&mut self, mime_type: String, fd: OwnedFd) {} +} + +#[derive(PartialEq, Eq)] +pub enum Selection { + Client(Source), + Compositor { mime_types: Vec }, +} + +pub struct Selection { + mime_types: Vec, +} + +pub struct State { + manager_global_id: GlobalId, + + devices: HashMap, + sources: HashMap, + + selection: Option, + primary_selection: Option, +} + +impl State { + pub fn new(display: &DisplayHandle) -> Self + where + D: GlobalDispatch + 'static, + { + Self { + manager_global_id: manager::new(dh), + devices: HashMap::new(), + sources: HashMap::new(), + selection: None, + primary_selection: None, + } + } + + fn clear_selection(&mut self, dh: &DisplayHandle) { + + } + + fn set_client_selection(&mut self, dh: &DisplayHandle, source: Source) + where + D: Handler + Dispatch + Dispatch, + D: 'static, + { + match &self.selection { + Some(Selection::Client(old)) if old == source => return, + Some(Selection::Client(old)) => self.emit_source_cancelled(old), + _ => {} + } + + let Some(data) = self.get_source_data(&source) else { + info!("Source is no longer valid (destroyed). Clearing selection."); + } + + if let Some(Selection::Client(source)) = self.selection { + self.emit_source_cancelled(&source); + }; + + self.selection = new_selection; + self.send_selection::(dh); + } + + fn broadcast_selection(&mut self, selection: Option) { + + for (dev, _) in self.devices.values() { + dev.selection() + } + } + + pub fn send_selection(&mut self, dh: &DisplayHandle) + where + D: Handler + Dispatch + Dispatch, + D: 'static, + { + // first sanitize the selection, reseting it to null if the client holding + // it dropped it + let cleanup = if let Selection::Client(ref source) = self.selection { + !source::is_alive(source) + } else { + false + }; + if cleanup { + self.selection = Selection::Empty; + } + + // then send it if appropriate + match self.selection { + Selection::Empty => { + // send an empty selection + for pd in &self.known_devices { + pd.selection(None); + } + } + Selection::Client(ref source) => { + for pd in &self.known_devices { + let client = match dh.get_client(pd.id()) { + Ok(c) => c, + Err(_) => continue, + }; + + // create a data offer + let offer = client + .create_resource::<_, _, D>(dh, pd.version(), source.clone()) + .unwrap(); + + // advertize the offer to the client + pd.data_offer(&offer); + with_source_metadata(source, |meta| { + for mime_type in meta.mime_types.iter().cloned() { + offer.offer(mime_type); + } + }) + .unwrap(); + pd.selection(Some(&offer)); + } + } + Selection::Compositor(ref meta) => { + for pd in &self.known_devices { + let client = match dh.get_client(pd.id()) { + Ok(c) => c, + Err(_) => continue, + }; + + // create a data offer + let offer = client + .create_resource::<_, _, D>(dh, pd.version(), meta.clone()) + .unwrap(); + + // advertize the offer to the client + pd.data_offer(&offer); + for mime_type in meta.mime_types.iter().cloned() { + offer.offer(mime_type); + } + pd.selection(Some(&offer)); + } + } + } + } +} + +/// Set the primary selection focus to a certain client for a given seat +#[instrument(name = "wayland_primary_selection", level = "debug", skip(dh, seat, client), fields(seat = seat.name(), client = ?client.as_ref().map(|c| c.id())))] +pub fn set_primary_focus(dh: &DisplayHandle, seat: &Seat, client: Option) +where + D: SeatHandler + + Handler + + Dispatch + + Dispatch + + 'static, +{ + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::new())); + let seat_data = seat.user_data().get::>().unwrap(); + seat_data.borrow_mut().set_focus::(dh, client); +} + +/// Set a compositor-provided primary selection for this seat +/// +/// You need to provide the available mime types for this selection. +/// +/// Whenever a client requests to read the selection, your callback will +/// receive a [`PrimarySelectionHandler::send_selection`] event. +#[instrument(name = "wayland_primary_selection", level = "debug", skip(dh, seat), fields(seat = seat.name()))] +pub fn set_primary_selection(dh: &DisplayHandle, seat: &Seat, mime_types: Vec) +where + D: SeatHandler + + Handler + + Dispatch + + Dispatch + + 'static, +{ + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::new())); + let seat_data = seat.user_data().get::>().unwrap(); + seat_data + .borrow_mut() + .set_selection::(dh, Selection::Compositor(Metadata { mime_types })); +} + +/// Errors happening when requesting selection contents +#[derive(Debug, thiserror::Error)] +pub enum SelectionRequestError { + /// Requested mime type is not available + #[error("Requested mime type is not available")] + InvalidMimetype, + /// Requesting server side selection contents is not supported + #[error("Current selection is server-side")] + ServerSideSelection, + /// There is no active selection + #[error("No active selection to query")] + NoSelection, +} + +/// Request the current data_control selection of the given seat +/// to be written to the provided file descriptor in the given mime type. +pub fn request_selection( + seat: &Seat, + mime_type: String, + fd: OwnedFd, +) -> Result<(), SelectionRequestError> +where + D: SeatHandler + Handler + 'static, +{ + tracing::error!("{mime_type} {fd:?}"); + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::new())); + let seat_data = seat.user_data().get::>().unwrap(); + match &seat_data.borrow().selection { + Selection::Client(source) => { + if !source + .data::() + .unwrap() + .supports_mime(&mime_type) + { + Err(SelectionRequestError::InvalidMimetype) + } else { + source.send(mime_type, fd.as_raw_fd()); + Ok(()) + } + } + Selection::Compositor(metadata) => { + if !metadata.supports_mime(&mime_type) { + Err(SelectionRequestError::InvalidMimetype) + } else { + Err(SelectionRequestError::ServerSideSelection) + } + } + Selection::Empty => Err(SelectionRequestError::NoSelection), + } +} + +pub struct SeatData { + known_devices: Vec, + selection: Selection, + current_focus: Option, +} + +impl Default for SeatData { + fn default() -> Self { + Self { + known_devices: Vec::new(), + selection: Selection::Empty, + current_focus: None, + } + } +} + +#[allow(missing_docs)] // TODO +#[macro_export] +macro_rules! delegate { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1: () + ] => $crate::wayland::protocols::data_control::State); + + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1: () + ] => $crate::wayland::protocols::data_control::State); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $crate::wayland::protocols::data_control::device::Data + ] => $crate::wayland::protocols::data_control::State); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $crate::wayland::protocols::data_control::source::Data + ] => $crate::wayland::protocols::data_control::State); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $crate::wayland::protocols::data_control::Source + ] => $crate::wayland::protocols::data_control::State); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $crate::wayland::protocols::data_control::source::Metadata + ] => $crate::wayland::protocols::data_control::State); + }; +} +pub(crate) use delegate; diff --git a/src/wayland/protocols/data_control/offer.rs b/src/wayland/protocols/data_control/offer.rs new file mode 100644 index 000000000..d9bb89f29 --- /dev/null +++ b/src/wayland/protocols/data_control/offer.rs @@ -0,0 +1,81 @@ +pub use super::server::zwlr_data_control_offer_v1::{Request, ZwlrDataControlOfferV1 as Offer}; +use super::{source, Handler, Source, State}; + +use std::sync::Arc; + +use smithay::reexports::wayland_server::{self, Client, Dispatch, DisplayHandle, Resource}; +use tracing::{debug, debug_span, error_span}; + +pub enum Data { + /// Offers selection from a native data source of this protocol. + NativeSource(Source), + + /// Offers selection from a source of an external origin. + ExternalSource { mime_types: Vec }, +} + +impl State { + pub(super) fn new_offer( + &mut self, + client: &Client, + version: u32, + mime_types: &[String], + ) -> Offer { + let offer = client.create_resource::<_, _, D>(dh, vesion, ()).unwrap(); + for mime_type in mime_types { + self.emit_offer(&offer, mime_type); + } + offer + } + + fn emit_offer(&self, offer: &Offer, mime_type: &str) { + offer.offer(mime_type.to_string()); + debug!(offer_id = offer.id(), event = offer, "emitted"); + } +} + +impl Dispatch for State { + fn request( + handler: &mut D, + _client: &wayland_server::Client, + offer: &Offer, + request: Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut wayland_server::DataInit<'_, D>, + ) { + let _span = warn_span!(offer_id = offer.id()).entered(); + + let state = handler.data_control(); + + match request { + Request::Receive { mime_type, fd } => { + let _span = warn_span(request = receive, mime_type).entered(); + let _span = debug_span!(fd).entered(); + + debug!("receive"); + + let Some(data) = state.offers.get(&offer.id()) else { + warn!("Offer no longer valid (destroyed)"); + return; + }; + + match data { + Data::NativeSource(source) => { + state.emit_source_send(source, mime_type, fd); + } + Data::ExternalSource { mime_types } if mime_type.contains(&mime_type) => { + handler.send_selection(mime_type, fd); + } + source @ Data::ExternalSource { .. } => { + warn!(source, "Unsupported mime type"); + } + }; + } + Request::Destroy => { + debug!("destroy"); + } + _ => unreachable!(), + } + } +} diff --git a/src/wayland/protocols/data_control/seat_data.rs b/src/wayland/protocols/data_control/seat_data.rs new file mode 100644 index 000000000..bf2e7d59d --- /dev/null +++ b/src/wayland/protocols/data_control/seat_data.rs @@ -0,0 +1,19 @@ +use std::{ + os::unix::io::{AsRawFd, OwnedFd}, + sync::Arc, +}; + +use tracing::debug; +use wayland_protocols_wlr::data_control::v1::server::{ + zwlr_data_control_device_v1::ZwlrDataControlDeviceV1 as Device, + zwlr_data_control_offer_v1::{self as offer, ZwlrDataControlOfferV1 as offer}, + zwlr_data_control_source_v1::ZwlrDataControlSourceV1 as Source, +}; +use wayland_server::{ + backend::{protocol::Message, ClientId, Handle, ObjectData, ObjectId}, + Client, DisplayHandle, Resource, +}; + +use crate::utils::IsAlive; + +use super::{with_source_metadata, Handler, Metadata}; diff --git a/src/wayland/protocols/data_control/source.rs b/src/wayland/protocols/data_control/source.rs new file mode 100644 index 000000000..6bdabdaf3 --- /dev/null +++ b/src/wayland/protocols/data_control/source.rs @@ -0,0 +1,119 @@ +pub use super::server::zwlr_data_control_source_v1::{Request, ZwlrDataControlSourceV1 as Source}; + +use std::collections::HashMap; +use std::sync::Mutex; +use std::{sync::atomic::AtomicBool, sync::atomic::Ordering}; + +use smithay::reexports::wayland_server::{ + self, + backend::{ClientId, ObjectId}, + Dispatch, DisplayHandle, Resource, +}; +use smithay::reexports::wayland_server::{DataInit, New}; +use smithay::{input::SeatHandler, utils::IsAlive}; +use tracing::{debug, warn_span, debug_span}; + +use super::{Handler, State}; + +#[derive(Default)] +pub struct Data { + mime_types: Vec, +} + +impl State { + /// Registers a new [`Source`] created by the client. + pub(super) fn register_source(&mut self, source: New, di: DataInit) -> Source { + let source = di.init::(new, ()); + let _ = self.sources.insert(source.id(), Data::default()); + source + } + + fn update_source(&mut self, source: &Source, f: impl FnMut(&mut Data)) { + self.sources + .get_mut(&source.id()) + .map(f) + .unwrap_or_else(|| warn!(id = source.id(), "Source doesn't exist")) + } + + + pub(super) fn get_source_data(&self, source: &Source) -> Option<&Data> { + self.sources.get(&source.id()) + } + + pub(super) fn emit_source_send(&self, source: &Source, mime_type: String, fd: Fd) { + let id = source.id(); + let _span = warn_span!(id, event = send, mime_type).entered(); + let _span = debug_span!(fd).entered(); + + let Some(data) = self.sources.get(&id) else { + warn!("Source is no longer valid (destroyed)"); + return; + }; + + if !data.mime_types.contains(&mime_type) { + warn!(supported_mime_types = &self.mime_types, "Unsupported mime type"); + return; + } + + source.send(mime_type, fd); + debug!("emitted"); + } + + pub(crate) fn emit_source_cancelled(&self, source: &Source) { + let _span = warn_span!(id = source.id(), event = cancelled).entered(); + + source.cancelled(); + debug!("emitted"); + } +} + + +impl Dispatch for State +where + D: SeatHandler + Dispatch, + D: Handler, + D: 'static, +{ + fn request( + handler: &mut D, + _client: &wayland_server::Client, + source: &Source, + request: Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut wayland_server::DataInit<'_, D>, + ) { + let state = handler.data_control(); + let source_id = source.id(); + + match request { + Request::Offer { mime_type } => { + let _span = debug_span!(source_id, request = offer, mime_type).entered(); + debug!("received"); + + state.update_source(source, |data| data.mime_types.push(mime)) + } + Request::Destroy => { + debug!(source_id, request = destroy, "received"); + + let _ = self.sources.remove(&id); + } + _ => unreachable!(), + } + } + + fn destroyed(state: &mut D, _client: ClientId, id: ObjectId, _: &()) { + // TODO: cancelled + } +} + +/// Access the metadata of a data source +// pub fn with_source_metadata T>( +// source: &Source, +// f: F, +// ) -> Result { +// match source.data::() { +// Some(data) => Ok(f(&data.inner.lock().unwrap())), +// None => Err(smithay::utils::UnmanagedResource), +// } +// } diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index e84f5ad17..398d38389 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -2,6 +2,7 @@ pub mod drm; //pub mod export_dmabuf; +pub mod data_control; pub mod output_configuration; pub mod screencopy; pub mod toplevel_info; diff --git a/src/xwayland.rs b/src/xwayland.rs index 5627ba4f5..a3e87764d 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -5,7 +5,10 @@ use crate::{ shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell}, state::{Data, State}, utils::prelude::*, - wayland::{handlers::screencopy::PendingScreencopyBuffers, protocols::screencopy::SessionType}, + wayland::{ + handlers::screencopy::PendingScreencopyBuffers, + protocols::{data_control, screencopy::SessionType}, + }, }; use smithay::{ backend::drm::DrmNode, @@ -459,7 +462,14 @@ impl XwmHandler for Data { let seat = self.state.common.last_active_seat(); match selection { SelectionType::Clipboard => { - if let Err(err) = request_data_device_client_selection(seat, mime_type, fd) { + // if let Err(err) = request_data_device_client_selection(seat, mime_type, fd) { + // error!( + // ?err, + // "Failed to request current wayland clipboard for Xwayland.", + // ); + // } + + if let Err(err) = data_control::request_selection(seat, mime_type, fd) { error!( ?err, "Failed to request current wayland clipboard for Xwayland.", @@ -467,10 +477,17 @@ impl XwmHandler for Data { } } SelectionType::Primary => { - if let Err(err) = request_primary_client_selection(seat, mime_type, fd) { + // if let Err(err) = request_primary_client_selection(seat, mime_type, fd) { + // error!( + // ?err, + // "Failed to request current wayland primary selection for Xwayland.", + // ); + // } + + if let Err(err) = data_control::request_selection(seat, mime_type, fd) { error!( ?err, - "Failed to request current wayland primary selection for Xwayland.", + "Failed to request current wayland clipboard for Xwayland.", ); } }