diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index 43532f61..e3856daa 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1,5 +1,10 @@ #![allow(clippy::await_holding_refcell_ref)] // all borrows are to data that is only used by this task +mod selection; +mod transfer; + +use selection::SelectionData; + use { crate::{ async_engine::SpawnedFuture, @@ -161,48 +166,6 @@ atoms! { XdndTypeList, } -struct EnhancedOffer { - offer: Rc, - mime_types: RefCell>, - active: Cell, -} - -#[derive(Default)] -struct SelectionData { - sources: CopyHashMap>, - offers: CopyHashMap>, - active_offer: CloneCell>>, - win: Cell, - selection: Cell, - pending_transfers: RefCell>, - _phantom: PhantomData, -} - -impl SelectionData { - fn destroy(&self) { - for offer in self.offers.lock().drain_values() { - destroy_data_offer::(&offer.offer); - } - self.active_offer.take(); - self.destroy_sources(); - } - - fn destroy_sources(&self) { - for source in self.sources.lock().drain_values() { - destroy_data_source::(&source); - } - } - - fn seat_removed(&self, id: SeatId) { - if let Some(offer) = self.active_offer.get() - && offer.offer.get_seat().id() == id - { - self.active_offer.take(); - } - self.offers.remove(&id); - self.sources.remove(&id); - } -} #[derive(Default)] pub struct XwmShared { @@ -253,13 +216,6 @@ pub struct Wm { num_mapped: usize, } -struct PendingTransfer { - mime_type: u32, - fd: Rc, -} - -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 { @@ -715,202 +671,6 @@ impl Wm { } } - async fn dd_add_offer_mime_type( - &mut self, - sd: &SelectionData, - seat: SeatId, - offer: DataOfferId, - mt: String, - ) { - let enhanced = match sd.offers.get(&seat) { - Some(r) if r.offer.offer_id != offer => { - return; - } - None => { - return; - } - Some(r) => r, - }; - 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; - } - }; - enhanced.mime_types.borrow_mut().push(mt); - } - - async fn dd_set_offer( - &mut self, - sd: &SelectionData, - seat: SeatId, - offer: Rc, - ) { - let mut mime_types = vec![]; - if let Some(offer) = sd.offers.remove(&seat) { - destroy_data_offer::(&offer.offer); - mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut()); - } - sd.offers.set( - seat, - 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) - && 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, - seat: SeatId, - src: DataSourceId, - mime_type: String, - fd: Rc, - ) { - let actual_src = match sd.sources.get(&seat) { - None => return, - Some(src) => src, - }; - if actual_src.source_data().id != 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 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, - seat: SeatId, - source: DataSourceId, - ) { - if let Some(cur) = sd.sources.get(&seat) - && cur.source_data().id == source - { - sd.sources.remove(&seat); - destroy_data_source::(&cur); - } - } async fn handle_xwayland_configure(&mut self, window: Rc) { if window.data.destroyed.get() { @@ -1583,48 +1343,6 @@ impl Wm { } } - 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() { @@ -1644,227 +1362,6 @@ impl Wm { } } - 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 Pipe { - read: rx, - write: tx, - } = match pipe() { - Ok(p) => p, - Err(e) => { - log::error!("Could not create pipe: {}", 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: Rc::new(rx), - ring: self.state.ring.clone(), - 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("wayland to X transfer", 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 self.shared.devices.lock().values() { - let seat = T::get_device_seat(dev); - if !seat.may_modify_primary_selection(&self.client, None) { - continue; - } - let source = Rc::new(XDataSource { - state: self.state.clone(), - device: dev.clone(), - data: SourceData::new(&self.client), - location: T::LOCATION, - }); - for target in &targets { - add_data_source_mime_type::(&source, target); - } - let res = match source.location { - TransferLocation::Clipboard => seat.set_selection(Some(source.clone())), - TransferLocation::PrimarySelection => { - seat.set_primary_selection(Some(source.clone())) - } - }; - if let Err(e) = res { - log::error!("Could not set selection: {}", ErrorFmt(e)); - return Ok(()); - } - 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 mut data = Buf::from_slice(&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(), - fd: transfer.fd, - state: self.state.clone(), - shared: self.shared.clone(), - }; - self.shared.transfers.set( - id, - self.state - .eng - .spawn("X to wayland transfer", 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()?; @@ -2568,97 +2065,3 @@ impl Wm { data.info.wants_floating.set(res); } } - -struct XToWaylandTransfer { - id: u64, - data: Buf, - fd: Rc, - state: Rc, - shared: Rc, -} - -impl XToWaylandTransfer { - async fn run(mut self) { - let timeout = self.state.now() + Duration::from_millis(5000); - let mut pos = 0; - while pos < self.data.len() { - let res = self - .state - .ring - .write(&self.fd, self.data.slice(pos..), Some(timeout)); - match res.await { - Ok(n) => pos += n, - Err(IoUringError::OsError(OsError(c::ECANCELED))) => { - log::error!("Transfer timed out"); - 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: Rc, - ring: Rc, - 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 = Buf::new(1024); - loop { - match self.ring.read(&self.fd, buf.clone()).await { - Ok(0) => { - success = true; - break; - } - Ok(n) => { - let cp = ChangeProperty { - mode: PROP_MODE_APPEND, - window: self.window, - property: self.property, - ty: self.ty, - format: 8, - data: &buf[..n], - }; - if let Err(e) = self.c.call(&cp).await { - log::error!("Could not append data to property: {}", 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/src/xwayland/xwm/selection.rs b/src/xwayland/xwm/selection.rs new file mode 100644 index 00000000..0bbaa1ea --- /dev/null +++ b/src/xwayland/xwm/selection.rs @@ -0,0 +1,523 @@ +use {super::*, super::transfer::{WaylandToXTransfer, XToWaylandTransfer}}; + +pub(super) struct EnhancedOffer { + offer: Rc, + mime_types: RefCell>, + active: Cell, +} + +#[derive(Default)] +pub(super) struct SelectionData { + pub(super) sources: CopyHashMap>, + pub(super) offers: CopyHashMap>, + pub(super) active_offer: CloneCell>>, + pub(super) win: Cell, + pub(super) selection: Cell, + pub(super) pending_transfers: RefCell>, + pub(super) _phantom: PhantomData, +} + +impl SelectionData { + pub(super) fn destroy(&self) { + for offer in self.offers.lock().drain_values() { + destroy_data_offer::(&offer.offer); + } + self.active_offer.take(); + self.destroy_sources(); + } + + fn destroy_sources(&self) { + for source in self.sources.lock().drain_values() { + destroy_data_source::(&source); + } + } + + pub(super) fn seat_removed(&self, id: SeatId) { + if let Some(offer) = self.active_offer.get() + && offer.offer.get_seat().id() == id + { + self.active_offer.take(); + } + self.offers.remove(&id); + self.sources.remove(&id); + } +} + +pub(super) struct PendingTransfer { + mime_type: u32, + fd: Rc, +} + +const TEXT_PLAIN_UTF_8: &str = "text/plain;charset=utf-8"; +const TEXT_PLAIN: &str = "text/plain"; + +impl Wm { + pub(super) async fn dd_add_offer_mime_type( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: DataOfferId, + mt: String, + ) { + let enhanced = match sd.offers.get(&seat) { + Some(r) if r.offer.offer_id != offer => { + return; + } + None => { + return; + } + Some(r) => r, + }; + 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; + } + }; + enhanced.mime_types.borrow_mut().push(mt); + } + + pub(super) async fn dd_set_offer( + &mut self, + sd: &SelectionData, + seat: SeatId, + offer: Rc, + ) { + let mut mime_types = vec![]; + if let Some(offer) = sd.offers.remove(&seat) { + destroy_data_offer::(&offer.offer); + mime_types = mem::take(offer.mime_types.borrow_mut().deref_mut()); + } + sd.offers.set( + seat, + Rc::new(EnhancedOffer { + offer, + mime_types: RefCell::new(mime_types), + active: Cell::new(false), + }), + ); + } + + pub(super) 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) + && 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 + } + } + + pub(super) async fn dd_send_source( + &mut self, + sd: &SelectionData, + seat: SeatId, + src: DataSourceId, + mime_type: String, + fd: Rc, + ) { + let actual_src = match sd.sources.get(&seat) { + None => return, + Some(src) => src, + }; + if actual_src.source_data().id != 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 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 }); + } + + pub(super) fn dd_cancel_source( + &mut self, + sd: &SelectionData, + seat: SeatId, + source: DataSourceId, + ) { + if let Some(cur) = sd.sources.get(&seat) + && cur.source_data().id == source + { + sd.sources.remove(&seat); + destroy_data_source::(&cur); + } + } + pub(super) 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(()) + } + pub(super) 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 Pipe { + read: rx, + write: tx, + } = match pipe() { + Ok(p) => p, + Err(e) => { + log::error!("Could not create pipe: {}", 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: Rc::new(rx), + ring: self.state.ring.clone(), + 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("wayland to X transfer", 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(()) + } + + pub(super) 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 self.shared.devices.lock().values() { + let seat = T::get_device_seat(dev); + if !seat.may_modify_primary_selection(&self.client, None) { + continue; + } + let source = Rc::new(XDataSource { + state: self.state.clone(), + device: dev.clone(), + data: SourceData::new(&self.client), + location: T::LOCATION, + }); + for target in &targets { + add_data_source_mime_type::(&source, target); + } + let res = match source.location { + TransferLocation::Clipboard => seat.set_selection(Some(source.clone())), + TransferLocation::PrimarySelection => { + seat.set_primary_selection(Some(source.clone())) + } + }; + if let Err(e) = res { + log::error!("Could not set selection: {}", ErrorFmt(e)); + return Ok(()); + } + 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 mut data = Buf::from_slice(&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(), + fd: transfer.fd, + state: self.state.clone(), + shared: self.shared.clone(), + }; + self.shared.transfers.set( + id, + self.state + .eng + .spawn("X to wayland transfer", 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) + } +} diff --git a/src/xwayland/xwm/transfer.rs b/src/xwayland/xwm/transfer.rs new file mode 100644 index 00000000..9eecc440 --- /dev/null +++ b/src/xwayland/xwm/transfer.rs @@ -0,0 +1,95 @@ +use super::*; + +pub(super) struct XToWaylandTransfer { + pub(super) id: u64, + pub(super) data: Buf, + pub(super) fd: Rc, + pub(super) state: Rc, + pub(super) shared: Rc, +} + +impl XToWaylandTransfer { + pub(super) async fn run(mut self) { + let timeout = self.state.now() + Duration::from_millis(5000); + let mut pos = 0; + while pos < self.data.len() { + let res = self + .state + .ring + .write(&self.fd, self.data.slice(pos..), Some(timeout)); + match res.await { + Ok(n) => pos += n, + Err(IoUringError::OsError(OsError(c::ECANCELED))) => { + log::error!("Transfer timed out"); + break; + } + Err(e) => { + log::error!("Could not write to wayland client: {}", ErrorFmt(e)); + break; + } + } + } + self.shared.transfers.remove(&self.id); + } +} + +pub(super) struct WaylandToXTransfer { + pub(super) id: u64, + pub(super) fd: Rc, + pub(super) ring: Rc, + pub(super) c: Rc, + pub(super) window: u32, + pub(super) time: u32, + pub(super) property: u32, + pub(super) ty: u32, + pub(super) selection: u32, + pub(super) shared: Rc, +} + +impl WaylandToXTransfer { + pub(super) async fn run(self) { + let mut success = false; + let mut buf = Buf::new(1024); + loop { + match self.ring.read(&self.fd, buf.clone()).await { + Ok(0) => { + success = true; + break; + } + Ok(n) => { + let cp = ChangeProperty { + mode: PROP_MODE_APPEND, + window: self.window, + property: self.property, + ty: self.ty, + format: 8, + data: &buf[..n], + }; + if let Err(e) = self.c.call(&cp).await { + log::error!("Could not append data to property: {}", 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); + } +}