diff --git a/docs/features.md b/docs/features.md index 2409c02d..e8ba1698 100644 --- a/docs/features.md +++ b/docs/features.md @@ -140,6 +140,7 @@ Jay supports the following wayland protocols: | Global | Version | Privileged | |-----------------------------------------|:----------------|---------------| +| ext_data_control_manager_v1 | 1 | Yes | | ext_foreign_toplevel_list_v1 | 1 | Yes | | ext_idle_notifier_v1 | 1 | Yes | | ext_session_lock_manager_v1 | 1 | Yes | diff --git a/release-notes.md b/release-notes.md index ec359474..69afbd32 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,6 +4,7 @@ - Tiles and workspaces can now be dragged with the mouse. - Vulkan is now the default renderer. - Emulate vblank events on the nvidia driver. +- Add support fo ext-data-control-v1. # 1.6.0 (2024-09-25) diff --git a/src/client/objects.rs b/src/client/objects.rs index d4f9ff5e..9102d95e 100644 --- a/src/client/objects.rs +++ b/src/client/objects.rs @@ -3,7 +3,10 @@ use { client::{Client, ClientError}, ifs::{ ipc::{ - data_control::zwlr_data_control_source_v1::ZwlrDataControlSourceV1, + data_control::{ + ext_data_control_source_v1::ExtDataControlSourceV1, + zwlr_data_control_source_v1::ZwlrDataControlSourceV1, + }, wl_data_source::WlDataSource, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }, @@ -32,11 +35,11 @@ use { copyhashmap::{CopyHashMap, Locked}, }, wire::{ - JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, WlBufferId, - WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, WlSeatId, - WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, XdgPopupId, - XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, ZwlrDataControlSourceV1Id, - ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, + ExtDataControlSourceV1Id, JayOutputId, JayScreencastId, JayToplevelId, JayWorkspaceId, + WlBufferId, WlDataSourceId, WlOutputId, WlPointerId, WlRegionId, WlRegistryId, + WlSeatId, WlSurfaceId, WpDrmLeaseConnectorV1Id, WpLinuxDrmSyncobjTimelineV1Id, + XdgPopupId, XdgPositionerId, XdgSurfaceId, XdgToplevelId, XdgWmBaseId, + ZwlrDataControlSourceV1Id, ZwpPrimarySelectionSourceV1Id, ZwpTabletToolV2Id, }, }, std::{cell::RefCell, mem, rc::Rc}, @@ -68,6 +71,7 @@ pub struct Objects { pub drm_lease_outputs: CopyHashMap>, pub tablet_tools: CopyHashMap>, pub xdg_popups: CopyHashMap>, + pub ext_data_sources: CopyHashMap>, ids: RefCell>, } @@ -101,6 +105,7 @@ impl Objects { drm_lease_outputs: Default::default(), tablet_tools: Default::default(), xdg_popups: Default::default(), + ext_data_sources: Default::default(), ids: RefCell::new(vec![]), } } @@ -138,6 +143,7 @@ impl Objects { self.drm_lease_outputs.clear(); self.tablet_tools.clear(); self.xdg_popups.clear(); + self.ext_data_sources.clear(); } pub fn id(&self, client_data: &Client) -> Result diff --git a/src/globals.rs b/src/globals.rs index 2e7ca34b..f95fd435 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -7,7 +7,10 @@ use { ext_idle_notifier_v1::ExtIdleNotifierV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ipc::{ - data_control::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global, + data_control::{ + ext_data_control_manager_v1::ExtDataControlManagerV1Global, + zwlr_data_control_manager_v1::ZwlrDataControlManagerV1Global, + }, wl_data_device_manager::WlDataDeviceManagerGlobal, zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1Global, }, @@ -197,6 +200,7 @@ impl Globals { add_singleton!(ZwpPointerGesturesV1Global); add_singleton!(ZwpTabletManagerV2Global); add_singleton!(JayDamageTrackingGlobal); + add_singleton!(ExtDataControlManagerV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs/ipc/data_control.rs b/src/ifs/ipc/data_control.rs index 20c3418d..26fe691c 100644 --- a/src/ifs/ipc/data_control.rs +++ b/src/ifs/ipc/data_control.rs @@ -3,6 +3,10 @@ use { std::rc::Rc, }; +pub mod ext_data_control_device_v1; +pub mod ext_data_control_manager_v1; +pub mod ext_data_control_offer_v1; +pub mod ext_data_control_source_v1; mod private; pub mod zwlr_data_control_device_v1; pub mod zwlr_data_control_manager_v1; diff --git a/src/ifs/ipc/data_control/ext_data_control_device_v1.rs b/src/ifs/ipc/data_control/ext_data_control_device_v1.rs new file mode 100644 index 00000000..b498fb6a --- /dev/null +++ b/src/ifs/ipc/data_control/ext_data_control_device_v1.rs @@ -0,0 +1,158 @@ +use { + crate::{ + client::Client, + ifs::{ + ipc::data_control::{ + ext_data_control_offer_v1::ExtDataControlOfferV1, + ext_data_control_source_v1::ExtDataControlSourceV1, + private::{ + logic::{self, DataControlError}, + DataControlDevice, DataControlDeviceData, DataControlIpc, DataControlOfferData, + }, + }, + wl_seat::WlSeatGlobal, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ + ext_data_control_device_v1::*, ExtDataControlDeviceV1Id, ExtDataControlOfferV1Id, + ExtDataControlSourceV1Id, + }, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtDataControlDeviceV1 { + pub id: ExtDataControlDeviceV1Id, + pub data: DataControlDeviceData, + pub tracker: Tracker, +} + +impl ExtDataControlDeviceV1 { + pub fn new( + id: ExtDataControlDeviceV1Id, + client: &Rc, + version: Version, + seat: &Rc, + ) -> Self { + Self { + id, + data: DataControlDeviceData { + data_control_device_id: client.state.data_control_device_ids.next(), + 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.data.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(ExtDataControlOfferV1Id::NONE); + self.data.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(ExtDataControlOfferV1Id::NONE); + self.data.client.event(PrimarySelection { + self_id: self.id, + id, + }) + } +} + +impl ExtDataControlDeviceV1RequestHandler for ExtDataControlDeviceV1 { + type Error = ExtDataControlDeviceV1Error; + + fn set_selection(&self, req: SetSelection, _slf: &Rc) -> Result<(), Self::Error> { + logic::device_set_selection(self, req.source.is_some().then_some(req.source))?; + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + logic::device_destroy(self)?; + Ok(()) + } + + fn set_primary_selection( + &self, + req: SetPrimarySelection, + _slf: &Rc, + ) -> Result<(), Self::Error> { + logic::device_set_primary_selection(self, req.source.is_some().then_some(req.source))?; + Ok(()) + } +} + +pub struct ExtDataControlIpc; + +impl DataControlIpc for ExtDataControlIpc { + const PRIMARY_SELECTION_SINCE: Version = Version(1); + type Device = ExtDataControlDeviceV1; + type OfferId = ExtDataControlOfferV1Id; + type Offer = ExtDataControlOfferV1; + type SourceId = ExtDataControlSourceV1Id; + type Source = ExtDataControlSourceV1; + + fn create_offer(id: Self::OfferId, data: DataControlOfferData) -> Rc { + let rc = Rc::new(ExtDataControlOfferV1 { + id, + data, + tracker: Default::default(), + }); + track!(rc.data.client, rc); + rc + } +} + +impl DataControlDevice for ExtDataControlDeviceV1 { + type Ipc = ExtDataControlIpc; + + fn data(&self) -> &DataControlDeviceData { + &self.data + } + + fn send_data_offer(&self, offer: &Rc<::Offer>) { + self.send_data_offer(offer) + } + + fn send_selection(&self, offer: Option<&Rc<::Offer>>) { + self.send_selection(offer) + } + + fn send_primary_selection(&self, offer: Option<&Rc<::Offer>>) { + self.send_primary_selection(offer) + } +} + +object_base! { + self = ExtDataControlDeviceV1; + version = self.data.version; +} + +impl Object for ExtDataControlDeviceV1 { + fn break_loops(&self) { + logic::data_device_break_loops(self); + } +} + +simple_add_obj!(ExtDataControlDeviceV1); + +#[derive(Debug, Error)] +pub enum ExtDataControlDeviceV1Error { + #[error(transparent)] + DataControlError(#[from] DataControlError), +} diff --git a/src/ifs/ipc/data_control/ext_data_control_manager_v1.rs b/src/ifs/ipc/data_control/ext_data_control_manager_v1.rs new file mode 100644 index 00000000..845d0d88 --- /dev/null +++ b/src/ifs/ipc/data_control/ext_data_control_manager_v1.rs @@ -0,0 +1,134 @@ +use { + crate::{ + client::{Client, ClientCaps, ClientError, CAP_DATA_CONTROL_MANAGER}, + globals::{Global, GlobalName}, + ifs::ipc::{ + data_control::{ + ext_data_control_device_v1::ExtDataControlDeviceV1, + ext_data_control_source_v1::ExtDataControlSourceV1, DynDataControlDevice, + }, + IpcLocation, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ext_data_control_manager_v1::*, ExtDataControlManagerV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtDataControlManagerV1Global { + name: GlobalName, +} + +pub struct ExtDataControlManagerV1 { + pub id: ExtDataControlManagerV1Id, + pub client: Rc, + pub version: Version, + tracker: Tracker, +} + +impl ExtDataControlManagerV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtDataControlManagerV1Id, + client: &Rc, + version: Version, + ) -> Result<(), ExtDataControlManagerV1Error> { + let obj = Rc::new(ExtDataControlManagerV1 { + id, + client: client.clone(), + version, + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +impl ExtDataControlManagerV1RequestHandler for ExtDataControlManagerV1 { + type Error = ExtDataControlManagerV1Error; + + fn create_data_source( + &self, + req: CreateDataSource, + _slf: &Rc, + ) -> Result<(), Self::Error> { + let res = Rc::new(ExtDataControlSourceV1::new( + req.id, + &self.client, + self.version, + )); + track!(self.client, res); + self.client.add_client_obj(&res)?; + Ok(()) + } + + fn get_data_device(&self, req: GetDataDevice, _slf: &Rc) -> Result<(), Self::Error> { + let seat = self.client.lookup(req.seat)?; + let dev = Rc::new(ExtDataControlDeviceV1::new( + req.id, + &self.client, + self.version, + &seat.global, + )); + track!(self.client, dev); + seat.global.add_data_control_device(dev.clone()); + self.client.add_client_obj(&dev)?; + dev.clone() + .handle_new_source(IpcLocation::Clipboard, seat.global.get_selection()); + dev.clone().handle_new_source( + IpcLocation::PrimarySelection, + seat.global.get_primary_selection(), + ); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + self.client.remove_obj(self)?; + Ok(()) + } +} + +global_base!( + ExtDataControlManagerV1Global, + ExtDataControlManagerV1, + ExtDataControlManagerV1Error +); + +impl Global for ExtDataControlManagerV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn required_caps(&self) -> ClientCaps { + CAP_DATA_CONTROL_MANAGER + } +} + +simple_add_global!(ExtDataControlManagerV1Global); + +object_base! { + self = ExtDataControlManagerV1; + version = self.version; +} + +impl Object for ExtDataControlManagerV1 {} + +simple_add_obj!(ExtDataControlManagerV1); + +#[derive(Debug, Error)] +pub enum ExtDataControlManagerV1Error { + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtDataControlManagerV1Error, ClientError); diff --git a/src/ifs/ipc/data_control/ext_data_control_offer_v1.rs b/src/ifs/ipc/data_control/ext_data_control_offer_v1.rs new file mode 100644 index 00000000..f401b227 --- /dev/null +++ b/src/ifs/ipc/data_control/ext_data_control_offer_v1.rs @@ -0,0 +1,76 @@ +use { + crate::{ + ifs::ipc::data_control::{ + ext_data_control_device_v1::ExtDataControlIpc, + private::{ + logic::{self, DataControlError}, + DataControlOffer, DataControlOfferData, + }, + }, + leaks::Tracker, + object::Object, + wire::{ext_data_control_offer_v1::*, ExtDataControlOfferV1Id}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct ExtDataControlOfferV1 { + pub id: ExtDataControlOfferV1Id, + pub data: DataControlOfferData, + pub tracker: Tracker, +} + +impl DataControlOffer for ExtDataControlOfferV1 { + type Ipc = ExtDataControlIpc; + + fn data(&self) -> &DataControlOfferData { + &self.data + } + + fn send_offer(&self, mime_type: &str) { + self.send_offer(mime_type); + } +} + +impl ExtDataControlOfferV1 { + pub fn send_offer(&self, mime_type: &str) { + self.data.client.event(Offer { + self_id: self.id, + mime_type, + }) + } +} + +impl ExtDataControlOfferV1RequestHandler for ExtDataControlOfferV1 { + type Error = ExtDataControlOfferV1Error; + + fn receive(&self, req: Receive, _slf: &Rc) -> Result<(), Self::Error> { + logic::data_offer_receive(self, req.mime_type, req.fd); + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + logic::data_offer_destroy(self)?; + Ok(()) + } +} + +object_base! { + self = ExtDataControlOfferV1; + version = self.data.device.data.version; +} + +impl Object for ExtDataControlOfferV1 { + fn break_loops(&self) { + logic::data_offer_break_loops(self); + } +} + +simple_add_obj!(ExtDataControlOfferV1); + +#[derive(Debug, Error)] +pub enum ExtDataControlOfferV1Error { + #[error(transparent)] + DataControlError(#[from] DataControlError), +} diff --git a/src/ifs/ipc/data_control/ext_data_control_source_v1.rs b/src/ifs/ipc/data_control/ext_data_control_source_v1.rs new file mode 100644 index 00000000..fdc0ed1d --- /dev/null +++ b/src/ifs/ipc/data_control/ext_data_control_source_v1.rs @@ -0,0 +1,107 @@ +use { + crate::{ + client::Client, + ifs::ipc::{ + data_control::{ + ext_data_control_device_v1::ExtDataControlIpc, + private::{ + logic::{self, DataControlError}, + DataControlSource, DataControlSourceData, + }, + }, + IpcLocation, SourceData, + }, + leaks::Tracker, + object::{Object, Version}, + wire::{ext_data_control_source_v1::*, ExtDataControlSourceV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, + uapi::OwnedFd, +}; + +pub struct ExtDataControlSourceV1 { + pub id: ExtDataControlSourceV1Id, + pub data: DataControlSourceData, + pub tracker: Tracker, +} + +impl DataControlSource for ExtDataControlSourceV1 { + type Ipc = ExtDataControlIpc; + + fn data(&self) -> &DataControlSourceData { + &self.data + } + + fn send_cancelled(&self) { + self.send_cancelled(); + } + + fn send_send(&self, mime_type: &str, fd: Rc) { + self.send_send(mime_type, fd); + } +} + +impl ExtDataControlSourceV1 { + pub fn new(id: ExtDataControlSourceV1Id, client: &Rc, version: Version) -> Self { + Self { + id, + data: DataControlSourceData { + data: SourceData::new(client), + version, + location: Cell::new(IpcLocation::Clipboard), + used: Cell::new(false), + }, + tracker: Default::default(), + } + } + + pub fn send_send(&self, mime_type: &str, fd: Rc) { + self.data.data.client.event(Send { + self_id: self.id, + mime_type, + fd, + }) + } + + pub fn send_cancelled(&self) { + self.data.data.client.event(Cancelled { self_id: self.id }) + } +} + +impl ExtDataControlSourceV1RequestHandler for ExtDataControlSourceV1 { + type Error = ExtDataControlSourceV1Error; + + fn offer(&self, req: Offer, _slf: &Rc) -> Result<(), Self::Error> { + logic::data_source_offer(self, req.mime_type)?; + Ok(()) + } + + fn destroy(&self, _req: Destroy, _slf: &Rc) -> Result<(), Self::Error> { + logic::data_source_destroy(self)?; + Ok(()) + } +} + +object_base! { + self = ExtDataControlSourceV1; + version = self.data.version; +} + +impl Object for ExtDataControlSourceV1 { + fn break_loops(&self) { + logic::data_source_break_loops(self); + } +} + +dedicated_add_obj!( + ExtDataControlSourceV1, + ExtDataControlSourceV1Id, + ext_data_sources +); + +#[derive(Debug, Error)] +pub enum ExtDataControlSourceV1Error { + #[error(transparent)] + DataControlError(#[from] DataControlError), +} diff --git a/wire/ext_data_control_device_v1.txt b/wire/ext_data_control_device_v1.txt new file mode 100644 index 00000000..5652eeef --- /dev/null +++ b/wire/ext_data_control_device_v1.txt @@ -0,0 +1,30 @@ +# requests + +request set_selection { + source: id(ext_data_control_source_v1), +} + +request destroy { + +} + +request set_primary_selection { + source: id(ext_data_control_source_v1), +} + +# events + +event data_offer { + id: id(ext_data_control_offer_v1), +} + +event selection { + id: id(ext_data_control_offer_v1), +} + +event finished { +} + +event primary_selection { + id: id(ext_data_control_offer_v1), +} diff --git a/wire/ext_data_control_manager_v1.txt b/wire/ext_data_control_manager_v1.txt new file mode 100644 index 00000000..096b01fa --- /dev/null +++ b/wire/ext_data_control_manager_v1.txt @@ -0,0 +1,14 @@ +# requests + +request create_data_source { + id: id(ext_data_control_source_v1), +} + +request get_data_device { + id: id(ext_data_control_device_v1), + seat: id(wl_seat), +} + +request destroy { + +} diff --git a/wire/ext_data_control_offer_v1.txt b/wire/ext_data_control_offer_v1.txt new file mode 100644 index 00000000..e7dd04d4 --- /dev/null +++ b/wire/ext_data_control_offer_v1.txt @@ -0,0 +1,16 @@ +# requests + +request receive { + mime_type: str, + fd: fd, +} + +request destroy { + +} + +# events + +event offer { + mime_type: str, +} diff --git a/wire/ext_data_control_source_v1.txt b/wire/ext_data_control_source_v1.txt new file mode 100644 index 00000000..92689834 --- /dev/null +++ b/wire/ext_data_control_source_v1.txt @@ -0,0 +1,20 @@ +# requests + +request offer { + mime_type: str, +} + +request destroy { + +} + +# events + +event send { + mime_type: str, + fd: fd, +} + +event cancelled { + +}