From e9927b28796cc8340f2c8ee2bcc89016bd3bc7e2 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 31 Mar 2024 01:36:45 +0100 Subject: [PATCH] wayland: implement wlr-data-control --- src/client/objects.rs | 6 +- src/globals.rs | 2 + src/ifs/ipc.rs | 52 ++- src/ifs/ipc/wl_data_source.rs | 14 +- src/ifs/ipc/x_data_source.rs | 28 +- src/ifs/ipc/zwlr_data_control_device_v1.rs | 306 ++++++++++++++++++ src/ifs/ipc/zwlr_data_control_manager_v1.rs | 145 +++++++++ src/ifs/ipc/zwlr_data_control_offer_v1.rs | 135 ++++++++ src/ifs/ipc/zwlr_data_control_source_v1.rs | 169 ++++++++++ .../ipc/zwp_primary_selection_source_v1.rs | 12 +- src/ifs/wl_seat.rs | 45 ++- src/ifs/wl_seat/event_handling.rs | 12 + src/utils/smallmap.rs | 4 + src/xwayland/xwm.rs | 6 +- wire/zwlr_data_control_device_v1.txt | 30 ++ wire/zwlr_data_control_manager_v1.txt | 14 + wire/zwlr_data_control_offer_v1.txt | 16 + wire/zwlr_data_control_source_v1.txt | 20 ++ 18 files changed, 983 insertions(+), 33 deletions(-) create mode 100644 src/ifs/ipc/zwlr_data_control_device_v1.rs create mode 100644 src/ifs/ipc/zwlr_data_control_manager_v1.rs create mode 100644 src/ifs/ipc/zwlr_data_control_offer_v1.rs create mode 100644 src/ifs/ipc/zwlr_data_control_source_v1.rs create mode 100644 wire/zwlr_data_control_device_v1.txt create mode 100644 wire/zwlr_data_control_manager_v1.txt create mode 100644 wire/zwlr_data_control_offer_v1.txt create mode 100644 wire/zwlr_data_control_source_v1.txt diff --git a/src/client/objects.rs b/src/client/objects.rs index 7e0df3ce..fa5d29e7 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -3,7 +3,7 @@ use { client::{Client, ClientError}, ifs::{ ipc::{ - wl_data_source::WlDataSource, + wl_data_source::WlDataSource, zwlr_data_control_source_v1::ZwlrDataControlSourceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }, jay_output::JayOutput, @@ -32,7 +32,7 @@ use { JayOutputId, JayScreencastId, JayWorkspaceId, WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, WlSurfaceId, WpLinuxDrmSyncobjTimelineV1Id, XdgPositionerId, XdgSurfaceId, XdgToplevelId, - XdgWmBaseId, ZwpPrimarySelectionSourceV1Id, + XdgWmBaseId, ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -59,6 +59,7 @@ pub struct Objects { pub seats: CopyHashMap>, pub screencasts: CopyHashMap>, pub timelines: CopyHashMap>, + pub zwlr_data_sources: CopyHashMap>, ids: RefCell>, } @@ -87,6 +88,7 @@ impl Objects { seats: Default::default(), screencasts: Default::default(), timelines: Default::default(), + zwlr_data_sources: Default::default(), ids: RefCell::new(vec![]), } } diff --git a/src/globals.rs b/src/globals.rs index dcc01c2f..aea66c17 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -8,6 +8,7 @@ use { ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ipc::{ wl_data_device_manager::WlDataDeviceManagerGlobal, + zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global, zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global, }, jay_compositor::JayCompositorGlobal, @@ -171,6 +172,7 @@ impl Globals { add_singleton!(ZwpIdleInhibitManagerV1Global); add_singleton!(ExtIdleNotifierV1Global); add_singleton!(XdgToplevelDragManagerV1Global); + add_singleton!(ZwlrDataControlManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/ipc.rs b/src/ifs/ipc.rs index 478d01a7..e7729437 100644 --- a/src/ifs/ipc.rs +++ b/src/ifs/ipc.rs @@ -3,7 +3,9 @@ use { client::{Client, ClientError, ClientId}, fixed::Fixed, ifs::{ - ipc::x_data_device::XIpcDevice, + ipc::{ + x_data_device::XIpcDevice, zwlr_data_control_device_v1::ZwlrDataControlDeviceV1, + }, wl_seat::{WlSeatError, WlSeatGlobal}, }, utils::{ @@ -31,6 +33,10 @@ pub mod wl_data_source; pub mod x_data_device; pub mod x_data_offer; pub mod x_data_source; +pub mod zwlr_data_control_device_v1; +pub mod zwlr_data_control_manager_v1; +pub mod zwlr_data_control_offer_v1; +pub mod zwlr_data_control_source_v1; pub mod zwp_primary_selection_device_manager_v1; pub mod zwp_primary_selection_device_v1; pub mod zwp_primary_selection_offer_v1; @@ -60,8 +66,9 @@ pub trait DynDataSource: 'static { fn send_send(&self, mime_type: &str, fd: Rc); fn offer_to_regular_client(self: Rc, client: &Rc); fn offer_to_x(self: Rc, dd: &Rc); + fn offer_to_wlr_device(self: Rc, dd: &Rc); fn detach_seat(&self, seat: &Rc); - fn cancel_offers(&self); + fn cancel_unprivileged_offers(&self); fn send_target(&self, mime_type: Option<&str>) { let _ = mime_type; @@ -98,6 +105,10 @@ pub trait DynDataOffer: 'static { fn cancel(&self); fn get_seat(&self) -> Rc; + fn is_privileged(&self) -> bool { + false + } + fn send_action(&self, action: u32) { let _ = action; log::warn!( @@ -129,6 +140,12 @@ pub trait IterableIpcVtable: IpcVtable { C: FnMut(&Rc); } +pub trait WlrIpcVtable: IpcVtable { + fn for_each_device(seat: &WlSeatGlobal, f: C) + where + C: FnMut(&Rc); +} + pub trait IpcVtable: Sized { const LOCATION: IpcLocation; @@ -277,11 +294,17 @@ pub fn attach_seat( Ok(()) } -pub fn cancel_offers(src: &S) { +pub fn cancel_offers(src: &S, cancel_privileged: bool) { let data = src.source_data(); - while let Some((_, offer)) = data.offers.pop() { - offer.cancel(); - } + let mut offers = data.offers.take(); + offers.retain(|o| { + let retain = !cancel_privileged && o.1.is_privileged(); + if !retain { + o.1.cancel(); + } + retain + }); + data.offers.replace(offers); } pub fn cancel_offer(offer: &T::Offer) { @@ -293,7 +316,7 @@ pub fn cancel_offer(offer: &T::Offer) { pub fn detach_seat(src: &S, seat: &Rc) { let data = src.source_data(); data.seat.set(None); - cancel_offers(src); + cancel_offers(src, true); if !data.state.get().contains(SOURCE_STATE_FINISHED) { src.send_cancelled(seat); } @@ -342,12 +365,23 @@ where S: DynDataSource, { let data = src.source_data(); - src.cancel_offers(); + src.cancel_unprivileged_offers(); let shared = data.shared.get(); shared.role.set(data.role.get()); offer_source_to_device::(src, dd, data, shared); } +pub fn offer_source_to_wlr_device(src: &Rc, dd: &Rc) +where + T: IpcVtable, + S: DynDataSource, +{ + let data = src.source_data(); + let shared = data.shared.get(); + shared.role.set(data.role.get()); + offer_source_to_device::(src, dd, data, shared); +} + fn offer_source_to_regular_client( src: &Rc, client: &Rc, @@ -360,7 +394,7 @@ fn offer_source_to_regular_client( return; } }; - src.cancel_offers(); + src.cancel_unprivileged_offers(); let shared = data.shared.get(); shared.role.set(data.role.get()); T::for_each_device(&seat, client.id, |dd| { diff --git a/src/ifs/ipc/wl_data_source.rs b/src/ifs/ipc/wl_data_source.rs index d8b790a3..aecd0956 100644 --- a/src/ifs/ipc/wl_data_source.rs +++ b/src/ifs/ipc/wl_data_source.rs @@ -4,10 +4,12 @@ use { ifs::{ ipc::{ add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, - detach_seat, offer_source_to_regular_client, offer_source_to_x, + detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device, + offer_source_to_x, wl_data_device::ClipboardIpc, wl_data_device_manager::{DND_ALL, DND_NONE}, x_data_device::{XClipboardIpc, XIpcDevice}, + zwlr_data_control_device_v1::{WlrClipboardIpc, ZwlrDataControlDeviceV1}, DataSource, DynDataOffer, DynDataSource, SharedState, SourceData, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, SOURCE_STATE_CANCELLED, SOURCE_STATE_DROPPED, @@ -66,12 +68,16 @@ impl DynDataSource for WlDataSource { offer_source_to_x::(&self, dd); } + fn offer_to_wlr_device(self: Rc, dd: &Rc) { + offer_source_to_wlr_device::(&self, dd) + } + fn detach_seat(&self, seat: &Rc) { detach_seat(self, seat); } - fn cancel_offers(&self) { - cancel_offers(self); + fn cancel_unprivileged_offers(&self) { + cancel_offers(self, false); } fn send_target(&self, mime_type: Option<&str>) { @@ -112,7 +118,7 @@ impl WlDataSource { self.data.shared.set(Rc::new(SharedState::default())); self.send_target(None); self.send_action(DND_NONE); - cancel_offers(self); + cancel_offers(self, false); } pub fn update_selected_action(&self) { diff --git a/src/ifs/ipc/x_data_source.rs b/src/ifs/ipc/x_data_source.rs index cf2af861..c071b592 100644 --- a/src/ifs/ipc/x_data_source.rs +++ b/src/ifs/ipc/x_data_source.rs @@ -4,9 +4,14 @@ use { ifs::{ ipc::{ cancel_offers, detach_seat, offer_source_to_regular_client, - wl_data_device::ClipboardIpc, x_data_device::XIpcDevice, - zwp_primary_selection_device_v1::PrimarySelectionIpc, DataSource, DynDataSource, - IpcLocation, SourceData, + offer_source_to_wlr_device, + wl_data_device::ClipboardIpc, + x_data_device::XIpcDevice, + zwlr_data_control_device_v1::{ + WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1, + }, + zwp_primary_selection_device_v1::PrimarySelectionIpc, + DataSource, DynDataSource, IpcLocation, SourceData, }, wl_seat::WlSeatGlobal, }, @@ -61,7 +66,7 @@ impl DynDataSource for XDataSource { } fn offer_to_x(self: Rc, _dd: &Rc) { - self.cancel_offers(); + self.cancel_unprivileged_offers(); self.state.xwayland.queue.push(IpcSetSelection { location: self.location, seat: self.device.seat.id(), @@ -69,11 +74,22 @@ impl DynDataSource for XDataSource { }); } + fn offer_to_wlr_device(self: Rc, dd: &Rc) { + match self.location { + IpcLocation::Clipboard => { + offer_source_to_wlr_device::(&self, dd) + } + IpcLocation::PrimarySelection => { + offer_source_to_wlr_device::(&self, dd) + } + } + } + fn detach_seat(&self, seat: &Rc) { detach_seat(self, seat); } - fn cancel_offers(&self) { - cancel_offers(self) + fn cancel_unprivileged_offers(&self) { + cancel_offers(self, false) } } diff --git a/src/ifs/ipc/zwlr_data_control_device_v1.rs b/src/ifs/ipc/zwlr_data_control_device_v1.rs new file mode 100644 index 00000000..9bb6b29e --- /dev/null +++ b/src/ifs/ipc/zwlr_data_control_device_v1.rs @@ -0,0 +1,306 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::{ + ipc::{ + destroy_data_device, + zwlr_data_control_device_v1::private::{ + WlrClipboardIpcCore, WlrIpcImpl, WlrPrimarySelectionIpcCore, + }, + zwlr_data_control_offer_v1::ZwlrDataControlOfferV1, + zwlr_data_control_source_v1::ZwlrDataControlSourceV1, + DeviceData, IpcLocation, IpcVtable, OfferData, Role, WlrIpcVtable, + }, + wl_seat::{WlSeatError, WlSeatGlobal}, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{ + zwlr_data_control_device_v1::*, ZwlrDataControlDeviceV1Id, ZwlrDataControlOfferV1Id, + ZwlrDataControlSourceV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub const PRIMARY_SELECTION_SINCE: u32 = 2; + +pub struct ZwlrDataControlDeviceV1 { + pub id: ZwlrDataControlDeviceV1Id, + pub client: Rc, + pub version: u32, + pub seat: Rc, + pub clipboard_data: DeviceData, + pub primary_selection_data: DeviceData, + pub tracker: Tracker, +} + +impl ZwlrDataControlDeviceV1 { + pub fn new( + id: ZwlrDataControlDeviceV1Id, + client: &Rc, + version: u32, + seat: &Rc, + ) -> Self { + Self { + id, + client: client.clone(), + version, + seat: seat.clone(), + clipboard_data: Default::default(), + primary_selection_data: Default::default(), + tracker: Default::default(), + } + } + + pub fn send_data_offer(&self, offer: &Rc) { + self.client.event(DataOffer { + self_id: self.id, + id: offer.id, + }) + } + + pub fn send_selection(&self, offer: Option<&Rc>) { + let id = offer + .map(|o| o.id) + .unwrap_or(ZwlrDataControlOfferV1Id::NONE); + self.client.event(Selection { + self_id: self.id, + id, + }) + } + + pub fn send_primary_selection(&self, offer: Option<&Rc>) { + let id = offer + .map(|o| o.id) + .unwrap_or(ZwlrDataControlOfferV1Id::NONE); + self.client.event(PrimarySelection { + self_id: self.id, + id, + }) + } + + fn use_source( + &self, + source: ZwlrDataControlSourceV1Id, + location: IpcLocation, + ) -> Result>, ZwlrDataControlDeviceV1Error> { + if source.is_none() { + Ok(None) + } else { + let src = self.client.lookup(source)?; + if src.used.replace(true) { + return Err(ZwlrDataControlDeviceV1Error::AlreadyUsed); + } + src.location.set(location); + Ok(Some(src)) + } + } + + fn set_selection(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlDeviceV1Error> { + let req: SetSelection = self.client.parse(self, parser)?; + let src = self.use_source(req.source, IpcLocation::Clipboard)?; + self.seat.set_selection(src)?; + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlDeviceV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + destroy_data_device::(self); + destroy_data_device::(self); + self.seat.remove_wlr_device(self); + self.client.remove_obj(self)?; + Ok(()) + } + + fn set_primary_selection( + &self, + parser: MsgParser<'_, '_>, + ) -> Result<(), ZwlrDataControlDeviceV1Error> { + let req: SetPrimarySelection = self.client.parse(self, parser)?; + let src = self.use_source(req.source, IpcLocation::PrimarySelection)?; + self.seat.set_primary_selection(src)?; + Ok(()) + } +} + +mod private { + use std::marker::PhantomData; + + pub struct WlrClipboardIpcCore; + pub struct WlrPrimarySelectionIpcCore; + pub struct WlrIpcImpl(PhantomData); +} +pub type WlrClipboardIpc = WlrIpcImpl; +pub type WlrPrimarySelectionIpc = WlrIpcImpl; + +trait WlrIpc { + const MIN_VERSION: u32; + const LOCATION: IpcLocation; + + fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData; + + fn wlr_set_seat_selection( + seat: &Rc, + source: &Rc, + ) -> Result<(), WlSeatError>; + + fn wlr_send_selection(dd: &ZwlrDataControlDeviceV1, offer: Option<&Rc>); + + fn wlr_unset(seat: &Rc); +} + +impl WlrIpc for WlrClipboardIpcCore { + const MIN_VERSION: u32 = 1; + const LOCATION: IpcLocation = IpcLocation::Clipboard; + + fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData { + &dd.clipboard_data + } + + fn wlr_set_seat_selection( + seat: &Rc, + source: &Rc, + ) -> Result<(), WlSeatError> { + seat.set_selection(Some(source.clone())) + } + + fn wlr_send_selection( + dd: &ZwlrDataControlDeviceV1, + offer: Option<&Rc>, + ) { + dd.send_selection(offer) + } + + fn wlr_unset(seat: &Rc) { + seat.unset_selection() + } +} + +impl WlrIpc for WlrPrimarySelectionIpcCore { + const MIN_VERSION: u32 = PRIMARY_SELECTION_SINCE; + const LOCATION: IpcLocation = IpcLocation::PrimarySelection; + + fn wlr_get_device_data(dd: &ZwlrDataControlDeviceV1) -> &DeviceData { + &dd.primary_selection_data + } + + fn wlr_set_seat_selection( + seat: &Rc, + source: &Rc, + ) -> Result<(), WlSeatError> { + seat.set_primary_selection(Some(source.clone())) + } + + fn wlr_send_selection( + dd: &ZwlrDataControlDeviceV1, + offer: Option<&Rc>, + ) { + dd.send_primary_selection(offer) + } + + fn wlr_unset(seat: &Rc) { + seat.unset_primary_selection() + } +} + +impl WlrIpcVtable for WlrIpcImpl { + fn for_each_device(seat: &WlSeatGlobal, f: C) + where + C: FnMut(&Rc), + { + seat.for_each_wlr_data_device(T::MIN_VERSION, f) + } +} + +impl IpcVtable for WlrIpcImpl { + const LOCATION: IpcLocation = T::LOCATION; + type Device = ZwlrDataControlDeviceV1; + type Source = ZwlrDataControlSourceV1; + type Offer = ZwlrDataControlOfferV1; + + fn get_device_data(dd: &Self::Device) -> &DeviceData { + T::wlr_get_device_data(dd) + } + + fn get_device_seat(dd: &Self::Device) -> Rc { + dd.seat.clone() + } + + fn set_seat_selection( + seat: &Rc, + source: &Rc, + serial: Option, + ) -> Result<(), WlSeatError> { + debug_assert!(serial.is_none()); + let _ = serial; + T::wlr_set_seat_selection(seat, source) + } + + fn create_offer( + device: &Rc, + offer_data: OfferData, + ) -> Result, ClientError> { + let rc = Rc::new(ZwlrDataControlOfferV1 { + id: device.client.new_id()?, + offer_id: device.client.state.data_offer_ids.next(), + client: device.client.clone(), + device: device.clone(), + data: offer_data, + location: T::LOCATION, + tracker: Default::default(), + }); + track!(device.client, rc); + device.client.add_server_obj(&rc); + Ok(rc) + } + + fn send_selection(dd: &Self::Device, offer: Option<&Rc>) { + T::wlr_send_selection(dd, offer) + } + + fn send_offer(dd: &Self::Device, offer: &Rc) { + dd.send_data_offer(offer); + } + + fn unset(seat: &Rc, _role: Role) { + T::wlr_unset(seat) + } + + fn device_client(dd: &Rc) -> &Rc { + &dd.client + } +} + +object_base! { + self = ZwlrDataControlDeviceV1; + + SET_SELECTION => set_selection, + DESTROY => destroy, + SET_PRIMARY_SELECTION => set_primary_selection if self.version >= 2, +} + +impl Object for ZwlrDataControlDeviceV1 { + fn break_loops(&self) { + self.seat.remove_wlr_device(self); + } +} + +simple_add_obj!(ZwlrDataControlDeviceV1); + +#[derive(Debug, Error)] +pub enum ZwlrDataControlDeviceV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error(transparent)] + WlSeatError(Box), + #[error("The source has already been used")] + AlreadyUsed, +} +efrom!(ZwlrDataControlDeviceV1Error, MsgParserError); +efrom!(ZwlrDataControlDeviceV1Error, ClientError); +efrom!(ZwlrDataControlDeviceV1Error, WlSeatError); diff --git a/src/ifs/ipc/zwlr_data_control_manager_v1.rs b/src/ifs/ipc/zwlr_data_control_manager_v1.rs new file mode 100644 index 00000000..df2ecdd4 --- /dev/null +++ b/src/ifs/ipc/zwlr_data_control_manager_v1.rs @@ -0,0 +1,145 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::ipc::{ + zwlr_data_control_device_v1::{ZwlrDataControlDeviceV1, PRIMARY_SELECTION_SINCE}, + zwlr_data_control_source_v1::ZwlrDataControlSourceV1, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{zwlr_data_control_manager_v1::*, ZwlrDataControlManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwlrDataControlManagerV1Global { + name: GlobalName, +} + +pub struct ZwlrDataControlManagerV1 { + pub id: ZwlrDataControlManagerV1Id, + pub client: Rc, + pub version: u32, + tracker: Tracker, +} + +impl ZwlrDataControlManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ZwlrDataControlManagerV1Id, + client: &Rc, + version: u32, + ) -> Result<(), ZwlrDataControlManagerV1Error> { + let obj = Rc::new(ZwlrDataControlManagerV1 { + id, + client: client.clone(), + version, + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +impl ZwlrDataControlManagerV1 { + fn create_data_source( + &self, + parser: MsgParser<'_, '_>, + ) -> Result<(), ZwlrDataControlManagerV1Error> { + let req: CreateDataSource = self.client.parse(self, parser)?; + let res = Rc::new(ZwlrDataControlSourceV1::new( + req.id, + &self.client, + self.version, + )); + track!(self.client, res); + self.client.add_client_obj(&res)?; + Ok(()) + } + + fn get_data_device( + self: &Rc, + parser: MsgParser<'_, '_>, + ) -> Result<(), ZwlrDataControlManagerV1Error> { + let req: GetDataDevice = self.client.parse(&**self, parser)?; + let seat = self.client.lookup(req.seat)?; + let dev = Rc::new(ZwlrDataControlDeviceV1::new( + req.id, + &self.client, + self.version, + &seat.global, + )); + track!(self.client, dev); + seat.global.add_wlr_device(&dev); + self.client.add_client_obj(&dev)?; + match seat.global.get_selection() { + Some(s) => s.offer_to_wlr_device(&dev), + _ => dev.send_selection(None), + } + if self.version >= PRIMARY_SELECTION_SINCE { + match seat.global.get_primary_selection() { + Some(s) => s.offer_to_wlr_device(&dev), + _ => dev.send_primary_selection(None), + } + } + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlManagerV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + self.client.remove_obj(self)?; + Ok(()) + } +} + +global_base!( + ZwlrDataControlManagerV1Global, + ZwlrDataControlManagerV1, + ZwlrDataControlManagerV1Error +); + +impl Global for ZwlrDataControlManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 2 + } + + fn secure(&self) -> bool { + true + } +} + +simple_add_global!(ZwlrDataControlManagerV1Global); + +object_base! { + self = ZwlrDataControlManagerV1; + + CREATE_DATA_SOURCE => create_data_source, + GET_DATA_DEVICE => get_data_device, + DESTROY => destroy, +} + +impl Object for ZwlrDataControlManagerV1 {} + +simple_add_obj!(ZwlrDataControlManagerV1); + +#[derive(Debug, Error)] +pub enum ZwlrDataControlManagerV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), +} +efrom!(ZwlrDataControlManagerV1Error, ClientError); +efrom!(ZwlrDataControlManagerV1Error, MsgParserError); diff --git a/src/ifs/ipc/zwlr_data_control_offer_v1.rs b/src/ifs/ipc/zwlr_data_control_offer_v1.rs new file mode 100644 index 00000000..a9eb427c --- /dev/null +++ b/src/ifs/ipc/zwlr_data_control_offer_v1.rs @@ -0,0 +1,135 @@ +use { + crate::{ + client::{Client, ClientError, ClientId}, + ifs::{ + ipc::{ + break_offer_loops, cancel_offer, destroy_data_offer, receive_data_offer, + zwlr_data_control_device_v1::{ + WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1, + }, + DataOffer, DataOfferId, DynDataOffer, IpcLocation, OfferData, + }, + wl_seat::WlSeatGlobal, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{zwlr_data_control_offer_v1::*, ZwlrDataControlOfferV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ZwlrDataControlOfferV1 { + pub id: ZwlrDataControlOfferV1Id, + pub offer_id: DataOfferId, + pub client: Rc, + pub device: Rc, + pub data: OfferData, + pub location: IpcLocation, + pub tracker: Tracker, +} + +impl DataOffer for ZwlrDataControlOfferV1 { + type Device = ZwlrDataControlDeviceV1; + + fn offer_data(&self) -> &OfferData { + &self.data + } +} + +impl DynDataOffer for ZwlrDataControlOfferV1 { + fn offer_id(&self) -> DataOfferId { + self.offer_id + } + + fn client_id(&self) -> ClientId { + self.client.id + } + + fn send_offer(&self, mime_type: &str) { + ZwlrDataControlOfferV1::send_offer(self, mime_type) + } + + fn destroy(&self) { + match self.location { + IpcLocation::Clipboard => destroy_data_offer::(self), + IpcLocation::PrimarySelection => destroy_data_offer::(self), + } + } + + fn cancel(&self) { + match self.location { + IpcLocation::Clipboard => cancel_offer::(self), + IpcLocation::PrimarySelection => cancel_offer::(self), + } + } + + fn get_seat(&self) -> Rc { + self.device.seat.clone() + } + + fn is_privileged(&self) -> bool { + true + } +} + +impl ZwlrDataControlOfferV1 { + pub fn send_offer(&self, mime_type: &str) { + self.client.event(Offer { + self_id: self.id, + mime_type, + }) + } + + fn receive(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlOfferV1Error> { + let req: Receive = self.client.parse(self, parser)?; + match self.location { + IpcLocation::Clipboard => { + receive_data_offer::(self, req.mime_type, req.fd) + } + IpcLocation::PrimarySelection => { + receive_data_offer::(self, req.mime_type, req.fd) + } + } + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlOfferV1Error> { + let _req: Destroy = self.client.parse(self, parser)?; + match self.location { + IpcLocation::Clipboard => destroy_data_offer::(self), + IpcLocation::PrimarySelection => destroy_data_offer::(self), + } + self.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwlrDataControlOfferV1; + + RECEIVE => receive, + DESTROY => destroy, +} + +impl Object for ZwlrDataControlOfferV1 { + fn break_loops(&self) { + match self.location { + IpcLocation::Clipboard => break_offer_loops::(self), + IpcLocation::PrimarySelection => break_offer_loops::(self), + } + } +} + +simple_add_obj!(ZwlrDataControlOfferV1); + +#[derive(Debug, Error)] +pub enum ZwlrDataControlOfferV1Error { + #[error(transparent)] + ClientError(Box), + #[error("Parsing failed")] + MsgParserError(#[source] Box), +} +efrom!(ZwlrDataControlOfferV1Error, ClientError); +efrom!(ZwlrDataControlOfferV1Error, MsgParserError); diff --git a/src/ifs/ipc/zwlr_data_control_source_v1.rs b/src/ifs/ipc/zwlr_data_control_source_v1.rs new file mode 100644 index 00000000..bf5fa10a --- /dev/null +++ b/src/ifs/ipc/zwlr_data_control_source_v1.rs @@ -0,0 +1,169 @@ +use { + crate::{ + client::{Client, ClientError}, + ifs::{ + ipc::{ + add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, + detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device, + offer_source_to_x, + wl_data_device::ClipboardIpc, + x_data_device::{XClipboardIpc, XIpcDevice, XPrimarySelectionIpc}, + zwlr_data_control_device_v1::{ + WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1, + }, + zwp_primary_selection_device_v1::PrimarySelectionIpc, + DataSource, DynDataSource, IpcLocation, SourceData, + }, + wl_seat::WlSeatGlobal, + }, + leaks::Tracker, + object::Object, + utils::buffd::{MsgParser, MsgParserError}, + wire::{zwlr_data_control_source_v1::*, ZwlrDataControlSourceV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, + uapi::OwnedFd, +}; + +pub struct ZwlrDataControlSourceV1 { + pub id: ZwlrDataControlSourceV1Id, + pub data: SourceData, + pub version: u32, + pub location: Cell, + pub used: Cell, + pub tracker: Tracker, +} + +impl DataSource for ZwlrDataControlSourceV1 { + fn send_cancelled(&self, _seat: &Rc) { + ZwlrDataControlSourceV1::send_cancelled(self); + } +} + +impl DynDataSource for ZwlrDataControlSourceV1 { + fn source_data(&self) -> &SourceData { + &self.data + } + + fn send_send(&self, mime_type: &str, fd: Rc) { + ZwlrDataControlSourceV1::send_send(&self, mime_type, fd); + } + + fn offer_to_regular_client(self: Rc, client: &Rc) { + match self.location.get() { + IpcLocation::Clipboard => { + offer_source_to_regular_client::(&self, client) + } + IpcLocation::PrimarySelection => { + offer_source_to_regular_client::(&self, client) + } + } + } + + fn offer_to_x(self: Rc, dd: &Rc) { + match self.location.get() { + IpcLocation::Clipboard => offer_source_to_x::(&self, dd), + IpcLocation::PrimarySelection => { + offer_source_to_x::(&self, dd) + } + } + } + + fn offer_to_wlr_device(self: Rc, dd: &Rc) { + match self.location.get() { + IpcLocation::Clipboard => { + offer_source_to_wlr_device::(&self, dd) + } + IpcLocation::PrimarySelection => { + offer_source_to_wlr_device::(&self, dd) + } + } + } + + fn detach_seat(&self, seat: &Rc) { + detach_seat(self, seat) + } + + fn cancel_unprivileged_offers(&self) { + cancel_offers(self, false) + } +} + +impl ZwlrDataControlSourceV1 { + pub fn new(id: ZwlrDataControlSourceV1Id, client: &Rc, version: u32) -> Self { + Self { + id, + tracker: Default::default(), + data: SourceData::new(client), + version, + location: Cell::new(IpcLocation::Clipboard), + used: Cell::new(false), + } + } + + pub fn send_send(&self, mime_type: &str, fd: Rc) { + self.data.client.event(Send { + self_id: self.id, + mime_type, + fd, + }) + } + + pub fn send_cancelled(&self) { + self.data.client.event(Cancelled { self_id: self.id }) + } + + fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlSourceV1Error> { + let req: Offer = self.data.client.parse(self, parser)?; + if self.used.get() { + return Err(ZwlrDataControlSourceV1Error::AlreadyUsed); + } + add_data_source_mime_type::(self, req.mime_type); + Ok(()) + } + + fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwlrDataControlSourceV1Error> { + let _req: Destroy = self.data.client.parse(self, parser)?; + match self.location.get() { + IpcLocation::Clipboard => destroy_data_source::(self), + IpcLocation::PrimarySelection => destroy_data_source::(self), + } + self.data.client.remove_obj(self)?; + Ok(()) + } +} + +object_base! { + self = ZwlrDataControlSourceV1; + + OFFER => offer, + DESTROY => destroy, +} + +impl Object for ZwlrDataControlSourceV1 { + fn break_loops(&self) { + match self.location.get() { + IpcLocation::Clipboard => break_source_loops::(self), + IpcLocation::PrimarySelection => break_source_loops::(self), + } + } +} + +dedicated_add_obj!( + ZwlrDataControlSourceV1, + ZwlrDataControlSourceV1Id, + zwlr_data_sources +); + +#[derive(Debug, Error)] +pub enum ZwlrDataControlSourceV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), + #[error("The source has already been used")] + AlreadyUsed, +} +efrom!(ZwlrDataControlSourceV1Error, ClientError); +efrom!(ZwlrDataControlSourceV1Error, MsgParserError); diff --git a/src/ifs/ipc/zwp_primary_selection_source_v1.rs b/src/ifs/ipc/zwp_primary_selection_source_v1.rs index d58861cd..d6d50683 100644 --- a/src/ifs/ipc/zwp_primary_selection_source_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_source_v1.rs @@ -4,8 +4,10 @@ use { ifs::{ ipc::{ add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, - detach_seat, offer_source_to_regular_client, offer_source_to_x, + detach_seat, offer_source_to_regular_client, offer_source_to_wlr_device, + offer_source_to_x, x_data_device::{XIpcDevice, XPrimarySelectionIpc}, + zwlr_data_control_device_v1::{WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1}, zwp_primary_selection_device_v1::PrimarySelectionIpc, DataSource, DynDataSource, SourceData, }, @@ -50,12 +52,16 @@ impl DynDataSource for ZwpPrimarySelectionSourceV1 { offer_source_to_x::(&self, dd); } + fn offer_to_wlr_device(self: Rc, dd: &Rc) { + offer_source_to_wlr_device::(&self, dd) + } + fn detach_seat(&self, seat: &Rc) { detach_seat(self, seat); } - fn cancel_offers(&self) { - cancel_offers(self); + fn cancel_unprivileged_offers(&self) { + cancel_offers(self, false); } } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index b305433d..e4bdabde 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -23,6 +23,9 @@ use { wl_data_device::{ClipboardIpc, WlDataDevice}, wl_data_source::WlDataSource, x_data_device::{XClipboardIpc, XIpcDevice, XIpcDeviceId, XPrimarySelectionIpc}, + zwlr_data_control_device_v1::{ + WlrClipboardIpc, WlrPrimarySelectionIpc, ZwlrDataControlDeviceV1, + }, zwp_primary_selection_device_v1::{ PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, }, @@ -64,7 +67,8 @@ use { }, wire::{ wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, - WlSeatId, ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, + WlSeatId, ZwlrDataControlDeviceV1Id, ZwpPrimarySelectionDeviceV1Id, + ZwpRelativePointerV1Id, }, xkbcommon::{XkbKeymap, XkbState}, }, @@ -141,6 +145,8 @@ pub struct WlSeatGlobal { AHashMap>, >, >, + wlr_data_devices: + CopyHashMap<(ClientId, ZwlrDataControlDeviceV1Id), Rc>, repeat_rate: Cell<(i32, i32)>, kb_map: CloneCell>, kb_state: RefCell, @@ -219,6 +225,7 @@ impl WlSeatGlobal { constraint: Default::default(), idle_notifications: Default::default(), last_input_usec: Cell::new(now_usec()), + wlr_data_devices: Default::default(), }); state.add_cursor_size(*DEFAULT_CURSOR_SIZE); let seat = slf.clone(); @@ -394,6 +401,15 @@ impl WlSeatGlobal { } } + pub fn add_wlr_device(&self, device: &Rc) { + self.wlr_data_devices + .set((device.client.id, device.id), device.clone()); + } + + pub fn remove_wlr_device(&self, device: &ZwlrDataControlDeviceV1) { + self.wlr_data_devices.remove(&(device.client.id, device.id)); + } + pub fn get_output(&self) -> Rc { self.output.get() } @@ -727,7 +743,7 @@ impl WlSeatGlobal { } } - fn set_selection_( + fn set_selection_( self: &Rc, field: &CloneCell>>, src: Option>, @@ -735,6 +751,7 @@ impl WlSeatGlobal { where T: ipc::IterableIpcVtable, X: ipc::IpcVtable, + W: ipc::WlrIpcVtable, S: DynDataSource, { if let (Some(new), Some(old)) = (&src, &field.get()) { @@ -750,9 +767,13 @@ impl WlSeatGlobal { old.detach_seat(self); } if let Some(client) = self.keyboard_node.get().node_client() { - self.offer_selection_to_client::(src.map(|v| v as Rc<_>), &client); + self.offer_selection_to_client::(src.clone().map(|v| v as Rc<_>), &client); // client.flush(); } + W::for_each_device(self, |device| match &src { + Some(src) => src.clone().offer_to_wlr_device(device), + _ => W::send_selection(device, None), + }); Ok(()) } @@ -765,7 +786,7 @@ impl WlSeatGlobal { X: ipc::IpcVtable, { if let Some(src) = &selection { - src.cancel_offers(); + src.cancel_unprivileged_offers(); } if client.is_xwayland { self.for_each_x_data_device(|dd| match &selection { @@ -824,7 +845,14 @@ impl WlSeatGlobal { self: &Rc, selection: Option>, ) -> Result<(), WlSeatError> { - self.set_selection_::(&self.selection, selection) + self.set_selection_::( + &self.selection, + selection, + ) + } + + pub fn get_selection(&self) -> Option> { + self.selection.get() } pub fn may_modify_selection(&self, client: &Rc, serial: u32) -> bool { @@ -865,12 +893,16 @@ impl WlSeatGlobal { self: &Rc, selection: Option>, ) -> Result<(), WlSeatError> { - self.set_selection_::( + self.set_selection_::( &self.primary_selection, selection, ) } + pub fn get_primary_selection(&self) -> Option> { + self.primary_selection.get() + } + pub fn reload_known_cursor(&self) { if let Some(kc) = self.desired_known_cursor.get() { self.set_known_cursor(kc); @@ -978,6 +1010,7 @@ impl WlSeatGlobal { self.bindings.borrow_mut().clear(); self.data_devices.borrow_mut().clear(); self.primary_selection_devices.borrow_mut().clear(); + self.wlr_data_devices.clear(); self.cursor.set(None); self.selection.set(None); self.primary_selection.set(None); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 67263026..14b27d9e 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -7,6 +7,7 @@ use { ipc::{ wl_data_device::{ClipboardIpc, WlDataDevice}, x_data_device::{XClipboardIpc, XPrimarySelectionIpc}, + zwlr_data_control_device_v1::ZwlrDataControlDeviceV1, zwp_primary_selection_device_v1::{ PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, }, @@ -508,6 +509,17 @@ impl WlSeatGlobal { } } + pub fn for_each_wlr_data_device(&self, ver: u32, mut f: C) + where + C: FnMut(&Rc), + { + for dd in self.wlr_data_devices.lock().values() { + if dd.version >= ver { + f(dd); + } + } + } + fn surface_pointer_frame(&self, surface: &WlSurface) { self.surface_pointer_event(POINTER_FRAME_SINCE_VERSION, surface, |p| p.send_frame()); } diff --git a/src/utils/smallmap.rs b/src/utils/smallmap.rs index 1eb35979..624c653a 100644 --- a/src/utils/smallmap.rs +++ b/src/utils/smallmap.rs @@ -76,6 +76,10 @@ impl SmallMap { unsafe { self.m.get().deref_mut().take() } } + pub fn replace(&self, other: SmallVec<[(K, V); N]>) -> SmallVec<[(K, V); N]> { + unsafe { mem::replace(&mut self.m.get().deref_mut().m, other) } + } + pub fn pop(&self) -> Option<(K, V)> { unsafe { self.m.get().deref_mut().pop() } } diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index e28f7724..2a25e45e 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1733,13 +1733,13 @@ impl Wm { data: SourceData::new(&self.client), location: T::LOCATION, }); + for target in &targets { + add_data_source_mime_type::(&source, target); + } if let Err(e) = T::set_seat_selection(&seat, &source, None) { log::error!("Could not set selection: {}", ErrorFmt(e)); return Ok(()); } - for target in &targets { - add_data_source_mime_type::(&source, target); - } sd.sources.set(seat.id(), source); } } else { diff --git a/wire/zwlr_data_control_device_v1.txt b/wire/zwlr_data_control_device_v1.txt new file mode 100644 index 00000000..7a458bc8 --- /dev/null +++ b/wire/zwlr_data_control_device_v1.txt @@ -0,0 +1,30 @@ +# requests + +msg set_selection = 0 { + source: id(zwlr_data_control_source_v1), +} + +msg destroy = 1 { + +} + +msg set_primary_selection = 2 { + source: id(zwlr_data_control_source_v1), +} + +# events + +msg data_offer = 0 { + id: id(zwlr_data_control_offer_v1), +} + +msg selection = 1 { + id: id(zwlr_data_control_offer_v1), +} + +msg finished = 2 { +} + +msg primary_selection = 3 { + id: id(zwlr_data_control_offer_v1), +} diff --git a/wire/zwlr_data_control_manager_v1.txt b/wire/zwlr_data_control_manager_v1.txt new file mode 100644 index 00000000..5742e10a --- /dev/null +++ b/wire/zwlr_data_control_manager_v1.txt @@ -0,0 +1,14 @@ +# requests + +msg create_data_source = 0 { + id: id(zwlr_data_control_source_v1), +} + +msg get_data_device = 1 { + id: id(zwlr_data_control_device_v1), + seat: id(wl_seat), +} + +msg destroy = 2 { + +} diff --git a/wire/zwlr_data_control_offer_v1.txt b/wire/zwlr_data_control_offer_v1.txt new file mode 100644 index 00000000..5a7bf7ae --- /dev/null +++ b/wire/zwlr_data_control_offer_v1.txt @@ -0,0 +1,16 @@ +# requests + +msg receive = 0 { + mime_type: str, + fd: fd, +} + +msg destroy = 1 { + +} + +# events + +msg offer = 0 { + mime_type: str, +} diff --git a/wire/zwlr_data_control_source_v1.txt b/wire/zwlr_data_control_source_v1.txt new file mode 100644 index 00000000..b274415f --- /dev/null +++ b/wire/zwlr_data_control_source_v1.txt @@ -0,0 +1,20 @@ +# requests + +msg offer = 0 { + mime_type: str, +} + +msg destroy = 1 { + +} + +# events + +msg send = 0 { + mime_type: str, + fd: fd, +} + +msg cancelled = 1 { + +}