use std::{
cell::{Cell, RefCell},
collections::HashSet,
ffi::OsString,
io, mem,
os::unix::io::AsRawFd,
rc::Rc,
};
use failure::Fail;
use os_pipe::{pipe, PipeReader};
use wayland_client::{ConnectError, EventQueue};
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1;
use crate::{
common::{self, initialize, CommonData},
handlers::{data_device_handler, DataDeviceHandler},
seat_data::SeatData,
utils::is_text,
};
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
pub enum ClipboardType {
Regular,
Primary,
}
impl Default for ClipboardType {
#[inline]
fn default() -> Self {
ClipboardType::Regular
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
pub enum MimeType<'a> {
Any,
Text,
TextWithPriority(&'a str),
Specific(&'a str),
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, PartialOrd, Ord)]
pub enum Seat<'a> {
Unspecified,
Specific(&'a str),
}
impl Default for Seat<'_> {
#[inline]
fn default() -> Self {
Seat::Unspecified
}
}
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "There are no seats")]
NoSeats,
#[fail(display = "The clipboard of the requested seat is empty")]
ClipboardEmpty,
#[fail(display = "No suitable type of content copied")]
NoMimeType,
#[fail(display = "Couldn't connect to the Wayland compositor")]
WaylandConnection(#[cause] ConnectError),
#[fail(display = "Wayland compositor communication error")]
WaylandCommunication(#[cause] io::Error),
#[fail(display = "A required Wayland protocol ({} version {}) is not supported by the compositor",
name, version)]
MissingProtocol { name: &'static str, version: u32 },
#[fail(display = "The compositor does not support primary selection")]
PrimarySelectionUnsupported,
#[fail(display = "The requested seat was not found")]
SeatNotFound,
#[fail(display = "Couldn't create a pipe for content transfer")]
PipeCreation(#[cause] io::Error),
}
impl From<common::Error> for Error {
fn from(x: common::Error) -> Self {
use common::Error::*;
match x {
WaylandConnection(err) => Error::WaylandConnection(err),
WaylandCommunication(err) => Error::WaylandCommunication(err),
MissingProtocol { name, version } => Error::MissingProtocol { name, version },
}
}
}
fn get_offer(primary: bool,
seat: Seat<'_>,
socket_name: Option<OsString>)
-> Result<(EventQueue, ZwlrDataControlOfferV1), Error> {
let CommonData { mut queue,
clipboard_manager,
seats, } = initialize(primary, socket_name)?;
if seats.borrow_mut().is_empty() {
return Err(Error::NoSeats);
}
let supports_primary = Rc::new(Cell::new(false));
for seat in &*seats.borrow_mut() {
let mut handler = DataDeviceHandler::new(seat.detach(), primary, supports_primary.clone());
let device = clipboard_manager.get_data_device(seat);
device.quick_assign(move |data_device, event, dispatch_data| {
data_device_handler(&mut handler, data_device, event, dispatch_data)
});
}
queue.sync_roundtrip(&mut (), |_, _, _| {})
.map_err(Error::WaylandCommunication)?;
if primary && !supports_primary.get() {
return Err(Error::PrimarySelectionUnsupported);
}
let offer = seats.borrow_mut()
.iter()
.map(|seat| seat.as_ref().user_data().get::<RefCell<SeatData>>().unwrap().borrow())
.find_map(|data| {
let SeatData { name, offer, .. } = &*data;
match seat {
Seat::Unspecified => return Some(offer.clone()),
Seat::Specific(desired_name) => {
if let Some(name) = name {
if name == desired_name {
return Some(offer.clone());
}
}
}
}
None
});
if offer.is_none() {
return Err(Error::SeatNotFound);
}
offer.unwrap().map(|x| (queue, x)).ok_or(Error::ClipboardEmpty)
}
#[inline]
pub fn get_mime_types(clipboard: ClipboardType, seat: Seat<'_>) -> Result<HashSet<String>, Error> {
get_mime_types_internal(clipboard, seat, None)
}
pub(crate) fn get_mime_types_internal(clipboard: ClipboardType,
seat: Seat<'_>,
socket_name: Option<OsString>)
-> Result<HashSet<String>, Error> {
let primary = clipboard == ClipboardType::Primary;
let (_, offer) = get_offer(primary, seat, socket_name)?;
let mut mime_types = offer.as_ref()
.user_data()
.get::<RefCell<HashSet<String>>>()
.unwrap()
.borrow_mut();
let empty_hash_set = HashSet::new();
Ok(mem::replace(&mut *mime_types, empty_hash_set))
}
#[inline]
pub fn get_contents(clipboard: ClipboardType,
seat: Seat<'_>,
mime_type: MimeType<'_>)
-> Result<(PipeReader, String), Error> {
get_contents_internal(clipboard, seat, mime_type, None)
}
pub(crate) fn get_contents_internal(clipboard: ClipboardType,
seat: Seat<'_>,
mime_type: MimeType<'_>,
socket_name: Option<OsString>)
-> Result<(PipeReader, String), Error> {
let primary = clipboard == ClipboardType::Primary;
let (mut queue, offer) = get_offer(primary, seat, socket_name)?;
let mut mime_types = offer.as_ref()
.user_data()
.get::<RefCell<HashSet<String>>>()
.unwrap()
.borrow_mut();
let mime_type = match mime_type {
MimeType::Any => mime_types.take("text/plain;charset=utf-8")
.or_else(|| mime_types.take("UTF8_STRING"))
.or_else(|| mime_types.iter().find(|x| is_text(x)).cloned())
.or_else(|| mime_types.drain().next()),
MimeType::Text => mime_types.take("text/plain;charset=utf-8")
.or_else(|| mime_types.take("UTF8_STRING"))
.or_else(|| mime_types.drain().find(|x| is_text(x))),
MimeType::TextWithPriority(priority) => mime_types.take(priority)
.or_else(|| mime_types.take("text/plain;charset=utf-8"))
.or_else(|| mime_types.take("UTF8_STRING"))
.or_else(|| mime_types.drain().find(|x| is_text(x))),
MimeType::Specific(mime_type) => mime_types.take(mime_type),
};
if mime_type.is_none() {
return Err(Error::NoMimeType);
}
let mime_type = mime_type.unwrap();
let (read, write) = pipe().map_err(Error::PipeCreation)?;
offer.receive(mime_type.clone(), write.as_raw_fd());
drop(write);
queue.sync_roundtrip(&mut (), |_, _, _| {})
.map_err(Error::WaylandCommunication)?;
Ok((read, mime_type))
}