use std::collections::VecDeque;
use std::ffi::OsStr;
use std::io;
use std::os::raw::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use wingy_core::data_transfer::{
DataTransfer, DataTransferId, DataTransferSend, SendData, TransferType, TypeHint, TypedData,
};
use wingy_core::error::TransferError;
use wingy_core::event::WindowEvent;
use wingy_core::event_loop::AsyncRequestSerial;
use wingy_core::window::WindowId;
use x11_dl::xlib::{XPropertyEvent, XSelectionEvent, XSelectionRequestEvent};
use x11rb::CURRENT_TIME;
use x11rb::errors::ReplyError;
use x11rb::protocol::xfixes::{self, ConnectionExt as _};
use x11rb::protocol::xproto::{self, ConnectionExt};
use crate::atoms::AtomName::NoneAtom as DndNone;
use crate::atoms::*;
use crate::event_loop::{CookieResultExt, X11Error};
use crate::xdisplay::XConnection;
use crate::{XWindow, util};
const INCR_CHUNK_SIZE_BYTES: usize = 1024;
fn encode_uri_list<I>(uri_list: I) -> Vec<u8>
where
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
let mut out = Vec::new();
for uri in uri_list {
out.extend_from_slice(OsStr::new(&uri).as_encoded_bytes());
out.extend_from_slice(b"\r\n");
}
out
}
#[derive(Debug, Clone, Copy)]
pub enum DndState {
Accepted,
Rejected,
}
#[derive(Debug)]
pub struct SelectionReader {
type_: SelectionType,
data: Vec<u8>,
}
impl TypedData for SelectionReader {
fn try_read(&self) -> Option<Box<dyn io::BufRead>> {
Some(Box::new(io::Cursor::new(self.data.clone())))
}
fn type_(&self) -> &dyn TransferType {
&self.type_
}
fn try_as_string(&self) -> io::Result<String> {
fn invalid_data<E>(err: E) -> io::Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
io::Error::new(io::ErrorKind::InvalidData, err)
}
fn decode_utf16_bytes(bytes: &[u8]) -> io::Result<String> {
let utf16 = bytes
.chunks_exact(2)
.map(|chunk| {
let bytes: &[u8; 2] = chunk.try_into().unwrap();
u16::from_ne_bytes(*bytes)
})
.collect::<Vec<_>>();
String::from_utf16(&utf16).map_err(invalid_data)
}
match self.type_.hint() {
Some(TypeHint::Plaintext) | Some(TypeHint::Html) => std::str::from_utf8(&self.data)
.map(|str| str.to_owned())
.map_err(invalid_data)
.or_else(|_| decode_utf16_bytes(&self.data)),
Some(TypeHint::UriList) => String::from_utf8(self.data.clone()).map_err(invalid_data),
_ => Err(io::ErrorKind::InvalidData.into()),
}
}
fn try_as_uris(&self) -> io::Result<Vec<String>> {
if self.type_().hint() != Some(TypeHint::UriList) {
return Err(io::ErrorKind::InvalidData.into());
}
Ok(self
.try_as_string()?
.split(['\n', '\r'])
.filter(|s| !s.is_empty())
.map(ToOwned::to_owned)
.collect())
}
}
#[derive(Debug)]
pub struct DragState {
pub version: c_long,
pub transfer_id: DataTransferId,
pub types: Arc<[SelectionType]>,
pub source_window: xproto::Window,
pub target_window: xproto::Window,
pub pending_fetch_types: VecDeque<(AsyncRequestSerial, SelectionType)>,
pub finished: Option<(XWindow, XWindow)>,
pub accepted: bool,
}
fn generate_transfer_id() -> DataTransferId {
static DATA_TRANSFER_ID: AtomicUsize = AtomicUsize::new(0);
DataTransferId::from_raw(DATA_TRANSFER_ID.fetch_add(1, Ordering::Relaxed).try_into().unwrap())
}
impl Default for DragState {
fn default() -> Self {
Self {
version: Default::default(),
transfer_id: generate_transfer_id(),
types: Default::default(),
source_window: Default::default(),
target_window: Default::default(),
pending_fetch_types: Default::default(),
finished: None,
accepted: Default::default(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(usize)]
pub enum ClipboardSelectionType {
Primary,
Secondary,
Clipboard,
}
impl ClipboardSelectionType {
pub const COUNT: usize = 3;
#[must_use]
pub fn selection_atom(self, atoms: &crate::atoms::Atoms) -> xproto::Atom {
match self {
Self::Primary => xproto::AtomEnum::PRIMARY.into(),
Self::Secondary => xproto::AtomEnum::SECONDARY.into(),
Self::Clipboard => atoms[CLIPBOARD],
}
}
#[must_use]
pub fn from_atom(atoms: &crate::atoms::Atoms, atom: xproto::Atom) -> Option<Self> {
match atom {
_ if Self::Primary.selection_atom(atoms) == atom => Some(Self::Primary),
_ if Self::Secondary.selection_atom(atoms) == atom => Some(Self::Secondary),
_ if Self::Clipboard.selection_atom(atoms) == atom => Some(Self::Clipboard),
_ => None,
}
}
pub fn iter() -> impl Iterator<Item = Self> {
[Self::Primary, Self::Secondary, Self::Clipboard].into_iter()
}
#[must_use]
pub fn property_atom(self, atoms: &crate::atoms::Atoms) -> xproto::Atom {
match self {
Self::Primary => atoms[WINGY_TARGETS_PRIMARY],
Self::Secondary => atoms[WINGY_TARGETS_SECONDARY],
Self::Clipboard => atoms[WINGY_TARGETS_CLIPBOARD],
}
}
}
#[derive(Debug)]
struct IncrState {
data: Vec<u8>,
property: xproto::Atom,
ty: SelectionType,
clipboard: ClipboardSelectionType,
}
#[derive(Debug)]
pub struct ClipboardState {
owned_data: Option<Box<dyn DataTransferSend>>,
transfer_serial: DataTransferId,
x11_serial: u64,
types: Arc<[SelectionType]>,
pending_fetch_types: VecDeque<(AsyncRequestSerial, SelectionType)>,
}
impl Default for ClipboardState {
fn default() -> Self {
Self {
owned_data: Default::default(),
transfer_serial: generate_transfer_id(),
x11_serial: u64::MAX,
types: Default::default(),
pending_fetch_types: Default::default(),
}
}
}
impl ClipboardState {
pub fn get_types(&self, atoms: &Atoms) -> Selection {
if let Some(owned_data) = &self.owned_data {
let mut accepted: Vec<SelectionType> = Vec::new();
owned_data.for_each_available_type(&mut |ty| {
accepted.extend(SelectionType::from_dyn(atoms, ty));
core::ops::ControlFlow::Continue(())
});
return Selection::new(accepted.into());
}
Selection::new(Arc::clone(&self.types))
}
pub fn add_to_fetch(&mut self, serial: AsyncRequestSerial, ty: SelectionType) -> bool {
self.pending_fetch_types.push_back((serial, ty));
self.pending_fetch_types.len() == 1
}
pub fn find_type_by_hint(&self, hint: TypeHint, atoms: &Atoms) -> Option<SelectionType> {
if let Some(owned_data) = &self.owned_data {
let mut result = None;
owned_data.for_each_available_type(&mut |ty| {
if ty.matches(&hint) {
result = SelectionType::from_dyn(atoms, ty).next();
}
if result.is_some() {
core::ops::ControlFlow::Break(())
} else {
core::ops::ControlFlow::Continue(())
}
});
result
} else {
self.types.iter().find(|haystack| haystack.hint() == Some(hint)).cloned()
}
}
}
#[derive(Debug)]
pub struct DataTransferState {
xconn: Arc<XConnection>,
state: Option<DragState>,
clipboards: [ClipboardState; ClipboardSelectionType::COUNT],
clip_incr_send: Vec<IncrState>,
clip_incr_receive: Vec<IncrState>,
}
#[derive(Debug)]
pub struct Selection {
types: Arc<[SelectionType]>,
}
impl Selection {
pub(crate) fn new(types: Arc<[SelectionType]>) -> Selection {
Selection { types }
}
}
pub struct FinishedDataTransfer {
id: DataTransferId,
serial: AsyncRequestSerial,
value: Arc<dyn TypedData>,
window: WindowId,
}
impl FinishedDataTransfer {
#[must_use]
pub fn window(&self) -> WindowId {
self.window
}
#[must_use]
pub fn into_event(self) -> WindowEvent {
WindowEvent::DataTransferReceived { id: self.id, serial: self.serial, value: self.value }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SelectionType {
hint: Option<TypeHint>,
atom: xproto::Atom,
}
impl SelectionType {
fn atom_hints(atoms: &Atoms) -> impl Iterator<Item = (xproto::Atom, TypeHint)> {
[
(atoms[TextUriList], TypeHint::UriList),
(atoms[STRING], TypeHint::Plaintext),
(atoms[UTF8_STRING], TypeHint::Plaintext),
(atoms[TextPlain], TypeHint::Plaintext),
(atoms[TextPlainCharsetUtf8], TypeHint::Plaintext),
(atoms[TextHtml], TypeHint::Html),
(atoms[TextHtmlCharsetUtf8], TypeHint::Html),
(atoms[ApplicationRtf], TypeHint::Rtf),
(atoms[AudioAac], TypeHint::Audio { extension_hint: Some("aac") }),
(atoms[AudioAiff], TypeHint::Audio { extension_hint: Some("aif") }),
(atoms[AudioFlac], TypeHint::Audio { extension_hint: Some("flac") }),
(atoms[AudioVndWav], TypeHint::Audio { extension_hint: Some("wav") }),
(atoms[AudioVndWave], TypeHint::Audio { extension_hint: Some("wav") }),
(atoms[AudioWav], TypeHint::Audio { extension_hint: Some("wav") }),
(atoms[AudioWave], TypeHint::Audio { extension_hint: Some("wav") }),
(atoms[AudioXWav], TypeHint::Audio { extension_hint: Some("wav") }),
(atoms[AudioOgg], TypeHint::Audio { extension_hint: Some("ogg") }),
(atoms[AudioMpeg], TypeHint::Audio { extension_hint: Some("mp3") }),
(atoms[ImageBmp], TypeHint::Image { extension_hint: Some("bmp") }),
(atoms[ImageGif], TypeHint::Image { extension_hint: Some("gif") }),
(atoms[ImageJpeg], TypeHint::Image { extension_hint: Some("jpg") }),
(atoms[ImagePjpeg], TypeHint::Image { extension_hint: Some("jpg") }),
(atoms[ImagePng], TypeHint::Image { extension_hint: Some("png") }),
(atoms[ImageRaw], TypeHint::Image { extension_hint: Some("raw") }),
(atoms[ImageSvg], TypeHint::Image { extension_hint: Some("svg") }),
(atoms[ImageTiff], TypeHint::Image { extension_hint: Some("tiff") }),
(atoms[ImageWebp], TypeHint::Image { extension_hint: Some("webp") }),
(atoms[ImageXIcon], TypeHint::Image { extension_hint: Some("ico") }),
]
.into_iter()
}
pub(crate) fn new(atoms: &Atoms, atom: xproto::Atom) -> Self {
let hint =
Self::atom_hints(atoms).find_map(|(haystack, hint)| (haystack == atom).then_some(hint));
Self { hint, atom }
}
fn from_dyn(atoms: &Atoms, type_: &dyn TransferType) -> impl Iterator<Item = Self> {
let downcast = type_.cast_ref::<Self>().cloned();
let downcast_failed = downcast.is_none();
let from_hint = downcast_failed
.then_some(
Self::atom_hints(atoms)
.filter(|(_, haystack)| TransferType::matches(haystack, type_))
.map(|(atom, hint)| Self { atom, hint: Some(hint) }),
)
.into_iter()
.flatten();
downcast.into_iter().chain(from_hint)
}
pub fn atom(&self) -> xproto::Atom {
self.atom
}
}
impl TransferType for SelectionType {
fn hint(&self) -> Option<TypeHint> {
self.hint
}
fn matches(&self, other: &dyn TransferType) -> bool {
if let Some(other_mime) = other.cast_ref::<Self>() {
*self == *other_mime
} else {
self.hint().is_some_and(|hint| other.hint() == Some(hint))
}
}
}
impl DataTransfer for Selection {
fn for_each_available_type<'this>(
&'this self,
func: &'_ mut dyn FnMut(&'this dyn TransferType) -> std::ops::ControlFlow<()>,
) {
let _ = self.types.iter().map(|mime| mime as &dyn TransferType).try_for_each(func);
}
}
impl DataTransferState {
pub fn new(xconn: Arc<XConnection>) -> Self {
let conn = xconn.xcb_connection();
match conn.xfixes_query_version(5, 0).map_err(ReplyError::from).and_then(|c| c.reply()) {
Ok(_) => {
let root = xconn.default_root().root;
for item in ClipboardSelectionType::iter() {
conn.xfixes_select_selection_input(
root,
item.selection_atom(xconn.atoms()),
xfixes::SelectionEventMask::SET_SELECTION_OWNER,
)
.expect_then_ignore_error("Failed to select selection owner changes");
}
},
Err(err) => error!("Failed to query the XFixes version: {err}"),
}
DataTransferState {
xconn,
state: Default::default(),
clipboards: Default::default(),
clip_incr_send: Default::default(),
clip_incr_receive: Default::default(),
}
}
pub fn state(&self) -> Option<&DragState> {
self.state.as_ref()
}
pub fn state_mut(&mut self) -> Option<&mut DragState> {
self.state.as_mut()
}
pub fn find_type_by_hint(&self, hint: TypeHint) -> Option<&SelectionType> {
self.state.as_ref()?.types.iter().find(|haystack| haystack.hint() == Some(hint))
}
pub fn init_state(
&mut self,
version: c_long,
source_window: xproto::Window,
target_window: xproto::Window,
types: Arc<[SelectionType]>,
) -> &DragState {
self.state.insert(DragState {
version,
types,
source_window,
target_window,
..Default::default()
})
}
pub fn reset_state(&mut self) -> Option<DragState> {
self.state.take()
}
pub unsafe fn send_finished(
&self,
this_window: xproto::Window,
target_window: xproto::Window,
) -> Result<(), X11Error> {
let atoms = self.xconn.atoms();
let Some(state) = &self.state else {
return Err(X11Error::UnexpectedNull(
"Drag-and-drop state was not initialized (called `send_finished` before XdndEnter",
));
};
let (accepted, action) =
if state.accepted { (1, atoms[XdndActionCopy]) } else { (0, atoms[DndNone]) };
self.xconn
.send_client_msg(
target_window,
target_window,
atoms[XdndFinished] as _,
None,
[this_window, accepted, action as _, 0, 0],
)?
.ignore_error();
Ok(())
}
pub unsafe fn get_type_list(
&self,
source_window: xproto::Window,
) -> Result<Vec<xproto::Atom>, util::GetPropertyError> {
let atoms = self.xconn.atoms();
self.xconn.get_property(
source_window,
atoms[XdndTypeList],
xproto::Atom::from(xproto::AtomEnum::ATOM),
)
}
pub unsafe fn send_status(
&self,
this_window: xproto::Window,
target_window: xproto::Window,
status: DndState,
) -> Result<(), X11Error> {
let atoms = self.xconn.atoms();
let (accepted, action) = match status {
DndState::Accepted => (1, atoms[XdndActionCopy]),
DndState::Rejected => (0, atoms[DndNone]),
};
self.xconn
.send_client_msg(
target_window,
target_window,
atoms[XdndStatus] as _,
None,
[this_window, accepted, 0, 0, action as _],
)?
.ignore_error();
Ok(())
}
pub fn read_data(
&self,
window: xproto::Window,
type_: SelectionType,
) -> Result<SelectionReader, util::GetPropertyError> {
let atoms = self.xconn.atoms();
let type_atom = type_.atom();
let bytes = self.xconn.get_property(window, atoms[XdndSelection], type_atom)?;
Ok(SelectionReader { type_, data: bytes })
}
pub fn resolve_clipboard_type(
&self,
data_transfer: DataTransferId,
) -> Option<ClipboardSelectionType> {
ClipboardSelectionType::iter()
.find(|&clip| self.clipboards[clip as usize].transfer_serial == data_transfer)
}
pub fn get_clipboard(&self, clipboard: ClipboardSelectionType) -> &ClipboardState {
&self.clipboards[clipboard as usize]
}
pub fn get_clipboard_mut(&mut self, clipboard: ClipboardSelectionType) -> &mut ClipboardState {
&mut self.clipboards[clipboard as usize]
}
pub fn attach_clipboard_property(&mut self, xev: &XSelectionRequestEvent) -> bool {
let atoms = self.xconn.atoms();
let prop = xev.property as xproto::Atom;
let target = xev.target as xproto::Atom;
let requestor = xev.requestor as xproto::Window;
let Some(resolved_clipboard) = ClipboardSelectionType::from_atom(atoms, xev.selection as _)
else {
warn!("Received request for unknown target clipboard {}", self.xconn.atom_str(prop));
return false;
};
let Some(owned_data) = &self.get_clipboard(resolved_clipboard).owned_data else {
warn!("Received request for clipboard with no data {:?}", resolved_clipboard);
return false;
};
let replace = xproto::PropMode::REPLACE;
if target == atoms[TARGETS] as _ {
let mut accepted: Vec<xproto::Atom> = vec![atoms[TIMESTAMP] as _, atoms[TARGETS] as _];
owned_data.for_each_available_type(&mut |ty| {
accepted.extend(SelectionType::from_dyn(atoms, ty).map(|ty| ty.atom()));
core::ops::ControlFlow::Continue(())
});
self.xconn
.change_property(requestor, prop, xproto::AtomEnum::ATOM.into(), replace, &accepted)
.expect_then_ignore_error("unable to change property to set TARGETS");
let advertised =
accepted.iter().map(|&atom| self.xconn.atom_str(atom)).collect::<Vec<_>>();
trace!("Written TARGETS {advertised:?} into property {} ", self.xconn.atom_str(prop));
true
} else if target == atoms[TIMESTAMP] as _ {
self.xconn
.change_property(
requestor,
prop,
xproto::AtomEnum::INTEGER.into(),
replace,
&[CURRENT_TIME],
)
.expect_then_ignore_error("unable to change property to set TIMESTAMP");
trace!("Written TIMESTAMP into property {} ", self.xconn.atom_str(prop));
true
} else {
let ty = SelectionType::new(atoms, target);
let Some(send_data) = owned_data.data_for_type(&ty) else {
warn!(
"Selection request for unknown selection target {}",
self.xconn.atom_str(target)
);
return false;
};
let data = match send_data {
SendData::Uris(strings) => encode_uri_list(strings),
SendData::String(str) => str.into_bytes(),
SendData::Bytes(binary) => binary,
_ => {
warn!("Unknown send data {send_data:?}");
return false;
},
};
if data.len() < INCR_CHUNK_SIZE_BYTES {
self.xconn
.change_property(requestor, prop, ty.atom(), replace, &data)
.expect_then_ignore_error("unable to change property to send clipboard data");
trace!(
"Directly written data of type {} into {} ",
self.xconn.atom_str(target),
self.xconn.atom_str(prop)
);
} else if let Err(e) = self.start_incr_sender(xev, data, ty) {
warn!("Unable to start INCR sender {e}");
return false;
}
true
}
}
fn start_incr_sender(
&mut self,
xev: &XSelectionRequestEvent,
data: Vec<u8>,
ty: SelectionType,
) -> Result<(), crate::event_loop::X11Error> {
let xconn = &self.xconn;
let atoms = xconn.atoms();
let property = xev.property as xproto::Atom;
let requestor = xev.requestor as xproto::Window;
let Some(clipboard) = ClipboardSelectionType::from_atom(atoms, xev.selection as _) else {
return Err(X11Error::UnexpectedNull("getting clipboard selection"));
};
let current_attributes =
xconn.xcb_connection().get_window_attributes(requestor)?.reply()?;
let event_mask =
Some(current_attributes.your_event_mask | xproto::EventMask::PROPERTY_CHANGE);
xconn.xcb_connection().change_window_attributes(
requestor,
&xproto::ChangeWindowAttributesAux { event_mask, ..Default::default() },
)?;
self.clip_incr_send.push(IncrState { data, property, ty, clipboard });
xconn
.change_property::<u32>(
requestor,
property,
atoms[INCR],
xproto::PropMode::REPLACE,
&[],
)
.expect_then_ignore_error("unable to change property to start INCR sender");
trace!("Starting INCR sender to {requestor} property {}", xconn.atom_str(property));
Ok(())
}
pub fn start_incr_receiver(&mut self, xev: &XSelectionEvent) {
let property = xev.property as _;
let target = xev.target as _;
let Some(clipboard) =
ClipboardSelectionType::from_atom(self.xconn.atoms(), xev.selection as _)
else {
warn!("Invalid clipboard for starting INCR receiver");
return;
};
let ty = SelectionType::new(self.xconn.atoms(), target);
trace!(
"Starting INCR receiver {ty:?} property {} target {} requestor {}",
self.xconn.atom_str(property),
self.xconn.atom_str(target),
xev.requestor
);
self.clip_incr_receive.push(IncrState { data: Vec::new(), property, ty, clipboard });
}
#[must_use]
pub fn check_incr_receive_data(
&mut self,
xev: &XPropertyEvent,
) -> Option<FinishedDataTransfer> {
let atom = xev.atom as xproto::Atom;
let xwindow = xev.window as xproto::Window;
if xev.state as u32 != xproto::Property::NEW_VALUE.into() {
return None;
}
let index = self
.clip_incr_receive
.iter()
.position(|IncrState { property, .. }| *property == atom)?;
let IncrState { data, .. } = &mut self.clip_incr_receive[index];
let (_, read_bytes) = match self.xconn.get_dynamic_property(atom, xwindow, data) {
Ok(value) => value,
Err(e) => {
error!("Unable to read property to check INCR receive: {e}");
self.clip_incr_receive.remove(index); return None;
},
};
if read_bytes != 0 {
return None;
}
let IncrState { data, property, ty, clipboard } = self.clip_incr_receive.remove(index);
trace!(
"We are done receiving from {} ty {}",
self.xconn.atom_str(property),
self.xconn.atom_str(ty.atom())
);
self.send_clipboard_data(xwindow, clipboard, data)
}
pub fn do_next_clipboard_fetch(
&mut self,
clipboard: ClipboardSelectionType,
xwindow: xproto::Window,
) {
let selection = clipboard.selection_atom(self.xconn.atoms());
while let Some((_, ty)) = self.get_clipboard(clipboard).pending_fetch_types.front() {
match self.xconn.convert_selection(xwindow, selection, ty.atom(), selection) {
Ok(()) => {
trace!("Converting next selection {}", self.xconn.atom_str(ty.atom()));
return;
},
Err(e) => {
self.get_clipboard_mut(clipboard).pending_fetch_types.pop_front();
error!("Could not convert next selection so skipping: {e}");
continue;
},
}
}
}
#[must_use]
pub fn send_clipboard_data(
&mut self,
xwindow: xproto::Window,
clipboard: ClipboardSelectionType,
data: Vec<u8>,
) -> Option<FinishedDataTransfer> {
let Some((serial, type_)) =
self.get_clipboard_mut(clipboard).pending_fetch_types.pop_front()
else {
warn!("Got finished fetch but no fetches were pending");
return None;
};
let value = Arc::new(SelectionReader { type_, data });
let id = self.get_clipboard(clipboard).transfer_serial;
self.do_next_clipboard_fetch(clipboard, xwindow);
let window = crate::event_loop::mkwid(xwindow);
Some(FinishedDataTransfer { id, serial, value, window })
}
pub fn check_incr_send_data(&mut self, xev: &XPropertyEvent) {
let atom = xev.atom as xproto::Atom;
let xwindow = xev.window as xproto::Window;
if xev.state as u32 != xproto::Property::DELETE.into() {
return;
}
self.clip_incr_send.retain_mut(|IncrState { data, property, ty, .. }| {
if *property != atom {
return true;
}
let bytes_to_take = INCR_CHUNK_SIZE_BYTES.min(data.len());
let sending = &data[..bytes_to_take];
if let Err(err) = self.xconn.change_property(
xwindow,
atom,
ty.atom(),
xproto::PropMode::REPLACE,
sending,
) {
error!("Unable to change property {} for INCR: {}", self.xconn.atom_str(atom), err);
return false; }
trace!("Written partial data {:?} to {} ", sending, self.xconn.atom_str(atom),);
data.drain(..bytes_to_take);
if bytes_to_take != 0 {
return true;
}
trace!(
"We are done sending {} ty {}",
self.xconn.atom_str(*property),
self.xconn.atom_str(ty.atom())
);
false
});
}
pub fn populate_targets(&mut self, selection: xproto::Atom, data: Vec<u8>) {
let targets_bytes = data.chunks_exact(size_of::<xproto::Atom>());
if !targets_bytes.remainder().is_empty() {
warn!("Trailing bytes in list of targets from X11");
}
let targets: Vec<_> = targets_bytes
.map(|bytes| xproto::Atom::from_ne_bytes(bytes.try_into().unwrap()))
.map(|atom| SelectionType::new(self.xconn.atoms(), atom))
.filter(|ty| ty.hint.is_some())
.collect();
let Some(clipboard) = ClipboardSelectionType::from_atom(self.xconn.atoms(), selection)
else {
warn!("Got TARGETS for an unknown selection {}", self.xconn.atom_str(selection));
return;
};
let available = targets.iter().map(|t| self.xconn.atom_str(t.atom())).collect::<Vec<_>>();
trace!("Received available targets {available:?} for clipboard {clipboard:?}");
self.get_clipboard_mut(clipboard).types = targets.into();
}
pub fn request_updated_targets(
&mut self,
clipboard: ClipboardSelectionType,
xwindow: xproto::Window,
serial: u64,
) {
let clipboard_state = self.get_clipboard_mut(clipboard);
if serial == clipboard_state.x11_serial {
return;
}
clipboard_state.transfer_serial = generate_transfer_id();
clipboard_state.x11_serial = serial;
if clipboard_state.owned_data.is_some() {
return;
}
self.clear_clipboard(clipboard);
let atoms = self.xconn.atoms();
let selection = clipboard.selection_atom(atoms);
let property = clipboard.property_atom(atoms);
if let Err(e) = self.xconn.convert_selection(xwindow, selection, atoms[TARGETS], property) {
let selection = self.xconn.atom_str(selection);
let property = self.xconn.atom_str(property);
error!("Unable to request TARGETS for {selection} into {property}: {e}")
}
}
pub fn request_clipboard_read(&mut self, clipboard: ClipboardSelectionType) -> DataTransferId {
let clipboard_state = self.get_clipboard(clipboard);
if clipboard_state.owned_data.is_some() {
trace!("Clipboard is owned by current application");
}
clipboard_state.transfer_serial
}
pub fn set_clipboard(
&mut self,
xwindow: xproto::Window,
send_data: Box<dyn DataTransferSend>,
clipboard: ClipboardSelectionType,
) {
self.get_clipboard_mut(clipboard).owned_data = Some(send_data);
self.xconn.set_selection_owner(xwindow, clipboard.selection_atom(self.xconn.atoms()));
}
pub(crate) fn fetch_dnd_data_transfer(
&mut self,
type_: &dyn TransferType,
) -> Result<AsyncRequestSerial, TransferError> {
let type_ = type_
.cast_ref::<SelectionType>()
.or_else(|| self.find_type_by_hint(type_.hint()?))
.cloned()
.ok_or(TransferError::Failed)?;
let Some(state) = self.state.as_mut() else {
return Err(TransferError::Failed);
};
let should_emit_convert_selection = state.pending_fetch_types.is_empty();
let atom = type_.atom();
let selection = self.xconn.atoms()[XdndSelection];
if should_emit_convert_selection {
if let Err(e) =
self.xconn.convert_selection(state.target_window, selection, atom, selection)
{
return Err(TransferError::Os(os_error!(format!(
"could not convert selection: {e}"
))));
}
}
let serial = AsyncRequestSerial::get();
state.pending_fetch_types.push_back((serial, type_));
Ok(serial)
}
pub(crate) fn fetch_clipboard_data_transfer(
&mut self,
clipboard: ClipboardSelectionType,
type_: &dyn TransferType,
xwindow: xproto::Window,
) -> Result<AsyncRequestSerial, TransferError> {
let ty = type_
.cast_ref::<SelectionType>()
.cloned()
.or_else(|| {
self.get_clipboard(clipboard).find_type_by_hint(type_.hint()?, self.xconn.atoms())
})
.ok_or(TransferError::Failed)?;
let serial = AsyncRequestSerial::get();
let should_emit_convert_selection =
self.get_clipboard_mut(clipboard).add_to_fetch(serial, ty);
if should_emit_convert_selection {
self.do_next_clipboard_fetch(clipboard, xwindow);
}
Ok(serial)
}
pub fn clear_clipboard(&mut self, clipboard: ClipboardSelectionType) {
let clipboard_state = self.get_clipboard_mut(clipboard);
if !clipboard_state.pending_fetch_types.is_empty() {
trace!("Clearing all pending fetch types for {clipboard:?}");
clipboard_state.pending_fetch_types.clear();
}
clipboard_state.types = Default::default();
if clipboard_state.owned_data.take().is_some() {
trace!("Cleared owned data for {clipboard:?}")
}
self.clip_incr_receive.retain(|receive| receive.clipboard != clipboard);
self.clip_incr_send.retain(|receive| receive.clipboard != clipboard);
}
}