From d6fabcb2b501980b4d8f26c1a68d41cc502d023e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 10 May 2022 01:47:10 +0200 Subject: [PATCH] xwayland: implement copy/paste --- README.md | 1 - src/compositor.rs | 1 + src/ifs/ipc.rs | 128 +-- src/ifs/ipc/wl_data_device.rs | 210 +++-- src/ifs/ipc/wl_data_device_manager.rs | 12 +- src/ifs/ipc/wl_data_offer.rs | 65 +- src/ifs/ipc/wl_data_source.rs | 89 +- ...zwp_primary_selection_device_manager_v1.rs | 16 +- .../ipc/zwp_primary_selection_device_v1.rs | 167 ++-- src/ifs/ipc/zwp_primary_selection_offer_v1.rs | 43 +- .../ipc/zwp_primary_selection_source_v1.rs | 55 +- src/ifs/wl_seat.rs | 100 +- src/ifs/wl_seat/event_handling.rs | 24 +- src/ifs/wl_seat/pointer_owner.rs | 8 +- src/object.rs | 1 + src/state.rs | 1 + src/xcon.rs | 23 +- src/xcon/consts.rs | 8 + src/xwayland.rs | 54 +- src/xwayland/xwm.rs | 884 +++++++++++++++++- wire-xcon/xfixes.txt | 27 + wire-xcon/xproto.txt | 28 + 22 files changed, 1565 insertions(+), 380 deletions(-) create mode 100644 wire-xcon/xfixes.txt diff --git a/README.md b/README.md index 15a347de..30186f58 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ The following features have been implemented and should work: The following features are known to be missing or broken and will be implemented later: -- Copy/paste between X and Wayland applications - Games that require pointer grabs - Screen locking - Touch and tablet support diff --git a/src/compositor.rs b/src/compositor.rs index b6541586..05e201c2 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -179,6 +179,7 @@ fn start_compositor2( config_dir: config_dir(), config_file_id: NumCell::new(1), tracker: Default::default(), + data_offer_ids: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/ifs/ipc.rs b/src/ifs/ipc.rs index 2f559924..7ef8c41f 100644 --- a/src/ifs/ipc.rs +++ b/src/ifs/ipc.rs @@ -1,8 +1,7 @@ use { crate::{ - client::{Client, ClientId, WaylandObject}, - ifs::wl_seat::WlSeatGlobal, - object::ObjectId, + client::{Client, ClientError, ClientId, WaylandObject}, + ifs::wl_seat::{WlSeatError, WlSeatGlobal}, utils::{ bitflags::BitflagsExt, clonecell::CloneCell, numcell::NumCell, smallmap::SmallMap, }, @@ -32,16 +31,19 @@ pub enum Role { Dnd, } -pub trait Vtable: Sized { - type DeviceId: Eq + Copy; - type OfferId: Eq + Copy + From; - +pub trait IpcVtable: Sized { type Device; type Source; type Offer: WaylandObject; - fn device_id(dd: &Self::Device) -> Self::DeviceId; fn get_device_data(dd: &Self::Device) -> &DeviceData; + fn get_device_seat(dd: &Self::Device) -> Rc; + fn create_xwm_source(client: &Rc) -> Self::Source; + fn set_seat_selection( + seat: &Rc, + source: &Rc, + serial: Option, + ) -> Result<(), WlSeatError>; fn get_offer_data(offer: &Self::Offer) -> &OfferData; fn get_source_data(src: &Self::Source) -> &SourceData; fn for_each_device(seat: &WlSeatGlobal, client: ClientId, f: C) @@ -51,35 +53,35 @@ pub trait Vtable: Sized { client: &Rc, dd: &Rc, data: OfferData, - id: ObjectId, - ) -> Rc; - fn send_selection(dd: &Self::Device, offer: Self::OfferId); - fn send_cancelled(source: &Self::Source); - fn get_offer_id(offer: &Self::Offer) -> Self::OfferId; - fn send_offer(dd: &Self::Device, offer: &Self::Offer); - fn send_mime_type(offer: &Self::Offer, mime_type: &str); + ) -> Result, ClientError>; + fn send_selection(dd: &Self::Device, offer: Option<&Rc>); + fn send_cancelled(source: &Rc); + fn get_offer_id(offer: &Self::Offer) -> u64; + fn send_offer(dd: &Self::Device, offer: &Rc); + fn send_mime_type(offer: &Rc, mime_type: &str); fn unset(seat: &Rc, role: Role); - fn send_send(src: &Self::Source, mime_type: &str, fd: Rc); + fn send_send(src: &Rc, mime_type: &str, fd: Rc); + fn remove_from_seat(device: &Self::Device); + fn get_offer_seat(offer: &Self::Offer) -> Rc; } -pub struct DeviceData { +pub struct DeviceData { selection: CloneCell>>, dnd: CloneCell>>, + pub is_xwm: bool, } -impl Default for DeviceData { - fn default() -> Self { - Self { - selection: Default::default(), - dnd: Default::default(), - } - } -} - -pub struct OfferData { +pub struct OfferData { device: CloneCell>>, source: CloneCell>>, shared: Rc, + pub is_xwm: bool, +} + +impl OfferData { + pub fn source(&self) -> Option> { + self.source.get() + } } #[derive(Debug, Error)] @@ -99,9 +101,9 @@ const OFFER_STATE_DROPPED: u32 = 1 << 2; const SOURCE_STATE_USED: u32 = 1 << 1; const SOURCE_STATE_FINISHED: u32 = 1 << 2; -pub struct SourceData { - seat: CloneCell>>, - offers: SmallMap, 1>, +pub struct SourceData { + pub seat: CloneCell>>, + offers: SmallMap, 1>, offer_client: Cell, mime_types: RefCell>, client: Rc, @@ -109,6 +111,7 @@ pub struct SourceData { actions: Cell>, role: Cell, shared: CloneCell>, + pub is_xwm: bool, } struct SharedState { @@ -131,8 +134,8 @@ impl Default for SharedState { } } -impl SourceData { - fn new(client: &Rc) -> Self { +impl SourceData { + fn new(client: &Rc, is_xwm: bool) -> Self { Self { seat: Default::default(), offers: Default::default(), @@ -143,11 +146,12 @@ impl SourceData { actions: Cell::new(None), role: Cell::new(Role::Selection), shared: Default::default(), + is_xwm, } } } -pub fn attach_seat( +pub fn attach_seat( src: &T::Source, seat: &Rc, role: Role, @@ -173,16 +177,16 @@ pub fn attach_seat( Ok(()) } -pub fn cancel_offers(src: &T::Source) { +pub fn cancel_offers(src: &T::Source) { let data = T::get_source_data(src); while let Some((_, offer)) = data.offers.pop() { let data = T::get_offer_data(&offer); data.source.take(); - destroy_offer::(&offer); + destroy_data_offer::(&offer); } } -pub fn detach_seat(src: &T::Source) { +pub fn detach_seat(src: &Rc) { let data = T::get_source_data(src); data.seat.set(None); cancel_offers::(src); @@ -192,7 +196,7 @@ pub fn detach_seat(src: &T::Source) { // data.client.flush(); } -pub fn offer_source_to(src: &Rc, client: &Rc) { +pub fn offer_source_to(src: &Rc, client: &Rc) { let data = T::get_source_data(src); let seat = match data.seat.get() { Some(a) => a, @@ -206,21 +210,21 @@ pub fn offer_source_to(src: &Rc, client: &Rc) { let shared = data.shared.get(); shared.role.set(data.role.get()); T::for_each_device(&seat, client.id, |dd| { - let id = match client.new_id() { - Ok(id) => id, - Err(e) => { - client.error(e); - return; - } - }; let device_data = T::get_device_data(dd); let offer_data = OfferData { device: CloneCell::new(Some(dd.clone())), source: CloneCell::new(Some(src.clone())), shared: shared.clone(), + is_xwm: device_data.is_xwm, }; - let offer = T::create_offer(client, dd, offer_data, id); - data.offers.insert(id.into(), offer.clone()); + let offer = match T::create_offer(client, dd, offer_data) { + Ok(o) => o, + Err(e) => { + client.error(e); + return; + } + }; + data.offers.insert(T::get_offer_id(&offer), offer.clone()); let mt = data.mime_types.borrow_mut(); T::send_offer(dd, &offer); for mt in mt.deref() { @@ -228,18 +232,20 @@ pub fn offer_source_to(src: &Rc, client: &Rc) { } match data.role.get() { Role::Selection => { - T::send_selection(dd, T::get_offer_id(&offer)); + T::send_selection(dd, Some(&offer)); device_data.selection.set(Some(offer.clone())); } Role::Dnd => { device_data.dnd.set(Some(offer.clone())); } } - client.add_server_obj(&offer); + if !device_data.is_xwm { + client.add_server_obj(&offer); + } }); } -fn add_mime_type(src: &T::Source, mime_type: &str) { +pub fn add_data_source_mime_type(src: &T::Source, mime_type: &str) { let data = T::get_source_data(src); if data.mime_types.borrow_mut().insert(mime_type.to_string()) { for (_, offer) in &data.offers { @@ -250,20 +256,20 @@ fn add_mime_type(src: &T::Source, mime_type: &str) { } } -fn destroy_source(src: &T::Source) { +pub fn destroy_data_source(src: &T::Source) { let data = T::get_source_data(src); if let Some(seat) = data.seat.take() { T::unset(&seat, data.role.get()); } } -fn destroy_offer(offer: &T::Offer) { +pub fn destroy_data_offer(offer: &T::Offer) { let data = T::get_offer_data(offer); if let Some(device) = data.device.take() { let device_data = T::get_device_data(&device); match data.shared.role.get() { Role::Selection => { - T::send_selection(&device, ObjectId::NONE.into()); + T::send_selection(&device, None); device_data.selection.take(); } Role::Dnd => { @@ -285,37 +291,37 @@ fn destroy_offer(offer: &T::Offer) { } } -fn destroy_device(dd: &T::Device) { +pub fn destroy_data_device(dd: &T::Device) { let data = T::get_device_data(dd); let offers = [data.selection.take(), data.dnd.take()]; for offer in offers.into_iter().flat_map(|o| o.into_iter()) { T::get_offer_data(&offer).device.take(); - destroy_offer::(&offer); + destroy_data_offer::(&offer); } } -fn break_source_loops(src: &T::Source) { +fn break_source_loops(src: &T::Source) { let data = T::get_source_data(src); if data.offer_client.get() == data.client.id { data.offers.take(); } - destroy_source::(src); + destroy_data_source::(src); } -fn break_offer_loops(offer: &T::Offer) { +fn break_offer_loops(offer: &T::Offer) { let data = T::get_offer_data(offer); data.device.set(None); - destroy_offer::(offer); + destroy_data_offer::(offer); } -fn break_device_loops(dd: &T::Device) { +fn break_device_loops(dd: &T::Device) { let data = T::get_device_data(dd); data.selection.take(); data.dnd.take(); - destroy_device::(dd); + destroy_data_device::(dd); } -fn receive(offer: &T::Offer, mime_type: &str, fd: Rc) { +pub fn receive_data_offer(offer: &T::Offer, mime_type: &str, fd: Rc) { let data = T::get_offer_data(offer); if let Some(src) = data.source.get() { T::send_send(&src, mime_type, fd); diff --git a/src/ifs/ipc/wl_data_device.rs b/src/ifs/ipc/wl_data_device.rs index 377b755b..65207396 100644 --- a/src/ifs/ipc/wl_data_device.rs +++ b/src/ifs/ipc/wl_data_device.rs @@ -4,17 +4,17 @@ use { fixed::Fixed, ifs::{ ipc::{ - break_device_loops, destroy_device, wl_data_device_manager::WlDataDeviceManager, - wl_data_offer::WlDataOffer, wl_data_source::WlDataSource, DeviceData, OfferData, - Role, SourceData, Vtable, + break_device_loops, destroy_data_device, wl_data_offer::WlDataOffer, + wl_data_source::WlDataSource, DeviceData, IpcVtable, OfferData, Role, SourceData, }, - wl_seat::{WlSeat, WlSeatError, WlSeatGlobal}, + wl_seat::{WlSeatError, WlSeatGlobal}, wl_surface::{SurfaceRole, WlSurfaceError}, }, leaks::Tracker, - object::{Object, ObjectId}, + object::Object, utils::buffd::{MsgParser, MsgParserError}, - wire::{wl_data_device::*, WlDataDeviceId, WlDataOfferId, WlSurfaceId}, + wire::{wl_data_device::*, WlDataDeviceId, WlDataOfferId, WlDataSourceId, WlSurfaceId}, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -26,39 +26,73 @@ const ROLE: u32 = 0; pub struct WlDataDevice { pub id: WlDataDeviceId, - pub manager: Rc, - pub seat: Rc, - pub data: DeviceData, + pub client: Rc, + pub version: u32, + pub seat: Rc, + pub data: DeviceData, pub tracker: Tracker, } impl WlDataDevice { - pub fn new(id: WlDataDeviceId, manager: &Rc, seat: &Rc) -> Self { + pub fn new( + id: WlDataDeviceId, + client: &Rc, + version: u32, + seat: &Rc, + is_xwm: bool, + ) -> Self { Self { id, - manager: manager.clone(), + client: client.clone(), + version, seat: seat.clone(), - data: Default::default(), + data: DeviceData { + selection: Default::default(), + dnd: Default::default(), + is_xwm, + }, tracker: Default::default(), } } - pub fn send_data_offer(&self, id: WlDataOfferId) { - self.manager.client.event(DataOffer { - self_id: self.id, - id, - }) + pub fn send_data_offer(&self, offer: &Rc) { + if self.data.is_xwm { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::ClipboardSetOffer(offer.clone())); + } else { + self.client.event(DataOffer { + self_id: self.id, + id: offer.id, + }) + } } - pub fn send_selection(&self, id: WlDataOfferId) { - self.manager.client.event(Selection { - self_id: self.id, - id, - }) + pub fn send_selection(&self, offer: Option<&Rc>) { + if self.data.is_xwm { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::ClipboardSetSelection( + self.seat.id(), + offer.cloned(), + )); + } else { + let id = offer.map(|o| o.id).unwrap_or(WlDataOfferId::NONE); + self.client.event(Selection { + self_id: self.id, + id, + }) + } } pub fn send_leave(&self) { - self.manager.client.event(Leave { self_id: self.id }) + if !self.data.is_xwm { + self.client.event(Leave { self_id: self.id }) + } } pub fn send_enter( @@ -69,101 +103,113 @@ impl WlDataDevice { offer: WlDataOfferId, serial: u32, ) { - self.manager.client.event(Enter { - self_id: self.id, - serial, - surface, - x, - y, - id: offer, - }) + if !self.data.is_xwm { + self.client.event(Enter { + self_id: self.id, + serial, + surface, + x, + y, + id: offer, + }) + } } pub fn send_motion(&self, x: Fixed, y: Fixed) { - self.manager.client.event(Motion { - self_id: self.id, - time: 0, - x, - y, - }) + if !self.data.is_xwm { + self.client.event(Motion { + self_id: self.id, + time: 0, + x, + y, + }) + } } pub fn send_drop(&self) { - self.manager.client.event(Drop { self_id: self.id }) + if !self.data.is_xwm { + self.client.event(Drop { self_id: self.id }) + } } fn start_drag(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataDeviceError> { - let req: StartDrag = self.manager.client.parse(self, parser)?; - if !self.manager.client.valid_serial(req.serial) { + let req: StartDrag = self.client.parse(self, parser)?; + if !self.client.valid_serial(req.serial) { log::warn!("Client tried to start_drag with an invalid serial"); return Ok(()); } - let origin = self.manager.client.lookup(req.origin)?; + let origin = self.client.lookup(req.origin)?; let source = if req.source.is_some() { - Some(self.manager.client.lookup(req.source)?) + Some(self.client.lookup(req.source)?) } else { None }; let icon = if req.icon.is_some() { - let icon = self.manager.client.lookup(req.icon)?; + let icon = self.client.lookup(req.icon)?; icon.set_role(SurfaceRole::DndIcon)?; Some(icon) } else { None }; - self.seat - .global - .start_drag(&origin, source, icon, req.serial)?; + self.seat.start_drag(&origin, source, icon, req.serial)?; Ok(()) } fn set_selection(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataDeviceError> { - let req: SetSelection = self.manager.client.parse(self, parser)?; - if !self.manager.client.valid_serial(req.serial) { + let req: SetSelection = self.client.parse(self, parser)?; + if !self.client.valid_serial(req.serial) { log::warn!("Client tried to set_selection with an invalid serial"); return Ok(()); } - if !self - .seat - .global - .may_modify_selection(&self.seat.client, req.serial) - { + if !self.seat.may_modify_selection(&self.client, req.serial) { log::warn!("Ignoring disallowed set_selection request"); return Ok(()); } let src = if req.source.is_none() { None } else { - Some(self.manager.client.lookup(req.source)?) + Some(self.client.lookup(req.source)?) }; - self.seat.global.set_selection(src, Some(req.serial))?; + self.seat.set_selection(src, Some(req.serial))?; Ok(()) } fn release(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataDeviceError> { - let _req: Release = self.manager.client.parse(self, parser)?; - destroy_device::(self); + let _req: Release = self.client.parse(self, parser)?; + destroy_data_device::(self); self.seat.remove_data_device(self); - self.manager.client.remove_obj(self)?; + self.client.remove_obj(self)?; Ok(()) } } -impl Vtable for WlDataDevice { - type DeviceId = WlDataDeviceId; - type OfferId = WlDataOfferId; +pub struct ClipboardIpc; + +impl IpcVtable for ClipboardIpc { type Device = WlDataDevice; type Source = WlDataSource; type Offer = WlDataOffer; - fn device_id(dd: &Self::Device) -> Self::DeviceId { - dd.id - } - fn get_device_data(dd: &Self::Device) -> &DeviceData { &dd.data } + fn get_device_seat(dd: &Self::Device) -> Rc { + dd.seat.clone() + } + + fn create_xwm_source(client: &Rc) -> Self::Source { + WlDataSource::new(WlDataSourceId::NONE, client, true) + } + + fn set_seat_selection( + seat: &Rc, + source: &Rc, + serial: Option, + ) -> Result<(), WlSeatError> { + seat.set_selection(Some(source.clone()), serial) + } + fn get_offer_data(offer: &Self::Offer) -> &OfferData { &offer.data } @@ -183,36 +229,36 @@ impl Vtable for WlDataDevice { client: &Rc, device: &Rc, offer_data: OfferData, - id: ObjectId, - ) -> Rc { + ) -> Result, ClientError> { let rc = Rc::new(WlDataOffer { - id: id.into(), + id: client.new_id()?, + u64_id: client.state.data_offer_ids.fetch_add(1), client: client.clone(), device: device.clone(), data: offer_data, tracker: Default::default(), }); track!(client, rc); - rc + Ok(rc) } - fn send_selection(dd: &Self::Device, offer: Self::OfferId) { + fn send_selection(dd: &Self::Device, offer: Option<&Rc>) { dd.send_selection(offer); } - fn send_cancelled(source: &Self::Source) { + fn send_cancelled(source: &Rc) { source.send_cancelled(); } - fn get_offer_id(offer: &Self::Offer) -> Self::OfferId { - offer.id + fn get_offer_id(offer: &Self::Offer) -> u64 { + offer.u64_id } - fn send_offer(dd: &Self::Device, offer: &Self::Offer) { - dd.send_data_offer(offer.id); + fn send_offer(dd: &Self::Device, offer: &Rc) { + dd.send_data_offer(offer); } - fn send_mime_type(offer: &Self::Offer, mime_type: &str) { + fn send_mime_type(offer: &Rc, mime_type: &str) { offer.send_offer(mime_type); } @@ -223,9 +269,17 @@ impl Vtable for WlDataDevice { } } - fn send_send(src: &Self::Source, mime_type: &str, fd: Rc) { + fn send_send(src: &Rc, mime_type: &str, fd: Rc) { src.send_send(mime_type, fd); } + + fn remove_from_seat(device: &Self::Device) { + device.seat.remove_data_device(device); + } + + fn get_offer_seat(offer: &Self::Offer) -> Rc { + offer.device.seat.clone() + } } object_base! { @@ -242,7 +296,7 @@ impl Object for WlDataDevice { } fn break_loops(&self) { - break_device_loops::(self); + break_device_loops::(self); self.seat.remove_data_device(self); } } diff --git a/src/ifs/ipc/wl_data_device_manager.rs b/src/ifs/ipc/wl_data_device_manager.rs index 1871dd56..dc4178c7 100644 --- a/src/ifs/ipc/wl_data_device_manager.rs +++ b/src/ifs/ipc/wl_data_device_manager.rs @@ -61,7 +61,7 @@ impl WlDataDeviceManager { parser: MsgParser<'_, '_>, ) -> Result<(), WlDataDeviceManagerError> { let req: CreateDataSource = self.client.parse(self, parser)?; - let res = Rc::new(WlDataSource::new(req.id, &self.client)); + let res = Rc::new(WlDataSource::new(req.id, &self.client, false)); track!(self.client, res); self.client.add_client_obj(&res)?; Ok(()) @@ -73,9 +73,15 @@ impl WlDataDeviceManager { ) -> Result<(), WlDataDeviceManagerError> { let req: GetDataDevice = self.client.parse(&**self, parser)?; let seat = self.client.lookup(req.seat)?; - let dev = Rc::new(WlDataDevice::new(req.id, self, &seat)); + let dev = Rc::new(WlDataDevice::new( + req.id, + &self.client, + self.version, + &seat.global, + false, + )); track!(self.client, dev); - seat.add_data_device(&dev); + seat.global.add_data_device(&dev); self.client.add_client_obj(&dev)?; Ok(()) } diff --git a/src/ifs/ipc/wl_data_offer.rs b/src/ifs/ipc/wl_data_offer.rs index f1f2f58d..d55645ee 100644 --- a/src/ifs/ipc/wl_data_offer.rs +++ b/src/ifs/ipc/wl_data_offer.rs @@ -2,9 +2,11 @@ use { crate::{ client::{Client, ClientError}, ifs::ipc::{ - break_offer_loops, destroy_offer, receive, wl_data_device::WlDataDevice, - wl_data_device_manager::DND_ALL, OfferData, Role, OFFER_STATE_ACCEPTED, - OFFER_STATE_DROPPED, OFFER_STATE_FINISHED, SOURCE_STATE_FINISHED, + break_offer_loops, destroy_data_offer, receive_data_offer, + wl_data_device::{ClipboardIpc, WlDataDevice}, + wl_data_device_manager::DND_ALL, + OfferData, Role, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, OFFER_STATE_FINISHED, + SOURCE_STATE_FINISHED, }, leaks::Tracker, object::Object, @@ -13,6 +15,7 @@ use { buffd::{MsgParser, MsgParserError}, }, wire::{wl_data_offer::*, WlDataOfferId}, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -29,36 +32,54 @@ const INVALID_OFFER: u32 = 3; pub struct WlDataOffer { pub id: WlDataOfferId, + pub u64_id: u64, pub client: Rc, pub device: Rc, - pub data: OfferData, + pub data: OfferData, pub tracker: Tracker, } impl WlDataOffer { - pub fn send_offer(&self, mime_type: &str) { - self.client.event(Offer { - self_id: self.id, - mime_type, - }) + pub fn send_offer(self: &Rc, mime_type: &str) { + if self.data.is_xwm { + if let Some(src) = self.data.source.get() { + if !src.data.is_xwm { + self.client.state.xwayland.queue.push( + XWaylandEvent::ClipboardAddOfferMimeType( + self.clone(), + mime_type.to_string(), + ), + ); + } + } + } else { + self.client.event(Offer { + self_id: self.id, + mime_type, + }) + } } pub fn send_source_actions(&self) { - if let Some(src) = self.data.source.get() { - if let Some(source_actions) = src.data.actions.get() { - self.client.event(SourceActions { - self_id: self.id, - source_actions, - }) + if !self.data.is_xwm { + if let Some(src) = self.data.source.get() { + if let Some(source_actions) = src.data.actions.get() { + self.client.event(SourceActions { + self_id: self.id, + source_actions, + }) + } } } } pub fn send_action(&self, dnd_action: u32) { - self.client.event(Action { - self_id: self.id, - dnd_action, - }) + if !self.data.is_xwm { + self.client.event(Action { + self_id: self.id, + dnd_action, + }) + } } fn accept(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataOfferError> { @@ -85,13 +106,13 @@ impl WlDataOffer { if self.data.shared.state.get().contains(OFFER_STATE_FINISHED) { return Err(WlDataOfferError::AlreadyFinished); } - receive::(self, req.mime_type, req.fd); + receive_data_offer::(self, req.mime_type, req.fd); Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataOfferError> { let _req: Destroy = self.client.parse(self, parser)?; - destroy_offer::(self); + destroy_data_offer::(self); self.client.remove_obj(self)?; Ok(()) } @@ -162,7 +183,7 @@ impl Object for WlDataOffer { } fn break_loops(&self) { - break_offer_loops::(self); + break_offer_loops::(self); } } diff --git a/src/ifs/ipc/wl_data_source.rs b/src/ifs/ipc/wl_data_source.rs index ecc566ec..fbe92ff2 100644 --- a/src/ifs/ipc/wl_data_source.rs +++ b/src/ifs/ipc/wl_data_source.rs @@ -2,8 +2,8 @@ use { crate::{ client::{Client, ClientError}, ifs::ipc::{ - add_mime_type, break_source_loops, cancel_offers, destroy_source, - wl_data_device::WlDataDevice, + add_data_source_mime_type, break_source_loops, cancel_offers, destroy_data_source, + wl_data_device::ClipboardIpc, wl_data_device_manager::{DND_ALL, DND_NONE}, wl_data_offer::WlDataOffer, SharedState, SourceData, OFFER_STATE_ACCEPTED, OFFER_STATE_DROPPED, @@ -15,6 +15,7 @@ use { buffd::{MsgParser, MsgParserError}, }, wire::{wl_data_source::*, WlDataSourceId}, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -28,16 +29,16 @@ const INVALID_SOURCE: u32 = 1; pub struct WlDataSource { pub id: WlDataSourceId, - pub data: SourceData, + pub data: SourceData, pub tracker: Tracker, } impl WlDataSource { - pub fn new(id: WlDataSourceId, client: &Rc) -> Self { + pub fn new(id: WlDataSourceId, client: &Rc, is_xwm: bool) -> Self { Self { id, tracker: Default::default(), - data: SourceData::new(client), + data: SourceData::new(client, is_xwm), } } @@ -55,7 +56,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); } pub fn update_selected_action(&self) { @@ -102,51 +103,81 @@ impl WlDataSource { shared.state.or_assign(OFFER_STATE_DROPPED); } - pub fn send_cancelled(&self) { - self.data.client.event(Cancelled { self_id: self.id }) + pub fn send_cancelled(self: &Rc) { + if self.data.is_xwm { + self.data + .client + .state + .xwayland + .queue + .push(XWaylandEvent::ClipboardCancelSource(self.clone())); + } else { + self.data.client.event(Cancelled { self_id: self.id }) + } } - 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_send(self: &Rc, mime_type: &str, fd: Rc) { + if self.data.is_xwm { + self.data + .client + .state + .xwayland + .queue + .push(XWaylandEvent::ClipboardSendSource( + self.clone(), + mime_type.to_string(), + fd, + )); + } else { + self.data.client.event(Send { + self_id: self.id, + mime_type, + fd, + }) + } } pub fn send_target(&self, mime_type: Option<&str>) { - self.data.client.event(Target { - self_id: self.id, - mime_type, - }) + if !self.data.is_xwm { + self.data.client.event(Target { + self_id: self.id, + mime_type, + }) + } } pub fn send_dnd_finished(&self) { - self.data.client.event(DndFinished { self_id: self.id }) + if !self.data.is_xwm { + self.data.client.event(DndFinished { self_id: self.id }) + } } pub fn send_action(&self, dnd_action: u32) { - self.data.client.event(Action { - self_id: self.id, - dnd_action, - }) + if !self.data.is_xwm { + self.data.client.event(Action { + self_id: self.id, + dnd_action, + }) + } } pub fn send_dnd_drop_performed(&self) { - self.data - .client - .event(DndDropPerformed { self_id: self.id }) + if !self.data.is_xwm { + self.data + .client + .event(DndDropPerformed { self_id: self.id }) + } } fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataSourceError> { let req: Offer = self.data.client.parse(self, parser)?; - add_mime_type::(self, req.mime_type); + add_data_source_mime_type::(self, req.mime_type); Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), WlDataSourceError> { let _req: Destroy = self.data.client.parse(self, parser)?; - destroy_source::(self); + destroy_data_source::(self); self.data.client.remove_obj(self)?; Ok(()) } @@ -178,7 +209,7 @@ impl Object for WlDataSource { } fn break_loops(&self) { - break_source_loops::(self); + break_source_loops::(self); } } diff --git a/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs b/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs index 86226e67..c0549def 100644 --- a/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_device_manager_v1.rs @@ -55,7 +55,11 @@ impl ZwpPrimarySelectionDeviceManagerV1 { parser: MsgParser<'_, '_>, ) -> Result<(), ZwpPrimarySelectionDeviceManagerV1Error> { let req: CreateSource = self.client.parse(self, parser)?; - let res = Rc::new(ZwpPrimarySelectionSourceV1::new(req.id, &self.client)); + let res = Rc::new(ZwpPrimarySelectionSourceV1::new( + req.id, + &self.client, + false, + )); track!(self.client, res); self.client.add_client_obj(&res)?; Ok(()) @@ -67,9 +71,15 @@ impl ZwpPrimarySelectionDeviceManagerV1 { ) -> Result<(), ZwpPrimarySelectionDeviceManagerV1Error> { let req: GetDevice = self.client.parse(&**self, parser)?; let seat = self.client.lookup(req.seat)?; - let dev = Rc::new(ZwpPrimarySelectionDeviceV1::new(req.id, self, &seat)); + let dev = Rc::new(ZwpPrimarySelectionDeviceV1::new( + req.id, + &self.client, + self.version, + &seat.global, + false, + )); track!(self.client, dev); - seat.add_primary_selection_device(&dev); + seat.global.add_primary_selection_device(&dev); self.client.add_client_obj(&dev)?; Ok(()) } diff --git a/src/ifs/ipc/zwp_primary_selection_device_v1.rs b/src/ifs/ipc/zwp_primary_selection_device_v1.rs index 3921bdb2..7d699ecb 100644 --- a/src/ifs/ipc/zwp_primary_selection_device_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_device_v1.rs @@ -3,21 +3,21 @@ use { client::{Client, ClientError, ClientId}, ifs::{ ipc::{ - break_device_loops, destroy_device, - zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1, + break_device_loops, destroy_data_device, zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, DeviceData, - OfferData, Role, SourceData, Vtable, + IpcVtable, OfferData, Role, SourceData, }, - wl_seat::{WlSeat, WlSeatError, WlSeatGlobal}, + wl_seat::{WlSeatError, WlSeatGlobal}, }, leaks::Tracker, - object::{Object, ObjectId}, + object::Object, utils::buffd::{MsgParser, MsgParserError}, wire::{ zwp_primary_selection_device_v1::*, ZwpPrimarySelectionDeviceV1Id, - ZwpPrimarySelectionOfferV1Id, + ZwpPrimarySelectionOfferV1Id, ZwpPrimarySelectionSourceV1Id, }, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -26,54 +26,83 @@ use { pub struct ZwpPrimarySelectionDeviceV1 { pub id: ZwpPrimarySelectionDeviceV1Id, - pub manager: Rc, - seat: Rc, - data: DeviceData, + pub client: Rc, + pub version: u32, + pub seat: Rc, + data: DeviceData, pub tracker: Tracker, } impl ZwpPrimarySelectionDeviceV1 { pub fn new( id: ZwpPrimarySelectionDeviceV1Id, - manager: &Rc, - seat: &Rc, + client: &Rc, + version: u32, + seat: &Rc, + is_xwm: bool, ) -> Self { Self { id, - manager: manager.clone(), + client: client.clone(), + version, seat: seat.clone(), - data: DeviceData::default(), + data: DeviceData { + selection: Default::default(), + dnd: Default::default(), + is_xwm, + }, tracker: Default::default(), } } - pub fn send_data_offer(&self, offer: ZwpPrimarySelectionOfferV1Id) { - self.manager.client.event(DataOffer { - self_id: self.id, - offer, - }) + pub fn send_data_offer(&self, offer: &Rc) { + if self.data.is_xwm { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::PrimarySelectionSetOffer(offer.clone())); + } else { + self.client.event(DataOffer { + self_id: self.id, + offer: offer.id, + }) + } } - pub fn send_selection(&self, id: ZwpPrimarySelectionOfferV1Id) { - self.manager.client.event(Selection { - self_id: self.id, - id, - }) + pub fn send_selection(&self, offer: Option<&Rc>) { + if self.data.is_xwm { + self.client + .state + .xwayland + .queue + .push(XWaylandEvent::PrimarySelectionSetSelection( + self.seat.id(), + offer.cloned(), + )); + } else { + let id = offer + .map(|o| o.id) + .unwrap_or(ZwpPrimarySelectionOfferV1Id::NONE); + self.client.event(Selection { + self_id: self.id, + id, + }) + } } fn set_selection( &self, parser: MsgParser<'_, '_>, ) -> Result<(), ZwpPrimarySelectionDeviceV1Error> { - let req: SetSelection = self.manager.client.parse(self, parser)?; - if !self.manager.client.valid_serial(req.serial) { + let req: SetSelection = self.client.parse(self, parser)?; + if !self.client.valid_serial(req.serial) { log::warn!("Client tried to set_selection with an invalid serial"); return Ok(()); } if !self .seat - .global - .may_modify_primary_selection(&self.seat.client, req.serial) + .may_modify_primary_selection(&self.client, Some(req.serial)) { log::warn!("Ignoring disallowed set_selection request"); return Ok(()); @@ -81,40 +110,50 @@ impl ZwpPrimarySelectionDeviceV1 { let src = if req.source.is_none() { None } else { - Some(self.manager.client.lookup(req.source)?) + Some(self.client.lookup(req.source)?) }; - self.seat - .global - .set_primary_selection(src, Some(req.serial))?; + self.seat.set_primary_selection(src, Some(req.serial))?; Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionDeviceV1Error> { - let _req: Destroy = self.manager.client.parse(self, parser)?; - destroy_device::(self); + let _req: Destroy = self.client.parse(self, parser)?; + destroy_data_device::(self); self.seat.remove_primary_selection_device(self); - self.manager.client.remove_obj(self)?; + self.client.remove_obj(self)?; Ok(()) } } -impl Vtable for ZwpPrimarySelectionDeviceV1 { - type DeviceId = ZwpPrimarySelectionDeviceV1Id; - type OfferId = ZwpPrimarySelectionOfferV1Id; +pub struct PrimarySelectionIpc; + +impl IpcVtable for PrimarySelectionIpc { type Device = ZwpPrimarySelectionDeviceV1; type Source = ZwpPrimarySelectionSourceV1; type Offer = ZwpPrimarySelectionOfferV1; - fn device_id(dd: &Self::Device) -> Self::DeviceId { - dd.id - } - fn get_device_data(dd: &Self::Device) -> &DeviceData { &dd.data } + fn get_device_seat(dd: &Self::Device) -> Rc { + dd.seat.clone() + } + + fn create_xwm_source(client: &Rc) -> Self::Source { + ZwpPrimarySelectionSourceV1::new(ZwpPrimarySelectionSourceV1Id::NONE, client, true) + } + + fn set_seat_selection( + seat: &Rc, + source: &Rc, + serial: Option, + ) -> Result<(), WlSeatError> { + seat.set_primary_selection(Some(source.clone()), serial) + } + fn get_offer_data(offer: &Self::Offer) -> &OfferData { - &offer.offer_data + &offer.data } fn get_source_data(src: &Self::Source) -> &SourceData { @@ -130,37 +169,43 @@ impl Vtable for ZwpPrimarySelectionDeviceV1 { fn create_offer( client: &Rc, - _device: &Rc, + device: &Rc, offer_data: OfferData, - id: ObjectId, - ) -> Rc { + ) -> Result, ClientError> { + let id = if device.data.is_xwm { + ZwpPrimarySelectionOfferV1Id::NONE + } else { + client.new_id()? + }; let rc = Rc::new(ZwpPrimarySelectionOfferV1 { - id: id.into(), + id, + u64_id: client.state.data_offer_ids.fetch_add(1), + seat: device.seat.clone(), client: client.clone(), - offer_data, + data: offer_data, tracker: Default::default(), }); track!(client, rc); - rc + Ok(rc) } - fn send_selection(dd: &Self::Device, offer: Self::OfferId) { + fn send_selection(dd: &Self::Device, offer: Option<&Rc>) { dd.send_selection(offer); } - fn send_cancelled(source: &Self::Source) { + fn send_cancelled(source: &Rc) { source.send_cancelled(); } - fn get_offer_id(offer: &Self::Offer) -> Self::OfferId { - offer.id + fn get_offer_id(offer: &Self::Offer) -> u64 { + offer.u64_id } - fn send_offer(dd: &Self::Device, offer: &Self::Offer) { - dd.send_data_offer(offer.id); + fn send_offer(dd: &Self::Device, offer: &Rc) { + dd.send_data_offer(offer); } - fn send_mime_type(offer: &Self::Offer, mime_type: &str) { + fn send_mime_type(offer: &Rc, mime_type: &str) { offer.send_offer(mime_type); } @@ -168,9 +213,17 @@ impl Vtable for ZwpPrimarySelectionDeviceV1 { seat.unset_primary_selection(); } - fn send_send(src: &Self::Source, mime_type: &str, fd: Rc) { + fn send_send(src: &Rc, mime_type: &str, fd: Rc) { src.send_send(mime_type, fd); } + + fn remove_from_seat(device: &Self::Device) { + device.seat.remove_primary_selection_device(device); + } + + fn get_offer_seat(offer: &Self::Offer) -> Rc { + offer.seat.clone() + } } object_base! { @@ -186,7 +239,7 @@ impl Object for ZwpPrimarySelectionDeviceV1 { } fn break_loops(&self) { - break_device_loops::(self); + break_device_loops::(self); self.seat.remove_primary_selection_device(self); } } diff --git a/src/ifs/ipc/zwp_primary_selection_offer_v1.rs b/src/ifs/ipc/zwp_primary_selection_offer_v1.rs index 81db26b5..db02a0b0 100644 --- a/src/ifs/ipc/zwp_primary_selection_offer_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_offer_v1.rs @@ -1,14 +1,18 @@ use { crate::{ client::{Client, ClientError}, - ifs::ipc::{ - break_offer_loops, destroy_offer, receive, - zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, OfferData, + ifs::{ + ipc::{ + break_offer_loops, destroy_data_offer, receive_data_offer, + zwp_primary_selection_device_v1::PrimarySelectionIpc, OfferData, + }, + wl_seat::WlSeatGlobal, }, leaks::Tracker, object::Object, utils::buffd::{MsgParser, MsgParserError}, wire::{zwp_primary_selection_offer_v1::*, ZwpPrimarySelectionOfferV1Id}, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -16,28 +20,43 @@ use { pub struct ZwpPrimarySelectionOfferV1 { pub id: ZwpPrimarySelectionOfferV1Id, + pub u64_id: u64, + pub seat: Rc, pub client: Rc, - pub offer_data: OfferData, + pub data: OfferData, pub tracker: Tracker, } impl ZwpPrimarySelectionOfferV1 { - pub fn send_offer(&self, mime_type: &str) { - self.client.event(Offer { - self_id: self.id, - mime_type, - }) + pub fn send_offer(self: &Rc, mime_type: &str) { + if self.data.is_xwm { + if let Some(src) = self.data.source.get() { + if !src.data.is_xwm { + self.client.state.xwayland.queue.push( + XWaylandEvent::PrimarySelectionAddOfferMimeType( + self.clone(), + mime_type.to_string(), + ), + ); + } + } + } else { + self.client.event(Offer { + self_id: self.id, + mime_type, + }) + } } fn receive(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionOfferV1Error> { let req: Receive = self.client.parse(self, parser)?; - receive::(self, req.mime_type, req.fd); + receive_data_offer::(self, req.mime_type, req.fd); Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionOfferV1Error> { let _req: Destroy = self.client.parse(self, parser)?; - destroy_offer::(self); + destroy_data_offer::(self); self.client.remove_obj(self)?; Ok(()) } @@ -56,7 +75,7 @@ impl Object for ZwpPrimarySelectionOfferV1 { } fn break_loops(&self) { - break_offer_loops::(self); + break_offer_loops::(self); } } diff --git a/src/ifs/ipc/zwp_primary_selection_source_v1.rs b/src/ifs/ipc/zwp_primary_selection_source_v1.rs index ab750c86..ef42148f 100644 --- a/src/ifs/ipc/zwp_primary_selection_source_v1.rs +++ b/src/ifs/ipc/zwp_primary_selection_source_v1.rs @@ -2,13 +2,14 @@ use { crate::{ client::{Client, ClientError}, ifs::ipc::{ - add_mime_type, break_source_loops, destroy_source, - zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, SourceData, + add_data_source_mime_type, break_source_loops, destroy_data_source, + zwp_primary_selection_device_v1::PrimarySelectionIpc, SourceData, }, leaks::Tracker, object::Object, utils::buffd::{MsgParser, MsgParserError}, wire::{zwp_primary_selection_source_v1::*, ZwpPrimarySelectionSourceV1Id}, + xwayland::XWaylandEvent, }, std::rc::Rc, thiserror::Error, @@ -17,40 +18,62 @@ use { pub struct ZwpPrimarySelectionSourceV1 { pub id: ZwpPrimarySelectionSourceV1Id, - pub data: SourceData, + pub data: SourceData, pub tracker: Tracker, } impl ZwpPrimarySelectionSourceV1 { - pub fn new(id: ZwpPrimarySelectionSourceV1Id, client: &Rc) -> Self { + pub fn new(id: ZwpPrimarySelectionSourceV1Id, client: &Rc, is_xwm: bool) -> Self { Self { id, - data: SourceData::new(client), + data: SourceData::new(client, is_xwm), tracker: Default::default(), } } - pub fn send_cancelled(&self) { - self.data.client.event(Cancelled { self_id: self.id }) + pub fn send_cancelled(self: &Rc) { + if self.data.is_xwm { + self.data + .client + .state + .xwayland + .queue + .push(XWaylandEvent::PrimarySelectionCancelSource(self.clone())); + } else { + self.data.client.event(Cancelled { self_id: self.id }); + } } - 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_send(self: &Rc, mime_type: &str, fd: Rc) { + if self.data.is_xwm { + self.data + .client + .state + .xwayland + .queue + .push(XWaylandEvent::PrimarySelectionSendSource( + self.clone(), + mime_type.to_string(), + fd, + )); + } else { + self.data.client.event(Send { + self_id: self.id, + mime_type, + fd, + }) + } } fn offer(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionSourceV1Error> { let req: Offer = self.data.client.parse(self, parser)?; - add_mime_type::(self, req.mime_type); + add_data_source_mime_type::(self, req.mime_type); Ok(()) } fn destroy(&self, parser: MsgParser<'_, '_>) -> Result<(), ZwpPrimarySelectionSourceV1Error> { let _req: Destroy = self.data.client.parse(self, parser)?; - destroy_source::(self); + destroy_data_source::(self); self.data.client.remove_obj(self)?; Ok(()) } @@ -69,7 +92,7 @@ impl Object for ZwpPrimarySelectionSourceV1 { } fn break_loops(&self) { - break_source_loops::(self); + break_source_loops::(self); } } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index dc4043ee..6e6ae7fd 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -18,9 +18,13 @@ use { ifs::{ ipc, ipc::{ - wl_data_device::WlDataDevice, wl_data_source::WlDataSource, - zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, - zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, IpcError, + wl_data_device::{ClipboardIpc, WlDataDevice}, + wl_data_source::WlDataSource, + zwp_primary_selection_device_v1::{ + PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, + }, + zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + IpcError, }, wl_seat::{ kb_owner::KbOwnerHolder, @@ -33,7 +37,7 @@ use { wl_surface::WlSurface, }, leaks::Tracker, - object::{Object, ObjectId}, + object::Object, state::State, tree::{ generic_node_visitor, ContainerNode, ContainerSplit, FloatNode, FoundNode, Node, @@ -97,7 +101,7 @@ pub struct DroppedDnd { impl Drop for DroppedDnd { fn drop(&mut self) { if let Some(src) = self.dnd.src.take() { - ipc::detach_seat::(&src); + ipc::detach_seat::(&src); } } } @@ -199,6 +203,40 @@ impl WlSeatGlobal { slf } + pub fn add_data_device(&self, device: &Rc) { + let mut dd = self.data_devices.borrow_mut(); + dd.entry(device.client.id) + .or_default() + .insert(device.id, device.clone()); + } + + pub fn remove_data_device(&self, device: &WlDataDevice) { + let mut dd = self.data_devices.borrow_mut(); + if let Entry::Occupied(mut e) = dd.entry(device.client.id) { + e.get_mut().remove(&device.id); + if e.get().is_empty() { + e.remove(); + } + } + } + + pub fn add_primary_selection_device(&self, device: &Rc) { + let mut dd = self.primary_selection_devices.borrow_mut(); + dd.entry(device.client.id) + .or_default() + .insert(device.id, device.clone()); + } + + pub fn remove_primary_selection_device(&self, device: &ZwpPrimarySelectionDeviceV1) { + let mut dd = self.primary_selection_devices.borrow_mut(); + if let Entry::Occupied(mut e) = dd.entry(device.client.id) { + e.get_mut().remove(&device.id); + if e.get().is_empty() { + e.remove(); + } + } + } + pub fn get_output(&self) -> Rc { self.output.get() } @@ -460,7 +498,7 @@ impl WlSeatGlobal { } } - fn set_selection_( + fn set_selection_( self: &Rc, field: &CloneCell>>, src: Option>, @@ -475,7 +513,7 @@ impl WlSeatGlobal { match src { Some(src) => ipc::offer_source_to::(&src, &client), _ => T::for_each_device(self, client.id, |device| { - T::send_selection(device, ObjectId::NONE.into()); + T::send_selection(device, None); }), } // client.flush(); @@ -510,7 +548,7 @@ impl WlSeatGlobal { if let Some(serial) = serial { self.selection_serial.set(serial); } - self.set_selection_::(&self.selection, selection) + self.set_selection_::(&self.selection, selection) } pub fn may_modify_selection(&self, client: &Rc, serial: u32) -> bool { @@ -521,10 +559,12 @@ impl WlSeatGlobal { self.keyboard_node.get().node_client_id() == Some(client.id) } - pub fn may_modify_primary_selection(&self, client: &Rc, serial: u32) -> bool { - let dist = serial.wrapping_sub(self.primary_selection_serial.get()) as i32; - if dist < 0 { - return false; + pub fn may_modify_primary_selection(&self, client: &Rc, serial: Option) -> bool { + if let Some(serial) = serial { + let dist = serial.wrapping_sub(self.primary_selection_serial.get()) as i32; + if dist < 0 { + return false; + } } self.keyboard_node.get().node_client_id() == Some(client.id) || self.pointer_node().and_then(|n| n.node_client_id()) == Some(client.id) @@ -542,7 +582,7 @@ impl WlSeatGlobal { if let Some(serial) = serial { self.primary_selection_serial.set(serial); } - self.set_selection_::(&self.primary_selection, selection) + self.set_selection_::(&self.primary_selection, selection) } pub fn set_known_cursor(&self, cursor: KnownCursor) { @@ -710,40 +750,6 @@ impl WlSeat { }) } - pub fn add_data_device(&self, device: &Rc) { - let mut dd = self.global.data_devices.borrow_mut(); - dd.entry(self.client.id) - .or_default() - .insert(device.id, device.clone()); - } - - pub fn remove_data_device(&self, device: &WlDataDevice) { - let mut dd = self.global.data_devices.borrow_mut(); - if let Entry::Occupied(mut e) = dd.entry(self.client.id) { - e.get_mut().remove(&device.id); - if e.get().is_empty() { - e.remove(); - } - } - } - - pub fn add_primary_selection_device(&self, device: &Rc) { - let mut dd = self.global.primary_selection_devices.borrow_mut(); - dd.entry(self.client.id) - .or_default() - .insert(device.id, device.clone()); - } - - pub fn remove_primary_selection_device(&self, device: &ZwpPrimarySelectionDeviceV1) { - let mut dd = self.global.primary_selection_devices.borrow_mut(); - if let Entry::Occupied(mut e) = dd.entry(self.client.id) { - e.get_mut().remove(&device.id); - if e.get().is_empty() { - e.remove(); - } - } - } - pub fn move_(&self, node: &Rc) { self.global.move_(node); } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 11a3b778..07d7e67f 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -6,8 +6,10 @@ use { ifs::{ ipc, ipc::{ - wl_data_device::WlDataDevice, - zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, + wl_data_device::{ClipboardIpc, WlDataDevice}, + zwp_primary_selection_device_v1::{ + PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, + }, }, wl_seat::{ wl_keyboard::{self, WlKeyboard}, @@ -21,7 +23,6 @@ use { }, wl_surface::{xdg_surface::xdg_popup::XdgPopup, WlSurface}, }, - object::ObjectId, tree::{FloatNode, Node, ToplevelNode}, utils::{bitflags::BitflagsExt, clonecell::CloneCell, smallmap::SmallMap}, wire::WlDataOfferId, @@ -328,7 +329,7 @@ impl WlSeatGlobal { self.kb_owner.set_kb_node(self, node); } - fn offer_selection( + fn offer_selection( &self, field: &CloneCell>>, client: &Rc, @@ -336,7 +337,7 @@ impl WlSeatGlobal { match field.get() { Some(sel) => ipc::offer_source_to::(&sel, client), None => T::for_each_device(self, client.id, |dd| { - T::send_selection(dd, ObjectId::NONE.into()); + T::send_selection(dd, None); }), } } @@ -398,7 +399,7 @@ impl WlSeatGlobal { let dd = self.data_devices.borrow_mut(); if let Some(dd) = dd.get(&client) { for dd in dd.values() { - if dd.manager.version >= ver { + if dd.version >= ver { f(dd); } } @@ -412,7 +413,7 @@ impl WlSeatGlobal { let dd = self.primary_selection_devices.borrow_mut(); if let Some(dd) = dd.get(&client) { for dd in dd.values() { - if dd.manager.version >= ver { + if dd.version >= ver { f(dd); } } @@ -624,11 +625,8 @@ impl WlSeatGlobal { }); if self.keyboard_node.get().node_client_id() != Some(surface.client.id) { - self.offer_selection::(&self.selection, &surface.client); - self.offer_selection::( - &self.primary_selection, - &surface.client, - ); + self.offer_selection::(&self.selection, &surface.client); + self.offer_selection::(&self.primary_selection, &surface.client); } } } @@ -692,7 +690,7 @@ impl WlSeatGlobal { serial: u32, ) { if let Some(src) = &dnd.src { - ipc::offer_source_to::(src, &surface.client); + ipc::offer_source_to::(src, &surface.client); src.for_each_data_offer(|offer| { offer.device.send_enter(surface.id, x, y, offer.id, serial); offer.send_source_actions(); diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index 6b7de5d4..a9b46534 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -4,7 +4,7 @@ use { fixed::Fixed, ifs::{ ipc, - ipc::{wl_data_device::WlDataDevice, wl_data_source::WlDataSource}, + ipc::{wl_data_device::ClipboardIpc, wl_data_source::WlDataSource}, wl_seat::{wl_pointer::PendingScroll, Dnd, DroppedDnd, WlSeatError, WlSeatGlobal}, wl_surface::WlSurface, }, @@ -341,7 +341,7 @@ impl PointerOwner for GrabPointerOwner { icon.dnd_icons.insert(seat.id(), seat.clone()); } if let Some(new) = &src { - ipc::attach_seat::(new, seat, ipc::Role::Dnd)?; + ipc::attach_seat::(new, seat, ipc::Role::Dnd)?; } *seat.dropped_dnd.borrow_mut() = None; let pointer_owner = Rc::new(DndPointerOwner { @@ -418,7 +418,7 @@ impl PointerOwner for DndPointerOwner { target.node_seat_state().remove_dnd_target(seat); if !should_drop { if let Some(src) = &self.dnd.src { - ipc::detach_seat::(src); + ipc::detach_seat::(src); } } if let Some(icon) = self.icon.get() { @@ -492,7 +492,7 @@ impl PointerOwner for DndPointerOwner { target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if let Some(src) = &self.dnd.src { - ipc::detach_seat::(src); + ipc::detach_seat::(src); } if let Some(icon) = self.icon.get() { icon.dnd_icons.remove(&seat.id()); diff --git a/src/object.rs b/src/object.rs index fd0ea698..29ed25d9 100644 --- a/src/object.rs +++ b/src/object.rs @@ -13,6 +13,7 @@ pub const WL_DISPLAY_ID: WlDisplayId = WlDisplayId::from_raw(1); pub struct ObjectId(u32); impl ObjectId { + #[allow(dead_code)] pub const NONE: Self = ObjectId(0); pub fn from_raw(raw: u32) -> Self { diff --git a/src/state.rs b/src/state.rs index ca3b3945..c738a332 100644 --- a/src/state.rs +++ b/src/state.rs @@ -105,6 +105,7 @@ pub struct State { pub config_dir: Option, pub config_file_id: NumCell, pub tracker: Tracker, + pub data_offer_ids: NumCell, } // impl Drop for State { diff --git a/src/xcon.rs b/src/xcon.rs index c43e65b7..a64a463c 100644 --- a/src/xcon.rs +++ b/src/xcon.rs @@ -541,6 +541,24 @@ impl Xcon { .send_event(t, &self.extensions, propagate, destination, event_mask) } + pub async fn get_property3( + self: &Rc, + window: u32, + property: u32, + ty: u32, + delete: bool, + buf: &mut Vec, + ) -> Result { + let len = buf.len(); + match self.get_property2(window, property, ty, delete, buf).await { + Ok(n) => Ok(n), + Err(e) => { + buf.truncate(len); + Err(e) + } + } + } + pub async fn get_property( self: &Rc, window: u32, @@ -549,7 +567,7 @@ impl Xcon { buf: &mut Vec, ) -> Result { let len = buf.len(); - match self.get_property2(window, property, ty, buf).await { + match self.get_property2(window, property, ty, false, buf).await { Ok(n) => Ok(n), Err(e) => { buf.truncate(len); @@ -563,10 +581,11 @@ impl Xcon { window: u32, property: u32, ty: u32, + delete: bool, buf: &mut Vec, ) -> Result { let mut gp = GetProperty { - delete: 0, + delete: delete as _, window, property, ty, diff --git a/src/xcon/consts.rs b/src/xcon/consts.rs index 1e7f130f..56fa5b4a 100644 --- a/src/xcon/consts.rs +++ b/src/xcon/consts.rs @@ -265,3 +265,11 @@ pub const CONFIG_WINDOW_HEIGHT: u16 = 8; pub const CONFIG_WINDOW_BORDER_WIDTH: u16 = 16; pub const CONFIG_WINDOW_SIBLING: u16 = 32; pub const CONFIG_WINDOW_STACK_MODE: u16 = 64; + +pub const SET_SELECTION_OWNER: u8 = 0; +pub const SELECTION_WINDOW_DESTROY: u8 = 1; +pub const SELECTION_CLIENT_CLOSE: u8 = 2; + +pub const SET_SELECTION_OWNER_MASK: u32 = 1; +pub const SELECTION_WINDOW_DESTROY_MASK: u32 = 2; +pub const SELECTION_CLIENT_CLOSE_MASK: u32 = 4; diff --git a/src/xwayland.rs b/src/xwayland.rs index 6f4c8c26..2668f044 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -7,16 +7,27 @@ use { client::ClientError, compositor::DISPLAY, forker::{ForkerError, ForkerProxy}, - ifs::wl_surface::{ - xwindow::{Xwindow, XwindowData}, - WlSurface, + ifs::{ + ipc::{ + wl_data_offer::WlDataOffer, wl_data_source::WlDataSource, + zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1, + zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + }, + wl_seat::SeatId, + wl_surface::{ + xwindow::{Xwindow, XwindowData}, + WlSurface, + }, }, state::State, user_session::import_environment, utils::{errorfmt::ErrorFmt, oserror::OsError, tri::Try}, wire::WlSurfaceId, xcon::XconError, - xwayland::{xsocket::allocate_socket, xwm::Wm}, + xwayland::{ + xsocket::allocate_socket, + xwm::{Wm, XwmShared}, + }, }, bstr::ByteSlice, std::{num::ParseIntError, rc::Rc}, @@ -74,6 +85,12 @@ enum XWaylandError { SpawnClient(#[source] ClientError), #[error("An unspecified XconError occurred")] XconError(#[from] XconError), + #[error("Could not create a window to manage a selection")] + CreateSelectionWindow(#[source] XconError), + #[error("Could not watch selection changes")] + WatchSelection(#[source] XconError), + #[error("Could not enable the xfixes extension")] + XfixesQueryVersion(#[source] XconError), } pub async fn manage(state: Rc) { @@ -177,13 +194,15 @@ async fn run( }; state.eng.fd(&Rc::new(dfdread))?.readable().await?; state.xwayland.queue.clear(); - let wm = match Wm::get(state, client, wm1).await { - Ok(w) => w, - Err(e) => return Err(XWaylandError::CreateWm(Box::new(e))), - }; - let wm = state.eng.spawn(wm.run()); - state.eng.fd(&Rc::new(pidfd))?.readable().await?; - drop(wm); + { + let shared = Rc::new(XwmShared::default()); + let wm = match Wm::get(state, client, wm1, &shared).await { + Ok(w) => w, + Err(e) => return Err(XWaylandError::CreateWm(Box::new(e))), + }; + let _wm = state.eng.spawn(wm.run()); + state.eng.fd(&Rc::new(pidfd))?.readable().await?; + } state.xwayland.queue.clear(); stderr_read.await; Ok(()) @@ -274,4 +293,17 @@ pub enum XWaylandEvent { Activate(Rc), ActivateRoot, Close(Rc), + SeatChanged, + + PrimarySelectionCancelSource(Rc), + PrimarySelectionSendSource(Rc, String, Rc), + PrimarySelectionSetOffer(Rc), + PrimarySelectionSetSelection(SeatId, Option>), + PrimarySelectionAddOfferMimeType(Rc, String), + + ClipboardCancelSource(Rc), + ClipboardSendSource(Rc, String, Rc), + ClipboardSetOffer(Rc), + ClipboardSetSelection(SeatId, Option>), + ClipboardAddOfferMimeType(Rc, String), } diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 738be2e5..1a4034d6 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1,25 +1,44 @@ use { crate::{ + async_engine::{AsyncFd, SpawnedFuture}, client::Client, - ifs::wl_surface::{ - xwindow::{XInputModel, Xwindow, XwindowData}, - WlSurface, + ifs::{ + ipc::{ + add_data_source_mime_type, destroy_data_device, destroy_data_offer, + destroy_data_source, receive_data_offer, + wl_data_device::{ClipboardIpc, WlDataDevice}, + zwp_primary_selection_device_v1::{ + PrimarySelectionIpc, ZwpPrimarySelectionDeviceV1, + }, + IpcVtable, + }, + wl_seat::{SeatId, WlSeatGlobal}, + wl_surface::{ + xwindow::{XInputModel, Xwindow, XwindowData}, + WlSurface, + }, }, rect::Rect, state::State, tree::ToplevelNode, - utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, linkedlist::LinkedList}, - wire::WlSurfaceId, + utils::{ + bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, linkedlist::LinkedList, numcell::NumCell, oserror::OsError, + rc_eq::rc_eq, tri::Try, + }, + wire::{WlDataDeviceId, WlSurfaceId, ZwpPrimarySelectionDeviceV1Id}, wire_xcon::{ ChangeProperty, ChangeWindowAttributes, ClientMessage, CompositeRedirectSubwindows, ConfigureNotify, ConfigureRequest, ConfigureWindow, ConfigureWindowValues, - CreateNotify, CreateWindow, CreateWindowValues, DestroyNotify, FocusIn, GetAtomName, - GetGeometry, InternAtom, KillClient, MapNotify, MapRequest, MapWindow, PropertyNotify, - ResClientIdSpec, ResQueryClientIds, SetInputFocus, SetSelectionOwner, UnmapNotify, + ConvertSelection, CreateNotify, CreateWindow, CreateWindowValues, DestroyNotify, + Extension, FocusIn, GetAtomName, GetGeometry, InternAtom, KillClient, MapNotify, + MapRequest, MapWindow, PropertyNotify, ResClientIdSpec, ResQueryClientIds, + SelectSelectionInput, SelectionNotify, SelectionRequest, SetInputFocus, + SetSelectionOwner, UnmapNotify, XfixesQueryVersion, XfixesSelectionNotify, }, xcon::{ consts::{ - ATOM_ATOM, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, + ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, ATOM_WM_SIZE_HINTS, ATOM_WM_TRANSIENT_FOR, COMPOSITE_REDIRECT_MANUAL, CONFIG_WINDOW_HEIGHT, CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_X, CONFIG_WINDOW_Y, EVENT_MASK_FOCUS_CHANGE, EVENT_MASK_PROPERTY_CHANGE, @@ -27,9 +46,11 @@ use { ICCCM_WM_HINT_INPUT, ICCCM_WM_STATE_ICONIC, ICCCM_WM_STATE_NORMAL, ICCCM_WM_STATE_WITHDRAWN, INPUT_FOCUS_POINTER_ROOT, MWM_HINTS_DECORATIONS_FIELD, MWM_HINTS_FLAGS_FIELD, NOTIFY_DETAIL_POINTER, NOTIFY_MODE_GRAB, NOTIFY_MODE_UNGRAB, - PROP_MODE_REPLACE, RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, STACK_MODE_ABOVE, - STACK_MODE_BELOW, WINDOW_CLASS_INPUT_OUTPUT, _NET_WM_STATE_ADD, - _NET_WM_STATE_REMOVE, _NET_WM_STATE_TOGGLE, + PROP_MODE_APPEND, PROP_MODE_REPLACE, RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, + SELECTION_CLIENT_CLOSE_MASK, SELECTION_WINDOW_DESTROY_MASK, + SET_SELECTION_OWNER_MASK, STACK_MODE_ABOVE, STACK_MODE_BELOW, + WINDOW_CLASS_INPUT_OUTPUT, _NET_WM_STATE_ADD, _NET_WM_STATE_REMOVE, + _NET_WM_STATE_TOGGLE, }, Event, XEvent, Xcon, XconError, }, @@ -41,11 +62,12 @@ use { smallvec::SmallVec, std::{ borrow::Cow, - mem, + cell::{Cell, RefCell}, + mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, rc::Rc, }, - uapi::OwnedFd, + uapi::{c, Errno, OwnedFd}, }; atoms! { @@ -124,6 +146,82 @@ atoms! { XdndTypeList, } +struct EnhancedOffer { + offer: Rc, + mime_types: RefCell>, + active: Cell, +} + +struct SelectionData { + devices: CopyHashMap>, + sources: CopyHashMap>, + offers: CopyHashMap>>, + active_offer: CloneCell>>>, + win: Cell, + selection: Cell, + pending_transfers: RefCell>, +} + +impl Default for SelectionData { + fn default() -> Self { + Self { + devices: Default::default(), + sources: Default::default(), + offers: Default::default(), + active_offer: Default::default(), + win: Cell::new(0), + selection: Cell::new(0), + pending_transfers: RefCell::new(vec![]), + } + } +} + +impl SelectionData { + fn destroy(&self) { + for (_, offer) in self.offers.lock().drain() { + destroy_data_offer::(&offer.offer); + } + self.active_offer.take(); + self.destroy_sources(); + for (_, device) in self.devices.lock().drain() { + destroy_data_device::(&device); + T::remove_from_seat(&device); + } + } + + fn destroy_sources(&self) { + for (_, source) in self.sources.lock().drain() { + destroy_data_source::(&source); + } + } + + fn seat_removed(&self, id: SeatId) { + if let Some(offer) = self.active_offer.get() { + if T::get_offer_seat(&offer.offer).id() == id { + self.active_offer.take(); + } + } + self.offers.remove(&id); + self.sources.remove(&id); + self.devices.remove(&id); + } +} + +#[derive(Default)] +pub struct XwmShared { + data: SelectionData, + primary_selection: SelectionData, + transfers: CopyHashMap>, +} + +impl Drop for XwmShared { + fn drop(&mut self) { + self.data.destroy(); + self.primary_selection.destroy(); + self.transfers.clear(); + } +} + pub struct Wm { state: Rc, c: Rc, @@ -136,6 +234,12 @@ pub struct Wm { windows_by_surface_id: AHashMap>, focus_window: Option>, last_input_serial: u64, + atom_cache: AHashMap, + atom_name_cache: AHashMap, + + transfer_ids: NumCell, + known_seats: AHashMap>, + shared: Rc, stack_list: LinkedList>, num_stacked: usize, @@ -144,6 +248,14 @@ pub struct Wm { num_mapped: usize, } +struct PendingTransfer { + mime_type: u32, + fd: AsyncFd, +} + +const TEXT_PLAIN_UTF_8: &str = "text/plain;charset=utf-8"; +const TEXT_PLAIN: &str = "text/plain"; + #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Initiator { X, @@ -165,6 +277,7 @@ impl Wm { state: &Rc, client: Rc, socket: OwnedFd, + shared: &Rc, ) -> Result { let c = match Xcon::connect_to_fd(&state.eng, &Rc::new(socket), &[], &[]).await { Ok(c) => c, @@ -336,6 +449,53 @@ impl Wm { return Err(XWaylandError::SetCursor(e)); } } + { + let qv = XfixesQueryVersion { + client_major_version: 1, + client_minor_version: 0, + }; + if let Err(e) = c.call(&qv).await { + return Err(XWaylandError::XfixesQueryVersion(e)); + } + } + let mut clipboard_wins = [0, 0]; + for (idx, atom) in [atoms.CLIPBOARD, atoms.PRIMARY].into_iter().enumerate() { + let win = c.generate_id()?; + let cw = CreateWindow { + depth: 0, + wid: win, + parent: root, + x: 0, + y: 0, + width: 10, + height: 10, + border_width: 0, + class: WINDOW_CLASS_INPUT_OUTPUT, + visual: 0, + values: CreateWindowValues { + event_mask: None, + ..Default::default() + }, + }; + if let Err(e) = c.call(&cw).await { + return Err(XWaylandError::CreateSelectionWindow(e)); + } + let ssi = SelectSelectionInput { + window: win, + selection: atom, + event_mask: SET_SELECTION_OWNER_MASK + | SELECTION_CLIENT_CLOSE_MASK + | SELECTION_WINDOW_DESTROY_MASK, + }; + if let Err(e) = c.call(&ssi).await { + return Err(XWaylandError::WatchSelection(e)); + } + clipboard_wins[idx] = win; + } + shared.data.win.set(clipboard_wins[0]); + shared.data.selection.set(atoms.CLIPBOARD); + shared.primary_selection.win.set(clipboard_wins[1]); + shared.primary_selection.selection.set(atoms.PRIMARY); Ok(Self { state: state.clone(), c, @@ -348,6 +508,11 @@ impl Wm { windows_by_surface_id: Default::default(), focus_window: Default::default(), last_input_serial: 0, + atom_cache: Default::default(), + atom_name_cache: Default::default(), + transfer_ids: Default::default(), + known_seats: Default::default(), + shared: shared.clone(), stack_list: Default::default(), num_stacked: 0, map_list: Default::default(), @@ -355,7 +520,57 @@ impl Wm { }) } + fn seats_changed(&mut self) { + let current_seats: AHashMap<_, _> = self + .state + .globals + .seats + .lock() + .values() + .map(|s| (s.id(), s.clone())) + .collect(); + let mut new_seats = vec![]; + let mut removed_seats = vec![]; + for (id, seat) in ¤t_seats { + if !self.known_seats.contains_key(id) { + new_seats.push(seat.clone()); + } + } + for id in self.known_seats.keys() { + if !current_seats.contains_key(id) { + removed_seats.push(*id); + } + } + for seat in removed_seats { + self.shared.data.seat_removed(seat); + self.shared.primary_selection.seat_removed(seat); + } + for seat in new_seats { + let dd = Rc::new(WlDataDevice::new( + WlDataDeviceId::NONE, + &self.client, + 1, + &seat, + true, + )); + seat.add_data_device(&dd); + self.shared.data.devices.set(seat.id(), dd); + + let dd = Rc::new(ZwpPrimarySelectionDeviceV1::new( + ZwpPrimarySelectionDeviceV1Id::NONE, + &self.client, + 1, + &seat, + true, + )); + seat.add_primary_selection_device(&dd); + self.shared.primary_selection.devices.set(seat.id(), dd); + } + self.known_seats = current_seats; + } + pub async fn run(mut self) { + self.seats_changed(); loop { select! { e = self.state.xwayland.queue.pop().fuse() => self.handle_xwayland_event(e).await, @@ -377,6 +592,262 @@ impl Wm { } XWaylandEvent::ActivateRoot => self.activate_window(None, Initiator::Wayland).await, XWaylandEvent::Close(window) => self.close_window(&window).await, + XWaylandEvent::SeatChanged => self.seats_changed(), + XWaylandEvent::PrimarySelectionCancelSource(src) => { + self.dd_cancel_source(&self.shared.clone().primary_selection, &src) + } + XWaylandEvent::PrimarySelectionSendSource(src, mime_type, fd) => { + self.dd_send_source(&self.shared.clone().primary_selection, &src, mime_type, fd) + .await; + } + XWaylandEvent::PrimarySelectionSetOffer(offer) => { + self.dd_set_offer(&self.shared.clone().primary_selection, offer) + .await; + } + XWaylandEvent::PrimarySelectionSetSelection(seat, offer) => { + self.dd_set_selection(&self.shared.clone().primary_selection, seat, offer) + .await; + } + XWaylandEvent::PrimarySelectionAddOfferMimeType(offer, mt) => { + self.dd_add_offer_mime_type(&self.shared.clone().primary_selection, offer, mt) + .await; + } + XWaylandEvent::ClipboardCancelSource(src) => { + self.dd_cancel_source(&self.shared.clone().data, &src) + } + XWaylandEvent::ClipboardSendSource(src, mime_type, fd) => { + self.dd_send_source(&self.shared.clone().data, &src, mime_type, fd) + .await; + } + XWaylandEvent::ClipboardSetOffer(offer) => { + self.dd_set_offer(&self.shared.clone().data, offer).await; + } + XWaylandEvent::ClipboardSetSelection(seat, offer) => { + self.dd_set_selection(&self.shared.clone().data, seat, offer) + .await; + } + XWaylandEvent::ClipboardAddOfferMimeType(offer, mt) => { + self.dd_add_offer_mime_type(&self.shared.clone().data, offer, mt) + .await; + } + } + } + + async fn dd_add_offer_mime_type( + &mut self, + sd: &SelectionData, + offer: Rc, + mt: String, + ) { + let seat = T::get_offer_seat(&offer); + let enhanced = match sd.offers.get(&seat.id()) { + Some(r) if !rc_eq(&r.offer, &offer) => { + return; + } + None => { + return; + } + Some(r) => r, + }; + let name = mt.clone(); + let mt = match self.mime_type_to_atom(mt).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not get mime type atom: {}", ErrorFmt(e)); + return; + } + }; + log::info!("push {} = {}", mt, name); + enhanced.mime_types.borrow_mut().push(mt); + } + + async fn dd_set_offer(&mut self, sd: &SelectionData, offer: Rc) { + let seat = T::get_offer_seat(&offer); + let mut mime_types = vec![]; + if let Some(offer) = sd.offers.remove(&seat.id()) { + destroy_data_offer::(&offer.offer); + mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut()); + } + match T::get_offer_data(&offer).source() { + None => return, + Some(s) if T::get_source_data(&s).is_xwm => return, + _ => {} + } + sd.offers.set( + seat.id(), + Rc::new(EnhancedOffer { + offer, + mime_types: RefCell::new(mime_types), + active: Cell::new(false), + }), + ); + } + + async fn dd_set_selection( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: Option>, + ) { + let offer = match offer { + None => { + if let Some(offer) = sd.offers.remove(&seat) { + destroy_data_offer::(&offer.offer); + if offer.active.get() { + sd.active_offer.take(); + } + } + return; + } + Some(offer) => offer, + }; + let enhanced = match sd.offers.get(&seat) { + None => { + destroy_data_offer::(&offer); + return; + } + Some(e) => e, + }; + if !rc_eq(&enhanced.offer, &offer) { + destroy_data_offer::(&offer); + return; + } + if !enhanced.active.replace(true) { + if let Some(old) = sd.active_offer.set(Some(enhanced)) { + old.active.set(false); + } + } + let so = SetSelectionOwner { + owner: sd.win.get(), + selection: sd.selection.get(), + time: 0, + }; + if let Err(err) = self.c.call(&so).await { + log::error!("Could not set primary selection owner: {}", ErrorFmt(err)); + } + } + + async fn get_atom_name(&mut self, atom: u32) -> Result { + if let Some(name) = self.atom_name_cache.get(&atom) { + return Ok(name.clone()); + } + let gan = GetAtomName { atom }; + match self.c.call(&gan).await { + Ok(name) => { + let name = name.get().name.to_string(); + self.atom_name_cache.insert(atom, name.clone()); + Ok(name) + } + Err(e) => Err(e), + } + } + + async fn get_atom(&mut self, name: String) -> Result { + if let Some(atom) = self.atom_cache.get(&name) { + return Ok(*atom); + } + let ia = InternAtom { + only_if_exists: 0, + name: name.as_bytes().as_bstr(), + }; + match self.c.call(&ia).await { + Ok(id) => { + let atom = id.get().atom; + self.atom_cache.insert(name, atom); + Ok(atom) + } + Err(e) => Err(e), + } + } + + async fn mime_type_to_atom(&mut self, mime_type: String) -> Result { + match mime_type.as_str() { + TEXT_PLAIN_UTF_8 => Ok(self.atoms.UTF8_STRING), + TEXT_PLAIN => Ok(ATOM_STRING), + _ => self.get_atom(mime_type).await, + } + } + + async fn atom_to_mime_type(&mut self, atom: u32) -> Result { + if atom == self.atoms.UTF8_STRING { + Ok(TEXT_PLAIN_UTF_8.to_string()) + } else if atom == ATOM_STRING { + Ok(TEXT_PLAIN.to_string()) + } else { + self.get_atom_name(atom).await + } + } + + async fn dd_send_source( + &mut self, + sd: &SelectionData, + src: &Rc, + mime_type: String, + fd: Rc, + ) { + let seat = match T::get_source_data(src).seat.get() { + Some(s) => s, + _ => return, + }; + let actual_src = match sd.sources.get(&seat.id()) { + None => return, + Some(src) => src, + }; + if !rc_eq(src, &actual_src) { + return; + } + let mime_type = match self.mime_type_to_atom(mime_type).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not intern mime type: {}", ErrorFmt(e)); + return; + } + }; + { + let res = OsError::tri(|| { + let fl = uapi::fcntl_getfl(fd.raw())?; + uapi::fcntl_setfl(fd.raw(), fl | c::O_NONBLOCK)?; + Ok(()) + }); + if let Err(e) = res { + log::error!("Could not set file description flags: {}", ErrorFmt(e)); + return; + } + } + let fd = match self.state.eng.fd(&fd) { + Ok(afd) => afd, + Err(e) => { + log::error!("Could not create async fd: {}", ErrorFmt(e)); + return; + } + }; + let cs = ConvertSelection { + requestor: sd.win.get(), + selection: sd.selection.get(), + target: mime_type, + property: self.atoms._WL_SELECTION, + time: 0, + }; + if let Err(e) = self.c.call(&cs).await { + log::error!( + "Could not perform convert selection request: {}", + ErrorFmt(e) + ); + return; + } + sd.pending_transfers + .borrow_mut() + .push(PendingTransfer { mime_type, fd }); + } + + fn dd_cancel_source(&mut self, sd: &SelectionData, src: &Rc) { + if let Some(seat) = T::get_source_data(src).seat.get() { + if let Some(cur) = sd.sources.get(&seat.id()) { + if rc_eq(src, &cur) { + sd.sources.remove(&seat.id()); + destroy_data_source::(&cur); + } + } } } @@ -973,14 +1444,71 @@ impl Wm { } async fn handle_event(&mut self, event: &Event) { - match event.ext() { - Some(_) => {} + let res = match event.ext() { + Some(ex) => self.handle_extension_event(ex, event).await, _ => self.handle_core_event(event).await, + }; + if let Err(e) = res { + log::warn!("Could not handle an event: {}", ErrorFmt(e)); } } - async fn handle_core_event(&mut self, event: &Event) { - let res = match event.code() { + async fn handle_extension_event( + &mut self, + ex: Extension, + event: &Event, + ) -> Result<(), XWaylandError> { + match ex { + Extension::XFIXES => self.handle_xfixes_event(event).await, + _ => Ok(()), + } + } + + async fn handle_xfixes_event(&mut self, event: &Event) -> Result<(), XWaylandError> { + match event.code() { + XfixesSelectionNotify::OPCODE => self.handle_xfixes_selection_notify(event).await, + _ => Ok(()), + } + } + + async fn handle_xfixes_selection_notify(&mut self, event: &Event) -> Result<(), XWaylandError> { + let event: XfixesSelectionNotify = event.parse()?; + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_xfixes_selection_notify_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_xfixes_selection_notify_(&shared.data, &event) + .await + } else { + Ok(()) + } + } + + async fn handle_xfixes_selection_notify_( + &mut self, + sd: &SelectionData, + event: &XfixesSelectionNotify, + ) -> Result<(), XWaylandError> { + if event.owner == sd.win.get() { + return Ok(()); + } + sd.destroy_sources(); + let cs = ConvertSelection { + requestor: sd.win.get(), + selection: sd.selection.get(), + target: self.atoms.TARGETS, + property: self.atoms._WL_SELECTION, + time: event.timestamp, + }; + if let Err(e) = self.c.call(&cs).await { + log::error!("Could not convert selection: {}", ErrorFmt(e)); + } + Ok(()) + } + + async fn handle_core_event(&mut self, event: &Event) -> Result<(), XWaylandError> { + match event.code() { MapRequest::OPCODE => self.handle_map_request(event).await, MapNotify::OPCODE => self.handle_map_notify(event).await, ConfigureRequest::OPCODE => self.handle_configure_request(event).await, @@ -991,13 +1519,232 @@ impl Wm { PropertyNotify::OPCODE => self.handle_property_notify(event).await, FocusIn::OPCODE => self.handle_focus_in(event).await, UnmapNotify::OPCODE => self.handle_unmap_notify(event).await, + SelectionNotify::OPCODE => self.handle_selection_notify(event).await, + SelectionRequest::OPCODE => self.handle_selection_request(event).await, _ => Ok(()), - }; - if let Err(e) = res { - log::warn!("Could not handle an event: {}", ErrorFmt(e)); } } + async fn handle_selection_request(&mut self, event: &Event) -> Result<(), XWaylandError> { + let event: SelectionRequest = event.parse()?; + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_selection_request_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_selection_request_(&shared.data, &event).await + } else { + log::warn!("Unknown selection request"); + Ok(()) + } + } + + async fn handle_selection_request_( + &mut self, + sd: &SelectionData, + event: &SelectionRequest, + ) -> Result<(), XWaylandError> { + let mut success = Some(false); + if let Some(offer) = sd.active_offer.get() { + let mt = offer.mime_types.borrow_mut(); + if event.target == self.atoms.TARGETS { + let cp = ChangeProperty { + mode: PROP_MODE_REPLACE, + window: event.requestor, + property: event.property, + ty: ATOM_ATOM, + format: 32, + data: uapi::as_bytes(&mt[..]), + }; + match self.c.call(&cp).await { + Ok(_) => success = Some(true), + Err(e) => { + log::error!("Could not set selection property: {}", ErrorFmt(e)); + } + } + } else { + 'convert: { + let present = mt.contains(&event.target); + drop(mt); + let mt = match self.atom_to_mime_type(event.target).await { + Ok(mt) => mt, + Err(e) => { + log::error!("Could not get mime type name: {}", ErrorFmt(e)); + break 'convert; + } + }; + if !present { + log::error!("Peer requested unavailable target {}", mt); + break 'convert; + } + let (rx, tx) = match uapi::pipe2(c::O_CLOEXEC) { + Ok(p) => p, + Err(e) => { + log::error!("Could not create pipe: {}", OsError::from(e)); + break 'convert; + } + }; + let res = OsError::tri(|| { + let fl = uapi::fcntl_getfl(rx.raw())?; + uapi::fcntl_setfl(rx.raw(), fl | c::O_NONBLOCK)?; + Ok(()) + }); + if let Err(e) = res { + log::error!("Could not make pipe nonblocking: {}", e); + break 'convert; + } + let fd = match self.state.eng.fd(&Rc::new(rx)) { + Ok(afd) => afd, + Err(e) => { + log::error!("Could not create an async fd: {}", ErrorFmt(e)); + break 'convert; + } + }; + success = None; + receive_data_offer::(&offer.offer, &mt, Rc::new(tx)); + let id = self.transfer_ids.fetch_add(1); + let wtx = WaylandToXTransfer { + id, + fd, + c: self.c.clone(), + window: event.requestor, + time: event.time, + property: event.property, + ty: event.target, + selection: sd.selection.get(), + shared: self.shared.clone(), + }; + self.shared + .transfers + .set(id, self.state.eng.spawn(wtx.run())); + } + } + } + if let Some(success) = success { + let target = match success { + true => event.target, + false => ATOM_NONE, + }; + let sn = SelectionNotify { + time: event.time, + requestor: event.requestor, + selection: sd.selection.get(), + target, + property: event.property, + }; + if let Err(e) = self.c.send_event(false, event.requestor, 0, &sn).await { + log::error!("Could not send event: {}", ErrorFmt(e)); + } + } + Ok(()) + } + + async fn handle_selection_notify(&mut self, event: &Event) -> Result<(), XWaylandError> { + let event: SelectionNotify = event.parse()?; + if event.property != self.atoms._WL_SELECTION { + return Ok(()); + } + let shared = self.shared.clone(); + if event.selection == self.atoms.PRIMARY { + self.handle_selection_notify_(&shared.primary_selection, &event) + .await + } else if event.selection == self.atoms.CLIPBOARD { + self.handle_selection_notify_(&shared.data, &event).await + } else { + Ok(()) + } + } + + async fn handle_selection_notify_( + &mut self, + sd: &SelectionData, + event: &SelectionNotify, + ) -> Result<(), XWaylandError> { + if event.property != self.atoms._WL_SELECTION { + return Ok(()); + } + if event.target == ATOM_NONE { + return Ok(()); + } + if event.target == self.atoms.TARGETS { + let targets = self.get_selection_mime_types(sd.win.get()).await?; + for dev in sd.devices.lock().values() { + let seat = T::get_device_seat(dev); + if !seat.may_modify_primary_selection(&self.client, None) { + continue; + } + let source = Rc::new(T::create_xwm_source(&self.client)); + 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 { + let mut transfers = sd.pending_transfers.borrow_mut(); + let transfers = transfers.drain(..); + let mut data = vec![]; + let gp = self + .c + .get_property( + sd.win.get(), + self.atoms._WL_SELECTION, + event.target, + &mut data, + ) + .await; + if let Err(e) = gp { + log::error!("Could not get converted property: {}", e); + return Ok(()); + } + let data = Rc::new(data); + for transfer in transfers { + if event.target != transfer.mime_type { + log::error!("Conversion yielded an incompatible mime type"); + continue; + } + let id = self.transfer_ids.fetch_add(1); + let transfer = XToWaylandTransfer { + id, + data: data.clone(), + pos: 0, + fd: transfer.fd, + shared: self.shared.clone(), + }; + self.shared + .transfers + .set(id, self.state.eng.spawn(transfer.run())); + } + } + + Ok(()) + } + + async fn get_selection_mime_types( + &mut self, + window: u32, + ) -> Result, XWaylandError> { + let mut buf = vec![]; + self.c + .get_property3::(window, self.atoms._WL_SELECTION, ATOM_ATOM, true, &mut buf) + .await?; + let mut res = vec![]; + for atom in buf { + let name = match self.atom_to_mime_type(atom).await { + Ok(n) => n, + Err(e) => { + log::error!("Could not get atom name: {}", ErrorFmt(e)); + continue; + } + }; + res.push(name); + } + Ok(res) + } + async fn handle_unmap_notify(&mut self, revent: &Event) -> Result<(), XWaylandError> { let event: UnmapNotify = revent.parse()?; let data = match self.windows.get(&event.window) { @@ -1644,3 +2391,98 @@ impl Wm { data.info.wants_floating.set(res); } } + +struct XToWaylandTransfer { + id: u64, + data: Rc>, + pos: usize, + fd: AsyncFd, + shared: Rc, +} + +impl XToWaylandTransfer { + async fn run(mut self) { + while self.pos < self.data.len() { + match uapi::write(self.fd.raw(), &self.data[self.pos..]) { + Ok(n) => self.pos += n, + Err(Errno(c::EAGAIN)) => { + if let Err(e) = self.fd.writable().await { + log::error!("Could not wait for fd to become writable: {}", ErrorFmt(e)); + break; + } + } + Err(e) => { + log::error!("Could not write to wayland client: {}", ErrorFmt(e)); + break; + } + } + } + self.shared.transfers.remove(&self.id); + } +} + +struct WaylandToXTransfer { + id: u64, + fd: AsyncFd, + c: Rc, + window: u32, + time: u32, + property: u32, + ty: u32, + selection: u32, + shared: Rc, +} + +impl WaylandToXTransfer { + async fn run(self) { + let mut success = false; + let mut buf = Box::new([MaybeUninit::::uninit(); 1024]); + loop { + match uapi::read(self.fd.raw(), &mut buf[..]) { + Ok(n) if n.is_empty() => { + success = true; + break; + } + Ok(n) => { + let cp = ChangeProperty { + mode: PROP_MODE_APPEND, + window: self.window, + property: self.property, + ty: self.ty, + format: 8, + data: n, + }; + if let Err(e) = self.c.call(&cp).await { + log::error!("Could not append data to property: {}", ErrorFmt(e)); + break; + } + } + Err(Errno(c::EAGAIN)) => { + if let Err(e) = self.fd.readable().await { + log::error!("Could not wait for fd to become readable: {}", ErrorFmt(e)); + break; + } + } + Err(e) => { + log::error!("Could not read from wayland client: {}", ErrorFmt(e)); + break; + } + } + } + let target = match success { + true => self.ty, + false => ATOM_NONE, + }; + let sn = SelectionNotify { + time: self.time, + requestor: self.window, + selection: self.selection, + target, + property: self.property, + }; + if let Err(e) = self.c.send_event(false, self.window, 0, &sn).await { + log::error!("Could not send event: {}", ErrorFmt(e)); + } + self.shared.transfers.remove(&self.id); + } +} diff --git a/wire-xcon/xfixes.txt b/wire-xcon/xfixes.txt new file mode 100644 index 00000000..c3d30915 --- /dev/null +++ b/wire-xcon/xfixes.txt @@ -0,0 +1,27 @@ +ext "XFIXES" + +request XfixesQueryVersion = 0 ( + client_major_version: u32, + client_minor_version: u32, +) { + @pad 1, + major_version: u32, + minor_version: u32, + @pad 16, +} + +event XfixesSelectionNotify = 0 { + subtype: u8, + window: u32, + owner: u32, + selection: u32, + timestamp: u32, + selection_timestamp: u32, + @pad 8, +} + +request SelectSelectionInput = 2 ( + window: u32, + selection: u32, + event_mask: u32, +); diff --git a/wire-xcon/xproto.txt b/wire-xcon/xproto.txt index c00cf3dd..fe0bf662 100644 --- a/wire-xcon/xproto.txt +++ b/wire-xcon/xproto.txt @@ -421,3 +421,31 @@ event MapNotify = 19 { override_redirect: u8, @pad 3, } + +request ConvertSelection = 24 ( + @pad 1, + requestor: u32, + selection: u32, + target: u32, + property: u32, + time: u32, +); + +event SelectionNotify = 31 { + @pad 1, + time: u32, + requestor: u32, + selection: u32, + target: u32, + property: u32, +} + +event SelectionRequest = 30 { + @pad 1, + time: u32, + owner: u32, + requestor: u32, + selection: u32, + target: u32, + property: u32, +}